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

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

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