偏移量功能(前端/API 可配,服务端持久化、多用户共享): - state_store:新增 site_config 表 + get_offset / set_offset(0..30 钳制) / get_all_offsets - server:新增 GET/PUT /config(百世锁定当天,不可配置) - 顺心/中通/韵达/安能(各 expected+actual):日期逻辑由 query_days 范围改为读 offset 算单日 target=今天-offset,起止同日;百世仍走当日 - config.example.yaml:query_days 标记为已废弃 修复启动时显示上一会话陈旧登录态: - state_store:新增 reset_login_states(登录态会话级,启动重置为 unknown;数据态会话无关、保留) - runtime.launch_and_prepare:启动时对各站重置登录态,心跳就绪后重新探测写真实值 Co-Authored-By: Claude <noreply@anthropic.com>
物流到货数据自动下载与应到未到核对工具
自动登录 5 家物流承运商的工作台,下载"应到 / 实到"货物数据,并离线比对出 应到未到(该到没到)的异常运单,最终汇总成一份 Excel 报表。
适用场景:网点每日核对进站货物是否到齐。
一、覆盖站点与流程
5 个站点中,4 个是网页、1 个是 Electron 桌面应用:
| 站点 | 形态 | 应到 | 实到 |
|---|---|---|---|
| 顺心捷达 | 网页 | ✅ 运单信息 | ✅ 卸车扫描记录 |
| 百世快运 | 网页 | — | —(直接提取"应到未到/当日未扫",单流程) |
| 中通快运 | 网页 | ✅ 运单信息 | ✅ 到件扫描 |
| 韵达快运 | 网页 | ✅ 进站主单 | ✅ 扫描记录 |
| 安能全网门户 | Electron 应用 | ✅ 运单信息 | ✅ 网点到件扫描 |
- 应到 = 进站交接单下的运单明细(这批货"应该"到);
- 实到 = 到件 / 卸车扫描记录(实际扫到了哪些);
- 共 9 个下载流程(顺心/中通/韵达/安能 各 2 个 + 百世 1 个)。
每个站点产出独立的 {站点}-应到货物数据.xlsx / {站点}-实到货物数据.xlsx
(百世为 百世-应到未到货物数据.xlsx),落在 downloads/。
二、目录结构
InboundVerify/
├── main_router.py # 主入口 / 调度层(菜单、启动浏览器与安能、就绪轮询、登录检测)
├── site_shunxin.py # 顺心站点模块(流程 + 重置 + 重试,自洽)
├── site_baishi.py # 百世站点模块
├── site_zto.py # 中通站点模块
├── site_yunda.py # 韵达站点模块(含自动登录)
├── site_anneng.py # 安能站点模块(Electron + CDP 驱动)
├── expected_undelivered.py # 全站点应到未到离线比对,输出 output/应到未到数据.xlsx
├── paths.py # 统一路径锚点(以本目录为基准,不依赖 cwd)
├── config.example.yaml # 配置模板
├── config.yaml # 真实配置(自行创建,已被 .gitignore 忽略)
├── requirements.txt
├── downloads/ # 各站点下载的原始数据
├── output/ # 比对报表输出
└── docs/ # 说明文档
三、环境准备
需 Python 3.10+。
# 1. 创建并激活虚拟环境
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 2. 安装依赖(含安能 CDP 驱动所需的 websocket-client)
pip install -r requirements.txt
# 3. 安装 Playwright 浏览器内核(网页站点用)
playwright install chromium
# 4. 由模板创建本地配置并填入真实凭据
cp config.example.yaml config.yaml
安能是 Electron 应用,还需在
config.yaml里填anneng.app_path指向本机的「安能全网门户.exe」路径。
四、配置说明(config.yaml)
config.yaml 存放凭据与各站参数,已被 .gitignore 忽略,不会提交。
所有项都有默认值,留空不会报错(但凭据留空会导致对应站点登录/导出失败)。
| 配置项 | 说明 |
|---|---|
debug.enabled / debug.target_site |
调试模式:仅挂载启动指定单个站点(顺心/百世/中通/韵达/安能) |
shunxin.query_days |
顺心查询时间范围(向前回溯 N 天至今天) |
baishi.password |
百世导出授权密码(必填,否则导出失败) |
zto.query_days |
中通查询时间范围 |
yunda.username / yunda.password |
韵达自动登录凭据(留空则需手动登录) |
yunda.query_days |
韵达查询时间范围 |
anneng.query_days |
安能查询时间范围 |
anneng.app_path |
安能 Electron 可执行文件路径 |
五、运行
python main_router.py
程序会:
- 用 Playwright 打开各网页站点(顺心为双账号:同一窗口开两个标签页,分别登录两个 不同归属地账号;韵达若配了凭据会尝试自动登录,其余手动登录);
- 以调试模式启动安能 Electron 应用(动态空闲端口),需在应用内手动登录;
- 轮询各站点登录就绪状态——全部登录完成后自动进入主菜单(顺心需两个标签页都进主页);
- 弹出主菜单,按编号选择任务。
主菜单:
[1][2] 顺心 应到 / 实到
[3] 百世 应到未到(当日未扫)
[4][5] 中通 应到 / 实到
[6][7] 韵达 应到 / 实到
[10][11] 安能 应到 / 实到
[8] 全站点自动化测试(交叉跑通校验)
[9] 应到未到比对(全站点汇总 → output/应到未到数据.xlsx)
[0] 退出
六、架构
分层原则:路由层只调度,站点模块自洽。
main_router.py(调度层):负责启动浏览器 / 安能、就绪轮询、登录检测、 菜单分发。不关心"任务能否完成、失败怎么办"——只调site_xxx.xxx_download(page)然后等结果。- 各
site_xxx.py(站点模块):每个模块对外只暴露一个"把任务做了"的入口xxx_download(...),内部自行处理一切:HOME_URL:站点首页 URL(也供路由层SITES_CONFIG引用,单一来源);xxx_reset(...):异常兜底的重置(网页 = 跳首页 URL;安能 = 关业务 tab + 收菜单);with_retry(...):内联在本模块的重试逻辑;xxx_download(...):公开入口 =with_retry(站点, 标签, xxx_download_impl, xxx_reset);xxx_download_impl(...):单次执行、无重试(供自动化测试探测原始失败)。
异常兜底(失败 → 重置 → 重试):任一流程失败(返回 False 或抛异常)→ 调对应
站点的 xxx_reset 回到初始态 → 重试,最多 3 次(含首次);每次失败都重置
(含最终放弃那次),确保环境不残留脏状态。
顺心是双账号特例:同一窗口开两个标签页(两个归属地账号),
shunxin_download接收 page 列表,内部读各账号归属地 → 去重校验 → 顺序下载 →pd.concat融合成 统一的顺心-{应到/实到}货物数据.xlsx,比对层无感(仍读同名文件)。
安能是 Electron,由 CDP(远程调试端口)驱动而非 Playwright page。其重置不用
Page.reload——reload 会让已开的业务 webContents 失去引用、变成无法清理的僵尸, 故改用"走 tab 条 X 关 tab + 收起菜单"。
七、输出
downloads/{站点}-应到货物数据.xlsx、{站点}-实到货物数据.xlsx:各站点原始数据;output/应到未到数据.xlsx:全站点比对报表(汇总 + 各站明细),由菜单 [9] 生成。
八、备注
- 首次运行需手动登录各站点(程序会停在就绪轮询,直到检测到所有站点进入工作台);
- 安能为单实例 Electron 应用:启动前请先关闭已打开的安能窗口;
- 所有下载/输出路径以项目目录为基准(见
paths.py),与从哪个目录启动无关。