Misaka dc7653c256 fix(db_compare): anchor comparison on actual_offset so yunda reads fresh data
比对以实到扫描日(actual.scan_time::date)为锚,锚点日期必须等于实到下载日 = actual_offset。
原 _site_undelivered_handler(runtime.py)与 _target_date_for(db_compare.py)误用
expected_offset 算锚点:韵达 exp=1 / act=0,锚点落到 today-1(昨天),读到历史数据。
两处改用 actual_offset 后韵达锚点 = today,反推出 expected 的昨天批次正确参与比对。
其余 3 站 exp == act == 0,锚点不变。

5 站真机验证:韵达 07-31 比对现读新鲜数据 107/104/3(修复前读昨天 116/115/1);
中通/安能/顺心 不变;全站汇总报表重新生成。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-31 22:58:12 +08:00

物流到货数据自动下载与应到未到核对工具

自动登录 5 家物流承运商的工作台,下载"应到 / 实到"货物数据,并离线比对出 应到未到(该到没到)的异常运单,最终汇总成一份 Excel 报表。

适用场景:网点每日核对进站货物是否到齐。


一、覆盖站点与流程

5 个站点中4 个是网页、1 个是 Electron 桌面应用:

站点 形态 应到 实到
顺心捷达 网页 运单信息 卸车扫描记录
百世快运 网页 —(直接提取"应到未到/当日未扫",单流程)
中通快运 网页 运单信息 到件扫描
韵达快运 网页 进站主单 扫描记录
安能全网门户 Electron 应用 运单信息 网点到件扫描
  • 应到 = 进站交接单下的运单明细(这批货"应该"到);
  • 实到 = 到件 / 卸车扫描记录(实际扫到了哪些);
  • 9 个下载流程(顺心/中通/韵达/安能 各 2 个 + 百世 1 个)。

每个站点产出独立的 {站点}-应到货物数据.xlsx / {站点}-实到货物数据.xlsx (百世为 百世-应到未到货物数据.xlsx),落在 downloads/


二、目录结构

InboundVerify/
├── pyproject.toml             # 打包 + 依赖 + console_scriptsinbound-verify 等)
├── inbound_verify/            # 源码包
│   ├── paths.py               # 统一路径锚点(以项目目录为基准,不依赖 cwd
│   ├── runtime.py             # 两种模式共享核心(启动 / 就绪 / 任务派发 / 心跳)
│   ├── state_store.py         # SQLite 状态持久化state/state.db
│   ├── domain.py               # 站点 / 文件名 / 列映射共享配置单一来源leaf
│   ├── compare.py              # 全站点应到未到离线比对,输出 output/应到未到数据.xlsx
│   ├── store.py               # DB CLI 入口createdb|init|ingest|ingest-one|all
│   ├── sites/                 # 各站点模块(流程 + 重置 + 重试,自洽)
│   │   ├── shunxin.py         # 顺心(含双账号)
│   │   ├── baishi.py          # 百世
│   │   ├── zto.py             # 中通
│   │   ├── yunda.py           # 韵达(含自动登录)
│   │   └── anneng.py          # 安能Electron + CDP 驱动)
│   └── cli/                   # 命令行入口
│       ├── router.py          # 交互菜单(调度层:启动 / 就绪轮询 / 登录检测 / 菜单分发)
│       └── server.py          # FastAPI 服务模式(常驻 + HTTP 触发)
├── config.example.yaml        # 配置模板
├── config.yaml                # 真实配置(自行创建,已被 .gitignore 忽略)
├── schema.sql                 # 数据库表结构store.py createdb / init 使用)
├── requirements.txt           # pyproject 依赖的静态镜像
├── downloads/                 # 各站点下载的原始数据
├── output/                    # 比对报表输出
├── state/                     # 运行状态持久化state.db
└── 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. 以可编辑模式安装本包(注册 inbound-verify 等命令)
pip install -e .

# 4. 安装 Playwright 浏览器内核(网页站点用)
playwright install chromium

# 5. 由模板创建本地配置并填入真实凭据
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 -m inbound_verify.cli.router
# 或装包后直接用命令:
inbound-verify

程序会:

  1. 用 Playwright 打开各网页站点(顺心为双账号:同一窗口开两个标签页,分别登录两个 不同归属地账号;韵达若配了凭据会尝试自动登录,其余手动登录);
  2. 以调试模式启动安能 Electron 应用(动态空闲端口),需在应用内手动登录
  3. 轮询各站点登录就绪状态——全部登录完成后自动进入主菜单(顺心需两个标签页都进主页);
  4. 弹出主菜单,按编号选择任务。

主菜单:

[1][2]   顺心 应到 / 实到
[3]      百世 应到未到(当日未扫)
[4][5]   中通 应到 / 实到
[6][7]   韵达 应到 / 实到
[10][11] 安能 应到 / 实到
[8]      全站点自动化测试(交叉跑通校验)
[9]      应到未到比对(全站点汇总 → output/应到未到数据.xlsx
[0]      退出

DB CLI入库

# DB CLI建库 / 初始化 / 灌数据 / 全流程 / 单站单类
.venv/Scripts/python.exe -m inbound_verify.store createdb   # 或 init | ingest | ingest-one <site> <kind> | all
# 注下载成功后会自动入库postgres.auto_ingest默认开ingest-one 用于手动重灌指定站/类。

六、架构

分层原则:路由层只调度,站点模块自洽。

  • inbound_verify.cli.router(调度层):负责启动浏览器 / 安能、就绪轮询、登录检测、 菜单分发。不关心"任务能否完成、失败怎么办"——只调 inbound_verify.sites.xxx_download(page) 然后等结果。
  • inbound_verify.sites.*(站点模块):每个模块对外只暴露一个"把任务做了"的入口 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 应用:启动前请先关闭已打开的安能窗口;
  • 所有下载/输出路径以项目目录为基准(见 inbound_verify.paths),与从哪个目录启动无关。
Description
Automated inbound verification system for logistics data processing
Readme 1.9 MiB
Languages
Python 100%