Commit f94fcf53 authored by 邱阿朋's avatar 邱阿朋

feat(gui): 添加暂停/继续功能并优化执行流程- 新增暂停/继续按钮,支持中断和恢复任务执行

- 实现处理器运行状态控制,确保线程安全
- 重构国家与功能选择逻辑,支持全选模式
-优化日志显示与资源清理机制
- 修复时间戳获取逻辑,确保数据准确性
- 统一异常处理,增强程序健壮性
parent f1dd204b
......@@ -3,7 +3,6 @@ import time
from lxml import etree
def switch_domain(country):
domain = "https://vendorcentral.amazon.com/"
......@@ -22,30 +21,47 @@ def switch_domain(country):
return domain
def domain_page(logger,page, country):
url = switch_domain(country)
page.get(url)
class LoginDomain:
def __init__(self, logger, page, country):
self.logger = logger
self.page = page
self.country = country
# 运行状态
self.running = False
def set_status(self,status):
self.running = status
def login_check(self):
url = switch_domain(self.country)
self.page.get(url)
while True:
while not self.running:
time.sleep(1)
time.sleep(3)
title = page.title
title = self.page.title
valid_titles = [
"Amazon Sign-In",
"Amazon Sign In",
"Connexion Amazon",
"Amazonサインイン",
"Amazon Iniciar sesión"
"Amazon Iniciar sesión",
"Connexion Amazon",
"Amazon Anmelden",
]
if title in valid_titles:
logger.info("请登录账号")
self.logger.info("请登录账号")
continue
if title in ["Authentication required"]:
logger.info("请输入图形验证码")
self.logger.info("请输入图形验证码")
continue
tree = etree.HTML(page.html)
tree = etree.HTML(self.page.html)
element = tree.xpath('//*[@id="cvf-submit-otp-button-announce"]')
if element:
logger.info('请输入验证码')
self.logger.info('请输入验证码')
else:
break
# coding: utf-8
import re
import time
import pandas as pd
......@@ -8,7 +10,7 @@ from app.helper import domain, file
from app.logger.logger import Logger
from app.vc.interface import AutoInterface
from DrissionPage import ChromiumPage as Page
from datetime import datetime
from datetime import datetime, timedelta
class AdvertCost(AutoInterface):
......@@ -16,6 +18,9 @@ class AdvertCost(AutoInterface):
self.logger = logger
self.page = page
self.country = country
# 运行状态
self.running = False
self.select_day = 0
# 获取当前日期和时间并格式化
current_datetime = datetime.now().strftime('%Y-%m-%d-%H-%M')
......@@ -24,6 +29,9 @@ class AdvertCost(AutoInterface):
# 拼接新的文件名
self.result_file_name = f"{current_datetime}_{file_name}"
def set_running(self, running: bool):
self.running = running
def __page_get(self, url):
host = domain.switch_domain(self.country)
full_url = host + url
......@@ -83,8 +91,10 @@ class AdvertCost(AutoInterface):
store_ids = country_store_ids[self.country]
# 打印结果
for store_id in store_ids:
while not self.running:
time.sleep(1)
name = store_id
self.logger.debug(f"开始处理店铺:{name}")
......@@ -110,6 +120,20 @@ class AdvertCost(AutoInterface):
if yesterday_but:
yesterday_but.click()
self.page.wait(3)
# 找到按钮元素
btn = self.page.ele('#products_view:products_view:dateRangeFilter:openContainer')
text = btn.text
# 提取日期字符串
match = re.search(r'Date range:\s*([A-Za-z]+)\s+(\d{1,2}),\s*(\d{4})', text)
if not match:
raise ValueError("无法解析日期")
month_str, day, year = match.groups()
date_str = f"{month_str} {day}, {year}"
# 解析成 datetime转换成时间戳
self.select_day = int(datetime.strptime(date_str, "%b %d, %Y").timestamp())
# 点击导出
date_format = datetime.now().strftime('%Y%m%d%H%M')
file_name = f"products\\{date_format}-{name}.csv"
......@@ -155,7 +179,7 @@ class AdvertCost(AutoInterface):
push_data = {
'asin': asin, # ASIN
'spend': spend, # 金额
'timestamp': int(datetime.now().timestamp()),
'timestamp': self.select_day,
'country': self.country,
}
# 推送数据
......
# coding: utf-8
import re
import time
import pandas as pd
from DrissionPage.errors import ElementNotFoundError
......@@ -17,12 +19,17 @@ class ProductSales(AutoInterface):
self.logger = logger
self.page = page
self.country = country
# 运行状态
self.running = False
# 获取当前日期和时间并格式化
current_datetime = datetime.now().strftime('%Y-%m-%d-%H-%M')
# 文件名
self.result_file_name = f"products\\{current_datetime}_ProductSales.xlsx"
def set_running(self, running: bool):
self.running = running
def __page_get(self, url):
host = domain.switch_domain(self.country)
full_url = host + url
......@@ -38,12 +45,13 @@ class ProductSales(AutoInterface):
url = "retail-analytics/dashboard/sales?submit=true&time-period=daily"
if self.country == "JP":
url = f"retail-analytics/dashboard/sales?daily-day={daily_day}&submit=true&time-period=daily"
self.__page_get(url)
self.page.ele("#raw_xlsx_btn").click()
self.logger.debug("点击导出等待下载任务提交...")
while True:
while not self.running:
time.sleep(1)
try:
self.page.wait(3)
......@@ -83,12 +91,17 @@ class ProductSales(AutoInterface):
# 过滤掉 Ordered Revenue <= 0 的数据
data = df[df['Ordered Revenue'] > 0]
dates = re.findall(r'\d{4}-\d{2}-\d{2}', self.result_file_name)
select_day = dates[-1] if dates else None
select_day = int(datetime.strptime(select_day, "%Y-%m-%d").timestamp())
for _, item_row in data.iterrows():
push_data = {
'asin': item_row.get('ASIN', ''),
'ordered_revenue': item_row.get('Ordered Revenue', ''), # 订单金额
'ordered_units': item_row.get('Ordered Units', ''), # 订单数量
'country': self.country,
'timestamp': select_day,
}
# 推送数据
rabbit.send_message(push_data)
......
......@@ -8,9 +8,10 @@ import traceback
from tkinter import messagebox
from DrissionPage import ChromiumPage
import ttkbootstrap as ttk
from dotenv import load_dotenv
from ttkbootstrap.constants import *
from app.helper import domain,file
from app.helper import domain, file
from app.logger.gui import GuiLog
from app.vc.advert_cost import AdvertCost
from app.vc.product_sales import ProductSales
......@@ -20,7 +21,12 @@ class VCManagerGUI(ttk.Window):
def __init__(self, logger):
super().__init__(themename="cosmo")
self.domain_login = None
self.processor = None
self.actions = None
self.countries = None
self.run_btn = None
self.pause_btn = None
self.shop_entry = None
self.country_var = None
self.action_var = None
......@@ -34,6 +40,7 @@ class VCManagerGUI(ttk.Window):
# 初始化状态变量
self.page = None
self.running = False
self.paused = False
self.log_queue = queue.Queue()
self.payee_code = None
......@@ -52,7 +59,7 @@ class VCManagerGUI(ttk.Window):
def _center_window(self):
"""设置窗口居中"""
window_width = 520
window_width = 540
window_height = 600
# 获取屏幕尺寸
......@@ -79,8 +86,9 @@ class VCManagerGUI(ttk.Window):
country_frame = ttk.Frame(config_frame)
country_frame.grid(row=0, column=0, sticky=W, pady=5)
ttk.Label(country_frame, text="国家:", width=6).pack(side='left')
self.country_var = ttk.StringVar(value="US")
countries = [
self.country_var = ttk.StringVar(value="all")
self.countries = [
("全部", "all"),
("美国", "US"),
("日本", "JP"),
("英国", "UK"),
......@@ -88,28 +96,32 @@ class VCManagerGUI(ttk.Window):
("德国", "DE"),
("加拿大", "CA"),
]
for i, (text, value) in enumerate(countries):
rb = ttk.Radiobutton(country_frame,text=text,variable=self.country_var,value=value)
for i, (text, value) in enumerate(self.countries):
rb = ttk.Radiobutton(country_frame, text=text, variable=self.country_var, value=value)
rb.pack(side='left', padx=(10 if i != 0 else 0))
# 功能选择
action_frame = ttk.Frame(config_frame)
action_frame.grid(row=1, column=0, sticky=W, pady=5)
ttk.Label(action_frame, text="功能:", width=6).pack(side='left')
self.action_var = ttk.StringVar()
actions = [
self.action_var = ttk.StringVar(value="all")
self.actions = [
("全部", "all"),
("广告费", "advert_cost"),
("商品销量", "product_sales")
]
for i, (text, value) in enumerate(actions):
for i, (text, value) in enumerate(self.actions):
rb = ttk.Radiobutton(action_frame, text=text, variable=self.action_var, value=value)
rb.pack(side='left', padx=(10 if i != 0 else 0))
# 控制按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill='x', pady=15)
self.pause_btn = ttk.Button(btn_frame, text="暂停", command=self.toggle_pause,
style='warning.TButton', width=12, state=ttk.DISABLED)
self.pause_btn.pack(side='right', padx=5)
self.run_btn = ttk.Button(btn_frame, text="开始执行", command=self.start_process,
style=PRIMARY, width=12)
style='success.TButton', width=12)
self.run_btn.pack(side='right', padx=5)
# 日志显示
......@@ -130,42 +142,77 @@ class VCManagerGUI(ttk.Window):
return
self.running = True
self.run_btn.config(state=ttk.DISABLED)
self.paused = False
self.run_btn.config(text="执行中...", style='info.TButton', state=ttk.DISABLED)
self.pause_btn.config(state=ttk.NORMAL)
threading.Thread(target=self.run_process, daemon=True).start()
def toggle_pause(self):
"""切换暂停/继续状态"""
self.paused = not self.paused
if self.paused:
self.pause_btn.config(text="继续", style='success.TButton')
self.logger.info("已暂停")
if self.processor:
self.processor.set_running(False)
if self.domain_login:
self.domain_login.set_status(False)
else:
self.pause_btn.config(text="暂停", style='warning.TButton')
self.logger.info("已继续")
if self.processor:
self.processor.set_running(True)
if self.domain_login:
self.domain_login.set_status(True)
def run_process(self):
"""后台处理流程"""
try:
# 初始化浏览器
self.logger.info("初始化浏览器引擎...")
self.init_browser()
# 获取参数
country = self.country_var.get()
action = self.action_var.get()
# 切换域名
domain.domain_page(self.logger, self.page, country)
# 执行核心操作
self.logger.info(f"开始执行 {action} 操作...")
processor = None
if action == "advert_cost":
processor = AdvertCost(self.logger, self.page, str(country))
elif action == "product_sales":
processor = ProductSales(self.logger, self.page, str(country))
processor.run("")
processor.push_data_queue()
# 处理国家选择
countries = self.countries if country == "all" else [(None, country)]
countries = [(text, value) for text, value in countries if value != "all"]
# 先登录选择的国家
for _, country_value in countries:
self.domain_login = domain.LoginDomain(self.logger, self.page, country_value)
self.domain_login.set_status(True)
self.domain_login.login_check()
for _, country_value in countries:
# 处理动作选择
actions = self.actions if action == "all" else [(None, action)]
actions = [(text, value) for text, value in actions if value != "all"]
for _, action_value in actions:
self.logger.info(f"开始执行 {action_value} 操作于国家 {country_value}...")
self.processor = self._get_processor(action_value, country_value)
if self.processor:
self.processor.set_running(True)
self.processor.run("")
self.processor.push_data_queue()
self.logger.info("操作成功完成!")
except Exception as e:
self.logger.info(f"发生错误:{str(e)}")
self.logger.info(traceback.format_exc())
self.logger.error(f"发生错误:{str(e)}")
self.logger.error(traceback.format_exc())
finally:
pass
self.cleanup_resources()
def _get_processor(self, action, country):
"""根据动作类型返回处理器实例"""
processors = {
"advert_cost": AdvertCost,
"product_sales": ProductSales
}
processor_class = processors.get(action)
if processor_class:
return processor_class(self.logger, self.page, str(country))
return None
def init_browser(self):
"""初始化浏览器配置"""
self.page = ChromiumPage()
......@@ -189,7 +236,7 @@ class VCManagerGUI(ttk.Window):
self.log_text.insert(ttk.END, msg + "\n")
self.log_text.configure(state=ttk.DISABLED)
self.log_text.see(ttk.END)
self.after(100, self.process_log_queue) # type: ignore
self.after(200, self.process_log_queue) # type: ignore
def clear_log(self):
"""清除日志"""
......@@ -207,12 +254,17 @@ class VCManagerGUI(ttk.Window):
self.logger.info(f"释放浏览器资源时出错:{str(e)}")
finally:
self.page = None
self.running = False
self.after(0, lambda: self.run_btn.config(state=ttk.NORMAL)) # type: ignore
self.running = False
self.paused = False
self.run_btn.configure(text="开始执行", style='success.TButton')
self.run_btn['state'] = ttk.NORMAL
self.pause_btn.configure(text="暂停", style='warning.TButton')
self.pause_btn['state'] = ttk.DISABLED
if __name__ == "__main__":
try:
load_dotenv()
log = GuiLog()
app = VCManagerGUI(log)
app.mainloop()
......
......@@ -47,6 +47,7 @@ if __name__ == '__main__':
if file_name == "":
raise Exception("请输入文件名")
domain.set_switch_status(True)
# 切换域名
domain.domain_page(logger, page, country)
# 执行功能
......
......@@ -216,6 +216,7 @@ class VCManagerGUI(ttk.Window):
# 创建处理器实例
processor = self.create_processor(params)
domain.set_switch_status(True)
# 切换域名
domain.domain_page(self.logger, self.page, self.country_var.get())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment