Integrate ZTO site module and add debug/single-site mode

- Add site_zto.py: ZTO expected-arrival data download with menu
  navigation, export polling, and Excel merge
- Wire ZTO into main_router: site URL, login detection, menu items
  [5]/[6], and a global offline Left Anti-Join compare entry [9]
- Read config.yaml debug flags to launch only the target site for
  focused debugging; guard each site init and route handler behind
  site-loaded checks via is_site_ready()
- Expand config.example.yaml with documented debug, baishi, and zto
  config keys

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-06-20 12:46:46 +08:00
parent 7f8d81af21
commit 9375b6efd6
3 changed files with 535 additions and 88 deletions

View File

@@ -1,17 +1,20 @@
# main_router.py
import os
import yaml
import pandas as pd
from playwright.sync_api import sync_playwright
# 导入抽离出去的各个网点模块
import site_shunxin
import site_baishi
import site_zto
# 定义网点及对应的初始登录 URL
SITES_CONFIG = {
"顺心": "https://sxne.sxjdfreight.com",
"百世": "https://v5.800best.com",
"中通": "https://ws.zto56.com/",
}
@@ -20,7 +23,6 @@ def task_process_undelivered_data(site_name="顺心"):
print(f"\n▶ 开始执行【{site_name} - 应到未到数据处理】任务...")
download_dir = os.path.join(os.getcwd(), "downloads")
# 动态拼接带网点前缀的文件路径
expected_path = os.path.join(download_dir, f"{site_name}-应到货物数据.xlsx")
actual_path = os.path.join(download_dir, f"{site_name}-实到货物数据.xlsx")
output_path = os.path.join(download_dir, f"{site_name}-应到未到货物数据.xlsx")
@@ -57,7 +59,7 @@ def task_process_undelivered_data(site_name="顺心"):
df_output.to_excel(output_path, index=False)
print(f"====================================================")
print(f" 🎉 异常比采取流完成!独立数据已安全输出。")
print(f" 🎉 异常比流完成!独立数据已安全输出。")
print(f" 📁 成果归档路径: {output_path}")
print(f"====================================================")
@@ -67,6 +69,27 @@ def task_process_undelivered_data(site_name="顺心"):
def run_multi_site_daemon():
"""多网点自动化主控引擎"""
# 1. 读取配置文件
debug_mode = False
debug_target = ""
try:
if os.path.exists("config.yaml"):
with open("config.yaml", "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
debug_mode = config.get("debug", {}).get("enabled", False)
debug_target = config.get("debug", {}).get("target_site", "")
except Exception as e:
print(f"⚠️ 读取 config.yaml 异常,将使用全量模式启动: {e}")
# 动态确定需要启动的站点
active_sites = {}
if debug_mode and debug_target in SITES_CONFIG:
print(f"\n🛠️ 【调试模式激活】当前仅挂载并启动目标站点: [{debug_target}]")
active_sites = {debug_target: SITES_CONFIG[debug_target]}
else:
active_sites = SITES_CONFIG
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": 1920, "height": 1080})
@@ -77,7 +100,7 @@ def run_multi_site_daemon():
print("【初始化阶段】正在构建多网点运行环境...")
print("====================================================")
for site_name, url in SITES_CONFIG.items():
for site_name, url in active_sites.items():
print(f">> 正在打开【{site_name}】页面: {url}")
page = context.new_page()
page.goto(url)
@@ -86,7 +109,7 @@ def run_multi_site_daemon():
print("\n====================================================")
print("⚠️ 【等待人工介入】")
print("请在弹出的浏览器中,依次切换标签页,人工完成所有网点的登录!")
print("请在弹出的浏览器中,人工完成已加载网点的登录!")
print("====================================================")
input(">> 登录全部完成后,请在此处按下【回车键】正式接管中控台...")
@@ -96,103 +119,116 @@ def run_multi_site_daemon():
print("====================================================")
# 顺心环境净化
try:
sx_page = pages_map["顺心"]
sx_page.bring_to_front()
print(">> 正在处理【顺心】网点初始状态...")
if "顺心" in pages_map:
try:
sx_page = pages_map["顺心"]
sx_page.bring_to_front()
print(">> 正在处理【顺心】网点初始状态...")
sx_page.wait_for_selector('h1:has-text("盟商门户网")', timeout=30000)
print(" 🎉 登录成功!系统已接管顺心浏览器。")
sx_page.wait_for_timeout(1000)
sx_page.wait_for_selector('h1:has-text("盟商门户网")', timeout=30000)
print(" 🎉 登录成功!系统已接管顺心浏览器。")
sx_page.wait_for_timeout(1000)
sx_page.locator("a").nth(4).click()
sx_page.wait_for_timeout(1000)
sx_page.get_by_role("button", name="Close").click()
sx_page.wait_for_timeout(1000)
sx_page.get_by_role("button", name="不再询问").click()
sx_page.wait_for_timeout(1000)
print(" ✅ 【顺心】弹窗清理完毕,状态就绪!")
except Exception as e:
print(f" ⚠️ 【顺心】初始化或弹窗清理异常 (如无弹窗可忽略): {e}")
sx_page.locator("a").nth(4).click()
sx_page.wait_for_timeout(1000)
sx_page.get_by_role("button", name="Close").click()
sx_page.wait_for_timeout(1000)
sx_page.get_by_role("button", name="不再询问").click()
sx_page.wait_for_timeout(1000)
print(" ✅ 【顺心】弹窗清理完毕,状态就绪!")
except Exception as e:
print(f" ⚠️ 【顺心】初始化或弹窗清理异常 (如无弹窗可忽略): {e}")
# 百世环境检测与“打地鼠”清理引擎
try:
bs_page = pages_map["百世"]
bs_page.bring_to_front()
print("\n>> 正在处理【百世】网点初始状态...")
if "百世" in pages_map:
try:
bs_page = pages_map["百世"]
bs_page.bring_to_front()
print("\n>> 正在处理【百世】网点初始状态...")
bs_page.wait_for_selector('h1[title="百世快运"]', timeout=30000)
print(" 🎉 登录成功!系统已接管百世浏览器。")
bs_page.wait_for_selector('h1[title="百世快运"]', timeout=30000)
print(" 🎉 登录成功!系统已接管百世浏览器。")
print(" >> 启动无序弹窗清理机制 (打地鼠模式)...")
# 留出一点时间让所有前端请求返回并渲染弹窗
bs_page.wait_for_timeout(2000)
print(" >> 启动无序弹窗清理机制 (打地鼠模式)...")
bs_page.wait_for_timeout(2000)
# 最多循环 5 轮,每次尝试点掉视野内的所有垃圾弹窗
for round_idx in range(5):
handled_any = False
for round_idx in range(5):
handled_any = False
try:
read_btns = bs_page.locator("button:has-text('阅读完毕')")
if read_btns.count() > 0:
for i in range(read_btns.count()):
if read_btns.nth(i).is_visible():
read_btns.nth(i).click()
bs_page.wait_for_timeout(500)
handled_any = True
except Exception:
pass
# 1. 清理【阅读完毕】(可能叠加出现多个)
try:
read_btns = bs_page.locator("button:has-text('阅读完毕')")
if read_btns.count() > 0:
for i in range(read_btns.count()):
if read_btns.nth(i).is_visible():
read_btns.nth(i).click()
bs_page.wait_for_timeout(500)
handled_any = True
print(" -> 已点击【阅读完毕】")
except Exception:
pass
try:
close_config = bs_page.locator("button:has-text('关 闭')")
if close_config.is_visible():
close_config.click()
bs_page.wait_for_timeout(500)
handled_any = True
except Exception:
pass
# 2. 清理【配置检查 -> 关 闭】
try:
close_config = bs_page.locator("button:has-text('关 闭')")
if close_config.is_visible():
close_config.click()
bs_page.wait_for_timeout(500)
handled_any = True
print(" -> 已关闭【配置检查】")
except Exception:
pass
try:
notice_close = bs_page.locator(
"a.ant-notification-notice-close"
)
if notice_close.is_visible():
notice_close.click()
bs_page.wait_for_timeout(500)
handled_any = True
except Exception:
pass
# 3. 清理【通知提示】
try:
notice_close = bs_page.locator("a.ant-notification-notice-close")
if notice_close.is_visible():
notice_close.click()
bs_page.wait_for_timeout(500)
handled_any = True
print(" -> 已关闭【通知提示】")
except Exception:
pass
try:
ad_close = bs_page.locator("svg[data-icon='close-circle']")
if ad_close.is_visible():
ad_close.click()
bs_page.wait_for_timeout(500)
handled_any = True
except Exception:
pass
# 4. 清理【广告】(捕获具有 close-circle 图标的 SVG)
try:
ad_close = bs_page.locator("svg[data-icon='close-circle']")
if ad_close.is_visible():
ad_close.click()
bs_page.wait_for_timeout(500)
handled_any = True
print(" -> 已关闭【广告弹窗】")
except Exception:
pass
if not handled_any:
break
bs_page.wait_for_timeout(1000)
# 如果这一轮没有任何可见的弹窗被处理,说明页面已经干净了,跳出循环
if not handled_any:
break
print(" ✅ 【百世】界面净化完毕,状态就绪!")
except Exception as e:
print(f" ⚠️ 【百世】初始化异常: {e}")
bs_page.wait_for_timeout(1000)
# 中通环境检测
if "中通" in pages_map:
try:
zto_page = pages_map["中通"]
zto_page.bring_to_front()
print("\n>> 正在处理【中通】网点初始状态...")
print(" ✅ 【百世】界面净化完毕,状态就绪!")
zto_page.wait_for_selector('.logo:has-text("网点版")', timeout=30000)
print(" 🎉 登录成功!系统已接管中通浏览器。")
print(" ✅ 【中通】状态就绪!")
except Exception as e:
print(f" ⚠️ 【中通】初始化异常: {e}")
except Exception as e:
print(f" ⚠️ 【百世】初始化异常: {e}")
# 路由拦截器
def is_site_ready(site_name):
if site_name not in pages_map:
print(
f"\n🚫 【拒绝执行】当前处于单站点调试模式,或【{site_name}】未加载,无法执行此操作!"
)
return False
return True
while True:
print("\n====================================================")
print(" 物流数据多端提取总枢纽 ")
if debug_mode:
print(f" [ 🛠️ 调试模式已激活 : {debug_target} ]")
print("====================================================")
print(" 模块一:【顺心】数据处理流")
print(" [1] 执行 - 应到货物数据下载")
@@ -202,28 +238,47 @@ def run_multi_site_daemon():
print(" 模块二:【百世】数据处理流")
print(" [4] 执行 - 一键提取应到未到异常数据")
print("-" * 52)
print(" 模块三:【中通】数据处理流")
print(" [5] 执行 - 应到货物数据下载")
print(" [6] 执行 - 实到货物数据下载 (待开发)")
print("-" * 52)
print(" 全局数据引擎")
print(" [9] 执行 - 离线异常数据清洗比对 (Left Anti-Join)")
print("-" * 52)
print(" [0] 退出系统")
print("====================================================")
choice = input("请输入任务编号并回车: ")
try:
if choice == "1":
if choice == "1" and is_site_ready("顺心"):
pages_map["顺心"].bring_to_front()
site_shunxin.shunxin_expected_download(pages_map["顺心"])
elif choice == "2":
elif choice == "2" and is_site_ready("顺心"):
pages_map["顺心"].bring_to_front()
site_shunxin.shunxin_actual_download(pages_map["顺心"])
elif choice == "3":
elif choice == "3" and is_site_ready("顺心"):
task_process_undelivered_data("顺心")
elif choice == "4":
elif choice == "4" and is_site_ready("百世"):
pages_map["百世"].bring_to_front()
site_baishi.baishi_download_undelivered_data(pages_map["百世"])
elif choice == "5" and is_site_ready("中通"):
pages_map["中通"].bring_to_front()
site_zto.zto_expected_download(pages_map["中通"])
elif choice == "6" and is_site_ready("中通"):
pages_map["中通"].bring_to_front()
site_zto.zto_actual_download(pages_map["中通"])
elif choice == "9":
site_name = input("请输入要比对的网点名称 (如 顺心/中通): ").strip()
if not site_name:
site_name = "顺心"
task_process_undelivered_data(site_name)
elif choice == "0":
print("\n准备退出程序,释放浏览器资源...")
break
else:
print("\n⚠️ 无效输入,请重新选一下。")
if choice not in ["1", "2", "3", "4", "5", "6", "9", "0"]:
print("\n⚠️ 无效输入,请重新选一下。")
except Exception as e:
print(f"❌ 调度执行异常: {e}")