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

feat(vendor central): 添加广告费和商品销量功能

- 新增 AdvertCost 和 ProductSales 类实现广告费和商品销量数据获取- 添加 super_gui.py 实现 Vendor Central 管理工具界面- 更新 build.bat 以生成新的可执行文件
- 重构 easy.py 和 gui.py,改为 easy_gui.py 和 tool_gui.py
-移除 payment.py 中的冗余导入
parent 06b983ba
# coding: utf-8
import pandas as pd
from lxml import etree
from app.vc import rabbit
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
class AdvertCost(AutoInterface):
def __init__(self, logger: Logger, page: Page, country: str):
self.logger = logger
self.page = page
self.country = country
# 获取当前日期和时间并格式化
current_datetime = datetime.now().strftime('%Y-%m-%d-%H-%M')
# 原文件名
file_name = "广告数据.xlsx"
# 拼接新的文件名
self.result_file_name = f"{current_datetime}_{file_name}"
def __page_get(self, url):
host = domain.switch_domain(self.country)
full_url = host + url
self.page.get(full_url, timeout=5)
@staticmethod
def __parse_vendor_info(xml_content):
# 解析 XML 内容
parser = etree.XMLParser(recover=True)
tree = etree.fromstring(xml_content, parser=parser)
# 存储结果
results = []
# 查找所有 h5 元素
h5_elements = tree.xpath('//h5[@class="heading align-start color-normal heading-size--normal"]')
# 查找所有包含 Account ID 的 p 元素
p_elements = tree.xpath(
'//div[@class="container vendor-account-id a20m border-type-none background-color-transparent desktop"]/p[@class="text align-start color-normal text--word-break--normal text-size--normal"]')
# 查找所有 button 元素的 onclick URL
button_elements = tree.xpath('//button[@class="form-button a20m-form-button-type-submit"]')
# 确保 h5、p 和 button 元素数量匹配
for h5, p, button in zip(h5_elements, p_elements, button_elements):
# 提取 onclick 中的 URL
onclick = button.get('onclick', '')
url = onclick.replace("window.location.href='", "").rstrip("'") if onclick else ""
results.append({
'name': h5.text.strip(),
'id': p.text.strip().replace('Account ID: ', ''),
'url': url
})
return results
def run(self,file_name: str):
self.__page_get("hz/vendor/members/advertising/home?ref_=vc_xx_subNav")
wrapper_html = self.page.ele(". columns-wrapper a20m-columns-wrapper", timeout=5).html
# 解析并提取信息
vendor_info = self.__parse_vendor_info(wrapper_html)
all_data = []
# 打印结果
for info in vendor_info:
name = info['name']
self.logger.debug(f"开始处理小组:{name}")
entity_id = info['id'][1:]
self.page.get(f"https://advertising.amazon.com/cm/products?entityId=ENTITY{entity_id}")
# 判断是否正常
target_text = "There’s an issue with your payment method and your campaigns have stopped delivering."
text_found = self.page.ele(f'text:{target_text}')
if text_found:
self.logger.warning(f"{name} 异常")
continue
# 点击日历
self.page.ele("#products_view:products_view:dateRangeFilter:openContainer").click()
self.page.wait(1)
# 点击昨天选项
yesterday_but = self.page.ele('xpath://button[@value="YESTERDAY"]')
if yesterday_but:
yesterday_but.click()
self.page.wait(3)
# 点击导出
date_format = datetime.now().strftime('%Y%m%d%H%M')
file_name = f"products\\{date_format}-{info['id']}.csv"
self.page.ele("#products_view:products_view:export").click.to_download(rename=file_name)
file.wait_for_downloads(file_name)
df = pd.read_csv(file_name)
if len(df) == 0:
continue
all_data.append(df)
# 合并所有 DataFrame
combined_df = pd.concat(all_data, ignore_index=True)
# 写入最终文件
combined_df.to_excel(self.result_file_name, index=False)
def push_data_queue(self):
rabbit.connection()
rabbit.connect(queue='advertising', routing_key='advertising', exchange='reports')
data = pd.read_excel(self.result_file_name, keep_default_na=False, na_values=[])
for _, item_row in data.iterrows():
asin = item_row.get('Products', '').split("-")[0]
push_data = {
'asin': asin, # ASIN
'spend': item_row.get('Spend(USD)', ''), # 金额
}
# 推送数据
rabbit.send_message(push_data)
rabbit.close()
\ No newline at end of file
...@@ -14,7 +14,7 @@ from lxml import etree ...@@ -14,7 +14,7 @@ from lxml import etree
from app.helper import domain, excel, file from app.helper import domain, excel, file
from app.logger.logger import Logger from app.logger.logger import Logger
from app.vc import rdb, rabbit from app.vc import rdb
from app.vc.interface import AutoInterface from app.vc.interface import AutoInterface
......
# coding: utf-8
import pandas as pd
from app.vc import rabbit
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
class ProductSales(AutoInterface):
def __init__(self, logger: Logger, page: Page, country: str):
self.logger = logger
self.page = page
self.country = country
# 获取当前日期和时间并格式化
current_datetime = datetime.now().strftime('%Y-%m-%d-%H-%M')
# 文件名
self.result_file_name = f"products\\{current_datetime}_ProductSales.xlsx"
def __page_get(self, url):
host = domain.switch_domain(self.country)
full_url = host + url
self.page.get(full_url, timeout=5)
def run(self, file_name: str):
self.__page_get("retail-analytics/dashboard/sales?submit=true&time-period=daily")
self.page.ele("#raw_xlsx_btn").click()
self.logger.debug("点击导出等待下载任务提交...")
self.page.wait(3)
while True:
self.page.ele("#downloadManager").click()
self.logger.debug("等待数据加载...")
self.page.wait(5)
# 使用 XPath 定位元素
xpath = '//kat-table-body/kat-table-row[1]/kat-table-cell[@role="cell"][1]/a'
link = self.page.ele(f'xpath:{xpath}')
if link is None:
self.page.refresh()
continue
self.logger.debug("开始下载文件...")
# 点击下载链接
link.click.to_download(rename=self.result_file_name)
self.logger.debug("等待下载...")
file.wait_for_downloads(self.result_file_name,3600)
break
def push_data_queue(self):
rabbit.connection()
rabbit.connect(queue='product_sales', routing_key='product_sales', exchange='reports')
df = pd.read_excel(self.result_file_name, header=1)
# 过滤掉 Ordered Revenue <= 0 的数据
data = df[df['Ordered Revenue'] > 0]
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', ''), # 订单数量
}
# 推送数据
rabbit.send_message(push_data)
rabbit.close()
...@@ -15,9 +15,10 @@ REM 安装依赖 ...@@ -15,9 +15,10 @@ REM 安装依赖
pip.exe install -i https://mirrors.cloud.tencent.com/pypi/simple -r requirements.txt pip.exe install -i https://mirrors.cloud.tencent.com/pypi/simple -r requirements.txt
REM 使用版本号生成 exe 文件 REM 使用版本号生成 exe 文件
pyinstaller -F -n amazon_cmd_%VERSION%.exe main.py pyinstaller -F -n tool_cmd_%VERSION%.exe tool_cmd.py
pyinstaller -F -n amazon_gui_%VERSION%.exe --noconsole gui.py pyinstaller -F -n tool_gui_%VERSION%.exe --noconsole tool_gui.py
pyinstaller -F -n easy_gui.exe --noconsole easy.py pyinstaller -F -n easy_gui.exe --noconsole easy_gui.py
pyinstaller -F -n super_gui_%VERSION%.exe --noconsole super_gui.py
REM 删除生成的 .spec 文件 REM 删除生成的 .spec 文件
del *.spec del *.spec
......
...@@ -128,6 +128,8 @@ class Application(ttk.Window): ...@@ -128,6 +128,8 @@ class Application(ttk.Window):
self.warehouse_combo = None self.warehouse_combo = None
self.title("入库单创建工具") self.title("入库单创建工具")
self.geometry("600x500") self.geometry("600x500")
# 设置窗口属性禁止缩放
self.resizable(False, False) self.resizable(False, False)
# 仓库编码映射 # 仓库编码映射
......
# coding: utf-8
import os
import threading
import queue
import time
import traceback
from tkinter import messagebox
from DrissionPage import ChromiumPage
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
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
class VCManagerGUI(ttk.Window):
def __init__(self, logger):
super().__init__(themename="cosmo")
self.run_btn = None
self.shop_entry = None
self.country_var = None
self.action_var = None
self.log_text = None
self.title("广告费工具")
self.geometry("520x600")
# 设置窗口属性禁止缩放
self.resizable(False, False)
# 初始化状态变量
self.page = None
self.running = False
self.log_queue = queue.Queue()
self.payee_code = None
# 设置日志处理
logger.set_console(self.log_queue)
self.logger = logger
# 设置窗口尺寸并居中
self._center_window()
# 创建界面组件
self.create_widgets()
# 启动日志处理循环
self.after(100, self.process_log_queue)
def _center_window(self):
"""设置窗口居中"""
window_width = 520
window_height = 600
# 获取屏幕尺寸
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
# 计算居中坐标
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
# 设置窗口位置
self.geometry(f"{window_width}x{window_height}+{x}+{y}")
def create_widgets(self):
"""创建主界面布局"""
main_frame = ttk.Frame(self)
main_frame.pack(fill=BOTH, expand=True, padx=15, pady=15)
# 配置区域
config_frame = ttk.Labelframe(main_frame, text="配置参数", padding=10)
config_frame.pack(fill=X, pady=10)
# 国家选择
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 = [
("美国", "US"),
# ("英国", "UK"),
# ("日本", "JP"),
# ("法国", "FR"),
# ("德国", "DE"),
# ("加拿大", "CA"),
]
for i, (text, value) in enumerate(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 = [
("广告费", "advert_cost"),
("商品销量", "product_sales")
]
for i, (text, value) in enumerate(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.run_btn = ttk.Button(btn_frame, text="开始执行", command=self.start_process,
style=PRIMARY, width=12)
self.run_btn.pack(side=RIGHT, padx=5)
# 日志显示
log_frame = ttk.Labelframe(main_frame, text="操作日志", padding=10)
log_frame.pack(fill=BOTH, expand=True)
self.log_text = ttk.ScrolledText(log_frame, state=DISABLED)
self.log_text.pack(fill=BOTH, expand=True)
def start_process(self):
"""启动处理线程"""
if self.running:
messagebox.showwarning("警告", "已有任务正在运行")
return
if not self.action_var.get():
messagebox.showerror("输入错误", "请选择要执行的功能")
return
self.running = True
self.run_btn.config(state=ttk.DISABLED)
threading.Thread(target=self.run_process, daemon=True).start()
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()
self.logger.info("操作成功完成!")
except Exception as e:
self.logger.info(f"发生错误:{str(e)}")
self.logger.info(traceback.format_exc())
finally:
pass
self.cleanup_resources()
def init_browser(self):
"""初始化浏览器配置"""
self.page = ChromiumPage()
self.page.set.load_mode.normal()
self.page.set.when_download_file_exists('overwrite')
download_path = os.path.join(os.getcwd())
self.page.set.download_path(download_path)
file.make_dir('products')
def log(self, message):
"""记录日志到队列"""
self.logger.info(message)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.log_queue.put(f"[{timestamp}] {message}")
def process_log_queue(self):
"""处理日志队列"""
while not self.log_queue.empty():
msg = self.log_queue.get()
self.log_text.configure(state=ttk.NORMAL)
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)
def clear_log(self):
"""清除日志"""
self.log_text.configure(state=ttk.NORMAL)
self.log_text.delete(1.0, ttk.END)
self.log_text.configure(state=ttk.DISABLED)
def cleanup_resources(self):
"""清理资源"""
if self.page:
try:
self.page.close()
self.logger.info("浏览器资源已释放")
except Exception as e:
self.logger.info(f"释放浏览器资源时出错:{str(e)}")
finally:
self.page = None
self.running = False
self.after(0, lambda: self.run_btn.config(state=ttk.NORMAL))
if __name__ == "__main__":
try:
log = GuiLog()
app = VCManagerGUI(log)
app.mainloop()
except KeyboardInterrupt:
exit(1)
...@@ -30,6 +30,9 @@ class VCManagerGUI(ttk.Window): ...@@ -30,6 +30,9 @@ class VCManagerGUI(ttk.Window):
self.title("店铺管理工具") self.title("店铺管理工具")
self.geometry("700x800") self.geometry("700x800")
# 设置窗口属性禁止缩放
self.resizable(False, False)
# 初始化状态变量 # 初始化状态变量
self.page = None self.page = None
self.running = False self.running = False
......
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