modified: amazon/__pycache__/approve.cpython-39.pyc

modified:   amazon/__pycache__/del_brand.cpython-39.pyc
	modified:   amazon/__pycache__/main.cpython-39.pyc
	modified:   amazon/approve.py
	new file:   amazon/backend_api.py
	modified:   amazon/del_brand.py
	modified:   amazon/main.py
	modified:   blueprints/main.py
	modified:   main.py
This commit is contained in:
铭坤
2026-04-08 16:03:48 +08:00
parent 3149d80456
commit 7151f6b5e0
9 changed files with 231 additions and 45 deletions

View File

@@ -7,6 +7,12 @@ from config import runing_task, runing_shop
from datetime import datetime
from amazon.del_brand import AmamzonBase, kill_process
# 导入 webview 用于前端通知
try:
import webview
except ImportError:
webview = None
class AmzoneApprove(AmamzonBase):
def SwitchPage(self):
@@ -190,8 +196,8 @@ class AmzoneApprove(AmamzonBase):
num = 0
while 1:
if num > 3: #测试
return
# if num > 3: #测试
# return
# 等待加载完成
load_ele = self.tab.eles("xpath://div[contains(@class,'Loader-module__loader')]",timeout=5)
if len(load_ele) > 0:
@@ -202,14 +208,17 @@ class AmzoneApprove(AmamzonBase):
page_pamel = self.tab.eles('xpath://kat-pagination',timeout=5)
if len(page_pamel) > 0:
current_page = page_pamel[0].sr('xpath:.//ul[@class="pages"]//li[@aria-current="true"]').text
print(f"开始处理,当前页码: {current_page}")
print(f"【current_page】开始处理,当前页码: {current_page}")
sku_ls = self.tab.eles("xpath://div[@data-sku]",timeout=3)
sku_ls = self.tab.eles("xpath://div[@data-sku]",timeout=10)
print(f"获取到 {len(sku_ls)}")
for sku_ele in sku_ls[0:2]:
# for sku_ele in sku_ls[0:2]:
for sku_ele in sku_ls:
# solve_problem = sku_ele.eles('xpath:.//kat-link[@label="解决商品信息问题"]')
solve_problem = sku_ele.eles('xpath:.//kat-link[@label="解决商品信息问题"]|.//kat-link[@label="修复被禁止显示的商品"]|//kat-link[@label="解决商品移除风险"]')
solve_problem = sku_ele.eles('xpath:.//kat-link[@label="解决商品信息问题"]|.//kat-link[@label="修复被禁止显示的商品"]|.//kat-link[@label="解决商品移除风险"]')
if len(solve_problem) == 0:
print(f"{asin},没有处理入口,不处理")
yield (asin,"没有处理入口,不处理")
continue
asin = sku_ele.ele('xpath:.//div[contains(@class,"JanusSplitBox-module__container")]//div[contains(@class,"JanusSplitBox-module__panel--") and contains(string(.),"ASIN")]/..//div[last()]').text
print(f"ASIN {asin} 存在问题,正在点击解决...")
@@ -235,9 +244,9 @@ class AmzoneApprove(AmamzonBase):
# 解决商品信息违规问题 按钮
kat_box_ls = action_panel.eles('xpath:.//kat-box[@variant="white"]',timeout=5)
if len(kat_box_ls) == 0:
print(f"ASIN {asin} 的操作面板中未找到选项,跳过...")
continue
# if len(kat_box_ls) == 0:
# print(f"ASIN {asin} 的操作面板中未找到选项,跳过...")
# continue
print(f"ASIN {asin}需要处理的有:{len(kat_box_ls)}个问题.")
# 解决商品信息违规问题 按钮,有多少个都要处理
for i in range(len(kat_box_ls)):
@@ -284,7 +293,7 @@ class AmzoneApprove(AmamzonBase):
kat_box_ls = action_panel.eles('xpath:.//kat-box[@variant="white"]', timeout=5)
# 修复商品信息
repair_product = action_panel.eles('xpath://kat-button[@class="action-button"]',timeout=5)
repair_product = action_panel.eles('xpath:.//kat-button[@class="action-button"]',timeout=5)
if len(repair_product) > 0:
print(f"ASIN {asin} 存在修复商品信息按钮,正在点击...")
self.clear_tab()
@@ -316,7 +325,7 @@ class AmzoneApprove(AmamzonBase):
break
next_page_btn.click()
num += 1
print(f"正在翻页,已翻 {num} 页...")
# print(f"正在翻页,已翻 {num} 页...")
class ApproveTask:
@@ -330,6 +339,75 @@ class ApproveTask:
"UK": "英国"
}
@staticmethod
def show_notification(message: str, message_type: str = "error"):
"""显示 pywebview 顶层通知5秒后自动消失
Args:
message: 通知消息
message_type: 消息类型 (success/warning/error/info)
"""
if webview and webview.windows:
try:
# 转义单引号,防止 JavaScript 语法错误
safe_message = message.replace("'", "\\'").replace('"', '\\"').replace('\n', '\\n')
# 设置通知样式颜色
color_map = {
'success': '#67C23A',
'warning': '#E6A23C',
'error': '#F56C6C',
'info': '#909399'
}
bg_color = color_map.get(message_type, '#F56C6C')
# 构建前端通知的 JavaScript 代码 - 创建原生 HTML 通知
js_code = f"""
(function() {{
// 移除已存在的通知
var existingNotif = document.getElementById('pywebview-notification');
if (existingNotif) {{
existingNotif.remove();
}}
// 创建通知容器
var notif = document.createElement('div');
notif.id = 'pywebview-notification';
notif.style.cssText = 'position: fixed; top: 20px; left: 50%; transform: translateX(-50%); ' +
'background: {bg_color}; color: white; padding: 12px 20px; border-radius: 4px; ' +
'box-shadow: 0 2px 12px rgba(0,0,0,0.3); z-index: 99999; font-size: 14px; ' +
'max-width: 600px; word-wrap: break-word; display: flex; align-items: center; gap: 10px;';
// 添加消息内容
var msgSpan = document.createElement('span');
msgSpan.textContent = '{safe_message}';
notif.appendChild(msgSpan);
// 添加关闭按钮
var closeBtn = document.createElement('span');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = 'cursor: pointer; font-size: 18px; font-weight: bold; margin-left: 10px;';
closeBtn.onclick = function() {{ notif.remove(); }};
notif.appendChild(closeBtn);
// 添加到页面
document.body.appendChild(notif);
// 5秒后自动消失
setTimeout(function() {{
if (notif && notif.parentNode) {{
notif.style.transition = 'opacity 0.3s';
notif.style.opacity = '0';
setTimeout(function() {{ notif.remove(); }}, 300);
}}
}}, 5000);
}})();
"""
# 在第一个窗口中执行 JavaScript
webview.windows[0].evaluate_js(js_code)
except Exception as e:
print(f"显示通知失败: {str(e)}")
"""
处理流程:
1、解析任务json,提取对应的companyName 组组装user_info实例化 AmzoneApprove
@@ -385,6 +463,8 @@ class ApproveTask:
"""
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if level == "ERROR":
self.show_notification(message, "error")
print(f"[{timestamp}] [ApproveTask] [{level}] {message}")
def process_task(self, task_data: dict):
@@ -439,6 +519,7 @@ class ApproveTask:
shop_name = shop_item.get("shopName", "未知店铺")
self.log(f"[{idx}/{len(items)}] 开始处理店铺: {shop_name}")
self.show_notification(f"开始处理店铺: {shop_name}", "info")
try:
self.process_shop(shop_item, country_codes, task_id,risk_listing_filter)
@@ -502,11 +583,11 @@ class ApproveTask:
self.log(f"尝试打开店铺 {shop_name} (第 {retry + 1}/{max_retries} 次)")
# 如果不是第一次尝试,先杀进程
if retry > 0:
self.log("重试前先杀掉浏览器进程...")
kill_process("v6")
kill_process("v5")
time.sleep(2)
# if retry > 0:
# self.log("重试前先杀掉浏览器进程...")
# kill_process("v6")
# kill_process("v5")
# time.sleep(2)
# 组装用户信息并创建驱动
user_info = {
@@ -522,7 +603,8 @@ class ApproveTask:
else:
self.log(f"打开店铺失败: {browser}", "WARNING")
driver = None
# 判断是否需要登录
except Exception as e:
import traceback
self.log(f"打开店铺异常: {str(e)}", "ERROR")
@@ -535,7 +617,8 @@ class ApproveTask:
# 检查是否成功打开
if not driver or not browser or browser == "店铺不存在":
self.log(f"店铺 {shop_name} 打开失败,已重试 {max_retries} 次,跳过该店铺", "ERROR")
error_msg = f"店铺 {shop_name} 打开失败,已重试 {max_retries} 次,跳过该店铺"
self.log(error_msg, "ERROR")
# 从执行列表中移除
if shop_name in runing_shop:
del runing_shop[shop_name]
@@ -589,7 +672,9 @@ class ApproveTask:
# 转换国家代码为中文名称
country_name = self.country_info.get(country_code, country_code)
self.log(f"开始处理国家: {country_name} ({country_code})")
info_mes = f"开始处理国家: {country_name} ({country_code})"
self.log(info_mes)
self.show_notification(info_mes, "info")
# 更新当前处理的国家
if task_id in runing_task:
@@ -630,7 +715,8 @@ class ApproveTask:
# 如果切换失败,直接返回
if not switch_success:
self.log(f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家", "ERROR")
error_message = f"切换到国家 {country_name} 失败,已重试 {max_retries} 次,跳过该国家"
self.log(error_message, "ERROR")
if task_id in runing_task:
runing_task[task_id]["processed_countries"] += 1
return
@@ -819,3 +905,5 @@ if __name__ == '__main__':
print(f"ASIN {asin} 的处理结果: {status}")
print("已完成操作")
# for i in range(0):
# print(i)

View File

View File

@@ -10,6 +10,7 @@ import os
from typing import Literal
from DrissionPage import Chromium
from DrissionPage.common import By
from DrissionPage._pages.chromium_tab import ChromiumTab
def kill_process(version: Literal["v5", "v6"]):
@@ -33,7 +34,7 @@ class ZiniaoDriver:
self.socket_port = socket_port
self.client_path = None
self.browser = None
self.tab = None
self.tab: ChromiumTab = None
self.store_id = None
def get_zinaio_exe(self, protocol_name: str = "superbrowser"):
@@ -136,11 +137,11 @@ class ZiniaoDriver:
print(r)
return r.get("browserList")
elif str(r.get("statusCode")) == "-10003":
print(f"login Err {json.dumps(r, ensure_ascii=False)}")
exit()
print(f"【get_browser_list】登录失败 {json.dumps(r, ensure_ascii=False)}")
# exit()
else:
print(f"Fail {json.dumps(r, ensure_ascii=False)} ")
exit()
print(f"【get_browser_list】失败 {json.dumps(r, ensure_ascii=False)} ")
# exit()
def open_store(self, store_info, isWebDriverReadOnlyMode=0, isprivacy=0,
isHeadless=0, cookieTypeSave=0, jsInfo=""):
@@ -188,11 +189,11 @@ class ZiniaoDriver:
if str(r.get("statusCode")) == "0":
return r
elif str(r.get("statusCode")) == "-10003":
print(f"login Err {json.dumps(r, ensure_ascii=False)}")
exit()
raise RuntimeError(f"【open_store】登录失败 {json.dumps(r, ensure_ascii=False)}")
# exit()
else:
print(f"Fail {json.dumps(r, ensure_ascii=False)} ")
exit()
raise RuntimeError(f"【open_store】失败 {json.dumps(r, ensure_ascii=False)} ")
# exit()
def get_browser(self, port) -> Chromium:
"""
@@ -209,6 +210,17 @@ class ZiniaoDriver:
def start_client(self):
"""启动紫鸟客户端"""
# 检查端口是否已经启动
try:
url = f'http://127.0.0.1:{self.socket_port}'
response = requests.get(url, timeout=2)
print(f"端口 {self.socket_port} 已经启动,跳过启动客户端操作")
return
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
# 端口未启动,继续执行启动操作
print(f"端口 {self.socket_port} 未启动,开始启动客户端")
self.client_path = self.get_zinaio_exe("superbrowserv6").strip('"')
print(self.client_path)
cmd = [self.client_path, '--run_type=web_driver', '--ipc_type=http',
@@ -261,7 +273,8 @@ class ZiniaoDriver:
print("ip检测页地址为空请升级紫鸟浏览器到最新版")
print(f"=====关闭店铺:{shop_name}=====")
self.close_store(self.store_id)
exit()
# exit()
raise RuntimeError("没有IP检测地址为了店铺安全不打开店铺")
ip_usable = self.open_ip_check(self.browser, ip_check_url)
if ip_usable:
print("ip检测通过打开店铺平台主页")
@@ -298,11 +311,11 @@ class ZiniaoDriver:
if str(r.get("statusCode")) == "0":
return r
elif str(r.get("statusCode")) == "-10003":
print(f"login Err {json.dumps(r, ensure_ascii=False)}")
exit()
raise RuntimeError(f"【close_store】登录失败 {json.dumps(r, ensure_ascii=False)}")
# exit()
else:
print(f"Fail {json.dumps(r, ensure_ascii=False)} ")
exit()
raise RuntimeError(f"【close_store】失败 {json.dumps(r, ensure_ascii=False)} ")
# exit()
def open_launcher_page(self, launcher_page: str, browser: Chromium = None):
"""
@@ -436,8 +449,38 @@ class AmamzonBase(ZiniaoDriver):
print(f"切换国家时发生异常:{traceback.format_exc()}")
return False
def need_login(self):
"""
判断是否需要登录,部分国家可能需要登录后才能切换国家
处理流程:
"""
need_login_ele = self.tab.eles('xpath://h1[@class="a-spacing-small"]',timeout=5)
if len(need_login_ele) >0:
print("检测到需要登录元素")
return True
return False
def login(self,password,username=""):
try:
pwd_input = self.tab.ele('xpath://input[@type="password"]',timeout=10)
pwd_input.input(password)
submit_btn = self.tab.ele('xpath://input[@id="signInSubmit"]',timeout=10)
submit_btn.click()
self.tab.wait.doc_loaded()
opt_code_input = self.tab.ele('xpath://input[@name="otpCode"]',timeout=10)
opt_code_input.wait.displayed(timeout=10,raise_err=False)
for _ in range(30):
if opt_code_input.value is not None and opt_code_input.value.strip() != "":
print("检测到验证码输入完成")
submit_btn = self.tab.ele('xpath://input[@id="auth-signin-button"]',timeout=10)
submit_btn.click()
self.tab.wait.doc_loaded()
return True
except Exception as e:
print("登录过程中发生异常:" + traceback.format_exc())
return False
class AmazoneDriver(AmamzonBase):
"""亚马逊专用驱动类继承自ZiniaoDriver"""

View File

@@ -78,7 +78,7 @@ class TaskMonitor:
except Exception as e:
# 静默处理队列为空的超时,只记录真正的异常
if "Empty" not in str(e):
if str(e) and "Empty" not in str(e):
self.log(f"任务监控异常: {str(e)}", "ERROR")
# 队列为空时不需要额外等待,直接继续循环
@@ -210,11 +210,11 @@ class TaskMonitor:
self.log(f"尝试打开店铺 {shop_name} (第 {retry + 1}/{max_retries} 次)")
# 如果不是第一次尝试,先杀进程
if retry > 0:
self.log("重试前先杀掉浏览器进程...")
kill_process("v6")
kill_process("v5")
time.sleep(2)
# if retry > 0:
# self.log("重试前先杀掉浏览器进程...")
# kill_process("v6")
# kill_process("v5")
# time.sleep(2)
# 创建驱动并打开店铺
user_info = {
@@ -230,6 +230,27 @@ class TaskMonitor:
else:
self.log(f"打开店铺失败: {browser}", "WARNING")
driver = None
# 判断是否需要登录
# need_login = driver.need_login()
# if need_login:
# self.log(f"店铺 {shop_name} 需要登录,正在登录...")
# password = ""
# login_success = driver.login(password)
# if login_success:
# self.log(f"店铺 {shop_name} 登录成功,正在重新打开店铺...")
# browser = driver.open_shop(shop_name)
# if browser and browser != "店铺不存在":
# self.log(f"成功打开店铺 {shop_name} 登录后")
# break
# else:
# self.log(f"登录后打开店铺失败: {browser}", "WARNING")
# driver = None
# else:
# self.log(f"店铺 {shop_name} 登录失败", "WARNING")
# driver = None
except Exception as e:
self.log(f"打开店铺异常: {str(e)}", "ERROR")

View File

@@ -110,7 +110,10 @@ def proxy(path):
if key.lower() in ['host', 'content-length', 'connection']:
continue
headers[key] = value
ignore_url = [f"{JAVA_API_BASE}/api/delete-brand/tasks/batch",f"{JAVA_API_BASE}/api/delete-brand/history"]
ignore_url = [f"{JAVA_API_BASE}/api/delete-brand/tasks/batch",
f"{JAVA_API_BASE}/api/delete-brand/history",
f"{JAVA_API_BASE}/api/product-risk-resolve/tasks/batch",
]
if target_url not in ignore_url:
try:
print("=============================")

View File

@@ -11,7 +11,7 @@ from amazon.main import TaskMonitor
os.makedirs(cache_path,exist_ok=True)
if not debug:
today = datetime.datetime.now().strftime("%Y_%m_%d")
sys.stdout = open(os.path.join(cache_path,f'{today}.log'), 'a',buffering=1)
sys.stdout = open(os.path.join(cache_path,f'{today}.log'), 'a',buffering=1,encoding='utf-8')
sys.stderr = sys.stdout
import webview
@@ -20,6 +20,9 @@ import time
import requests
import subprocess
from amazon.del_brand import kill_process
with open("version.txt","w",encoding="utf-8") as file:
file.write(json.dumps({"version":version}))
@@ -54,8 +57,15 @@ class WindowAPI:
window.events.restored += lambda _: setattr(self, '_is_maximized', False)
def close(self):
sys.exit(1)
self._window.destroy()
try:
kill_process('v6')
except Exception as e:
print(f"【退出前】关闭紫鸟浏览器进程异常: {str(e)}")
try:
self._window.destroy()
except:
pass
os._exit(0) # 强制退出整个进程,包括所有线程
def minimize(self):
self._window.minimize()
@@ -358,6 +368,23 @@ def start_task_monitor():
print(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 任务监控线程异常退出: {traceback.format_exc()}")
def on_window_closing():
"""窗口关闭事件处理器,执行与 close() 方法相同的清理逻辑"""
# 先关闭窗口,避免看起来卡着
try:
if webview.windows:
webview.windows[0].destroy()
except:
pass
# 然后执行清理逻辑
try:
kill_process('v6')
except Exception as e:
print(f"【退出前】关闭紫鸟浏览器进程异常: {str(e)}")
os._exit(0) # 强制退出整个进程,包括所有线程
def main():
if not os.path.exists(cache_path):
os.makedirs(cache_path,exist_ok=True)
@@ -384,11 +411,15 @@ def main():
frameless=False,
easy_drag=True
)
# 为窗口关闭事件添加处理器
window.events.closing += on_window_closing
api = WindowAPI(window)
window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder,
api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.open_external_url,api.enqueue_json,api.get_detail_del,api.save_file_from_url_new)
webview.start(
debug=True,
debug=False,
storage_path=cache_path,
private_mode=False
)