Refactor monolithic script into multi-site module architecture

- Introduce main_router.py as multi-site controller with interactive menu
- Extract 顺心 site logic into site_shunxin.py (expected & actual download)
- Add site_baishi.py stub for upcoming 百世 integration
- Add global 应到未到 data reconciliation via Left Anti-Join on 运单号

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-06-15 22:02:51 +08:00
parent b0c82bc23d
commit 411a2487fc
3 changed files with 563 additions and 0 deletions

173
main_router.py Normal file
View File

@@ -0,0 +1,173 @@
# main_router.py
import os
import pandas as pd
from playwright.sync_api import sync_playwright
# 导入抽离出去的各个网点模块
import site_shunxin
import site_baishi
# 定义网点及对应的初始登录 URL
SITES_CONFIG = {
"顺心": "https://sxne.sxjdfreight.com",
"百世": "https://v5.800best.com",
}
def task_process_undelivered_data():
"""全局模块:应到未到异常件比对引擎"""
print("\n▶ 开始执行【应到未到数据处理】任务...")
download_dir = os.path.join(os.getcwd(), "downloads")
expected_path = os.path.join(download_dir, "应到货物数据.xlsx")
actual_path = os.path.join(download_dir, "实到货物数据.xlsx")
output_path = os.path.join(download_dir, "应到未到货物数据.xlsx")
if not os.path.exists(expected_path):
print(f"❌ 错误:找不到【应到货物数据】主文档:{expected_path}")
return
if not os.path.exists(actual_path):
print(f"❌ 错误:找不到【实到货物数据】主文档:{actual_path}")
return
try:
print(">> 正在载入本地 Excel 文档...")
df_expected = pd.read_excel(expected_path)
df_actual = pd.read_excel(actual_path)
if "运单号" not in df_expected.columns or "运单号" not in df_actual.columns:
print("❌ 核心资产校验失败:数据源中缺失【运单号】字段,请检查导出配置。")
return
print(">> 正在启动多维数据集比对引擎...")
# Left Anti-Join在应到中找出不存在于实到里的运单号
df_undelivered = df_expected[~df_expected["运单号"].isin(df_actual["运单号"])]
target_columns = ["班次号", "交接单号", "运单号"]
available_columns = [
col for col in target_columns if col in df_undelivered.columns
]
df_output = df_undelivered[available_columns]
print(f">> 筛选完毕!共捕获到异常【应到未到】货物数据: {len(df_output)} 条。")
df_output.to_excel(output_path, index=False)
print(f"====================================================")
print(f" 🎉 异常比对流完成!独立数据已安全输出。")
print(f" 📁 成果归档路径: {output_path}")
print(f"====================================================")
except Exception as e:
print(f"❌ 数据处理引擎在执行连接和输出时发生致命异常: {e}")
def run_multi_site_daemon():
"""多网点自动化主控引擎"""
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
# 取消禁用视口大小限制,防止有些系统自适应出问题
context = browser.new_context(viewport={"width": 1920, "height": 1080})
# 用于存储网点名称与 Page 对象的映射字典
pages_map = {}
print("\n====================================================")
print("【初始化阶段】正在构建多网点运行环境...")
print("====================================================")
# 遍历配置,依次创建独立的标签页
for site_name, url in SITES_CONFIG.items():
print(f">> 正在打开【{site_name}】页面: {url}")
page = context.new_page()
page.goto(url)
pages_map[site_name] = page
page.wait_for_timeout(1000)
print("\n====================================================")
print("⚠️ 【等待人工介入】")
print("请在弹出的浏览器中,依次切换标签页,人工完成所有网点的登录!")
print("====================================================")
# 阻塞程序,等待用户登录完毕
input(">> 登录全部完成后,请在此处按下【回车键】正式接管中控台...")
print("\n====================================================")
print("【系统接管】正在执行各网点就绪前初始化动作...")
print("====================================================")
# 处理【顺心】网点的登录后就绪判定与弹窗清理
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.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()
# ...
while True:
print("\n==============================")
print(" 物流数据多端提取总枢纽 ")
print("==============================")
print("1. [顺心] - 执行【应到货物数据下载】")
print("2. [顺心] - 执行【实到货物数据下载】")
print("3. [百世] - 执行【应到货物数据下载】")
print("4. [百世] - 执行【实到货物数据下载】")
print("5. [全局] - 执行【应到未到数据清洗比对】")
print("0. 退出系统")
print("==============================")
choice = input("请输入任务编号并回车: ")
try:
if choice == "1":
pages_map["顺心"].bring_to_front() # 切换到对应标签页
site_shunxin.shunxin_expected_download(pages_map["顺心"])
elif choice == "2":
pages_map["顺心"].bring_to_front()
site_shunxin.shunxin_actual_download(pages_map["顺心"])
elif choice == "3":
pages_map["百世"].bring_to_front()
site_baishi.baishi_expected_download(pages_map["百世"])
elif choice == "4":
pages_map["百世"].bring_to_front()
site_baishi.baishi_actual_download(pages_map["百世"])
elif choice == "5":
task_process_undelivered_data()
elif choice == "0":
print("\n准备退出程序,释放浏览器资源...")
break
else:
print("\n⚠️ 无效输入,请重新选一下。")
except Exception as e:
print(f"❌ 调度执行异常: {e}")
# 退出循环后关闭浏览器
browser.close()
print("系统已安全关闭。")
if __name__ == "__main__":
run_multi_site_daemon()