refactor: move flat modules into inbound_verify package (Tier 1, behavior-identical)
Relocate 12 root .py modules into inbound_verify/ (sites/, cli/ subpackages). Rewrite all internal imports to package-qualified; drop the site_ prefix on the 5 site modules and their 28 call sites. Fix paths.py BASE_DIR to anchor at the project root. Add main() entry wrappers (cli/router, cli/server, store). No behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,8 @@ import time
|
||||
|
||||
import yaml
|
||||
|
||||
from paths import CONFIG_PATH
|
||||
from runtime import (
|
||||
from inbound_verify.paths import CONFIG_PATH
|
||||
from inbound_verify.runtime import (
|
||||
APP_SITES,
|
||||
HEARTBEAT_INTERVAL,
|
||||
dispatch_task,
|
||||
@@ -20,15 +20,11 @@ from runtime import (
|
||||
run_heartbeat,
|
||||
)
|
||||
|
||||
import state_store
|
||||
from inbound_verify import state_store
|
||||
|
||||
# 各站点模块(自动化测试 + 比对用;任务派发在 runtime)
|
||||
import site_shunxin
|
||||
import site_baishi
|
||||
import site_zto
|
||||
import site_yunda
|
||||
import site_anneng
|
||||
import expected_undelivered
|
||||
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
|
||||
from inbound_verify import expected_undelivered
|
||||
|
||||
|
||||
def run_undelivered_compare():
|
||||
@@ -57,20 +53,20 @@ def run_automation_test(pages_map):
|
||||
# 不被模块内部"失败→重置→重试"机制掩盖。
|
||||
flow_table = {
|
||||
"顺心": {
|
||||
"expected": ("应到", site_shunxin.shunxin_expected_download_impl),
|
||||
"actual": ("实到", site_shunxin.shunxin_actual_download_impl),
|
||||
"expected": ("应到", shunxin.shunxin_expected_download_impl),
|
||||
"actual": ("实到", shunxin.shunxin_actual_download_impl),
|
||||
},
|
||||
"中通": {
|
||||
"expected": ("应到", site_zto.zto_expected_download_impl),
|
||||
"actual": ("实到", site_zto.zto_actual_download_impl),
|
||||
"expected": ("应到", zto.zto_expected_download_impl),
|
||||
"actual": ("实到", zto.zto_actual_download_impl),
|
||||
},
|
||||
"韵达": {
|
||||
"expected": ("应到", site_yunda.yunda_expected_download_impl),
|
||||
"actual": ("实到", site_yunda.yunda_actual_download_impl),
|
||||
"expected": ("应到", yunda.yunda_expected_download_impl),
|
||||
"actual": ("实到", yunda.yunda_actual_download_impl),
|
||||
},
|
||||
"安能": {
|
||||
"expected": ("应到", site_anneng.anneng_expected_download_impl),
|
||||
"actual": ("实到", site_anneng.anneng_actual_download_impl),
|
||||
"expected": ("应到", anneng.anneng_expected_download_impl),
|
||||
"actual": ("实到", anneng.anneng_actual_download_impl),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,7 +79,7 @@ def run_automation_test(pages_map):
|
||||
continue
|
||||
for sx_idx, sx_page in enumerate(pages_map["顺心"], start=1):
|
||||
try:
|
||||
tag = site_shunxin.shunxin_belonging(sx_page)
|
||||
tag = shunxin.shunxin_belonging(sx_page)
|
||||
except Exception:
|
||||
tag = f"账号{sx_idx}" # 读不到归属地时用序号占位,不阻断测试
|
||||
for flow_key in CROSS_TEST_SEQUENCE:
|
||||
@@ -102,7 +98,7 @@ def run_automation_test(pages_map):
|
||||
(
|
||||
"百世",
|
||||
"应到未到",
|
||||
site_baishi.baishi_download_undelivered_data_impl,
|
||||
baishi.baishi_download_undelivered_data_impl,
|
||||
pages_map["百世"],
|
||||
"",
|
||||
)
|
||||
@@ -343,5 +339,10 @@ def run_multi_site_daemon():
|
||||
print("程序已退出。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
"""交互菜单模式入口。"""
|
||||
run_multi_site_daemon()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -26,9 +26,9 @@ from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from paths import DOWNLOAD_DIR, OUTPUT_DIR
|
||||
import state_store
|
||||
from runtime import (
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, OUTPUT_DIR
|
||||
from inbound_verify import state_store
|
||||
from inbound_verify.runtime import (
|
||||
HEARTBEAT_INTERVAL,
|
||||
TASK_HANDLERS,
|
||||
dispatch_task,
|
||||
@@ -64,7 +64,9 @@ def _worker_loop():
|
||||
# 【P1-2 重启自愈】worker 就绪后清理上轮遗留的 pending/running 僵尸任务
|
||||
cleaned = state_store.fail_stale_tasks()
|
||||
if cleaned:
|
||||
print(f">> [worker] 自愈:清理 {cleaned} 条遗留任务(pending/running → failed)")
|
||||
print(
|
||||
f">> [worker] 自愈:清理 {cleaned} 条遗留任务(pending/running → failed)"
|
||||
)
|
||||
print(">> [worker] 各站就绪,开始接收任务 ...")
|
||||
except Exception as e:
|
||||
worker_state["error"] = str(e)
|
||||
@@ -161,7 +163,9 @@ def create_task(req: TaskRequest):
|
||||
"""提交任务 {site, kind} → 入队,返回 task_id。"""
|
||||
# 【P0】后端未就绪时直接拒绝,避免任务在 worker 启动前入队卡死
|
||||
if not worker_state["ready"]:
|
||||
raise HTTPException(status_code=409, detail="后端尚未就绪,请等待各站点登录完成后再操作")
|
||||
raise HTTPException(
|
||||
status_code=409, detail="后端尚未就绪,请等待各站点登录完成后再操作"
|
||||
)
|
||||
if (req.site, req.kind) not in TASK_HANDLERS:
|
||||
raise HTTPException(status_code=400, detail=f"无效任务: {req.site}/{req.kind}")
|
||||
task_id = state_store.create_task(req.site, req.kind)
|
||||
@@ -287,5 +291,10 @@ def download_data(filename: str):
|
||||
return FileResponse(path, filename=filename)
|
||||
|
||||
|
||||
def main():
|
||||
"""服务模式入口。传字符串导入路径(规范写法;不开 reload/workers 时进程内 import,行为等价)。"""
|
||||
uvicorn.run("inbound_verify.cli.server:app", host="0.0.0.0", port=8000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
main()
|
||||
@@ -63,7 +63,7 @@ def arrived_pieces_zhongtong(df):
|
||||
for v in df["运单号"]:
|
||||
v = str(v).strip()
|
||||
if len(v) > 8 and v[-4:].isdigit():
|
||||
res[v[:-8]].add(v) # 以完整复合串作为“已到单号”存入
|
||||
res[v[:-8]].add(v) # 以完整复合串作为“已到单号”存入
|
||||
return res
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ def arrived_pieces_by_cols(wb_col, piece_col):
|
||||
wb_col:实到表中与应到运单号对齐的干净列
|
||||
(顺心=运单号 / 韵达=主单号 / 安能=所属单号)
|
||||
piece_col:实到表中每件货物的单号列(子单号 / 扫描单号)"""
|
||||
|
||||
def parse(df):
|
||||
res = defaultdict(set)
|
||||
for m, s in zip(df[wb_col], df[piece_col]):
|
||||
@@ -79,6 +80,7 @@ def arrived_pieces_by_cols(wb_col, piece_col):
|
||||
if m and s:
|
||||
res[m].add(s)
|
||||
return res
|
||||
|
||||
return parse
|
||||
|
||||
|
||||
@@ -87,9 +89,9 @@ STATIONS = [
|
||||
"name": "中通",
|
||||
"exp": "中通-应到货物数据.xlsx",
|
||||
"act": "中通-实到货物数据.xlsx",
|
||||
"exp_qty": "交接件数", # 应到件数口径:交接件数(非录单件数)
|
||||
"exp_wb": "运单号", # 应到表运单号列(兼作去重键)
|
||||
"exp_jd": "交接单号", # 未到数据需展示的交接单号
|
||||
"exp_qty": "交接件数", # 应到件数口径:交接件数(非录单件数)
|
||||
"exp_wb": "运单号", # 应到表运单号列(兼作去重键)
|
||||
"exp_jd": "交接单号", # 未到数据需展示的交接单号
|
||||
"arrived_pieces": arrived_pieces_zhongtong,
|
||||
"columns": ["交接单号", "运单号", "总件数"],
|
||||
},
|
||||
@@ -174,7 +176,7 @@ def process(name):
|
||||
|
||||
# 实到件数(新口径)= 实到表单号去重数量(分组 运单->已到单号集合)
|
||||
arrived = cfg["arrived_pieces"](df_act)
|
||||
act_pieces = sum(len(s) for s in arrived.values()) # 全局去重单号数
|
||||
act_pieces = sum(len(s) for s in arrived.values()) # 全局去重单号数
|
||||
|
||||
# 未到:逐运单比较,列出实际已到的单号(顺序号乱序,无法反推缺件序号)
|
||||
rows = []
|
||||
@@ -301,7 +303,9 @@ def process_baishi():
|
||||
# 应到/实到基数取自「扫描综合查询」应扫/已扫(到/接件扫描→当日),
|
||||
# 由 baishi_download_undelivered_data_impl 在同次导航里抓取并落 site_settings。
|
||||
# 未抓取过则 get_setting 返回 "" → 视为无基数(报表显示「—」)。
|
||||
import state_store # 与 _read_business_dates 一致:比对模块纯离线,懒加载
|
||||
from inbound_verify import (
|
||||
state_store,
|
||||
) # 与 _read_business_dates 一致:比对模块纯离线,懒加载
|
||||
|
||||
def _to_int(v):
|
||||
v = (v or "").strip().replace(",", "")
|
||||
@@ -348,7 +352,7 @@ def _read_business_dates(include):
|
||||
"""从状态库读各站业务日期(dispatch 下载成功时快照写入),供报告「数据日期」列。
|
||||
4 站取 expected_business_date(报告按应到口径);百世取 undelivered_business_date。
|
||||
从未下过的站返回空串(诚实留空,不反推)。"""
|
||||
import state_store # lazy import:比对模块本身保持纯离线
|
||||
from inbound_verify import state_store # lazy import:比对模块本身保持纯离线
|
||||
|
||||
status = state_store.get_all_status()
|
||||
dates = {}
|
||||
@@ -534,8 +538,16 @@ def build_summary(ws, results, generated_at, dates=None):
|
||||
# 百世:未到明细已知;若已抓取应到/实到基数(扫描综合查询应扫/已扫)则填真实值
|
||||
if s["应到件"] is not None and s["已到件"] is not None:
|
||||
srate = (s["未到件"] / s["应到件"]) if s["应到件"] else 0
|
||||
vals = [name, s["运单数"], s["应到件"], s["已到件"],
|
||||
s["未到件"], srate, "—", "—"]
|
||||
vals = [
|
||||
name,
|
||||
s["运单数"],
|
||||
s["应到件"],
|
||||
s["已到件"],
|
||||
s["未到件"],
|
||||
srate,
|
||||
"—",
|
||||
"—",
|
||||
]
|
||||
else:
|
||||
vals = [name, s["运单数"], "—", "—", s["未到件"], "—", "—", "—"]
|
||||
else:
|
||||
@@ -564,7 +576,12 @@ def build_summary(ws, results, generated_at, dates=None):
|
||||
cell.fill = PatternFill("solid", fgColor=ZEBRA)
|
||||
if isinstance(v, (int, float)):
|
||||
cell.number_format = "0.0%" if i == 5 else "#,##0"
|
||||
if i == 5 and s is not None and isinstance(v, (int, float)) and not isinstance(v, bool):
|
||||
if (
|
||||
i == 5
|
||||
and s is not None
|
||||
and isinstance(v, (int, float))
|
||||
and not isinstance(v, bool)
|
||||
):
|
||||
cell.fill = PatternFill("solid", fgColor=heat(srate))
|
||||
ws.row_dimensions[r].height = 19
|
||||
r += 1
|
||||
@@ -6,7 +6,8 @@
|
||||
import os
|
||||
|
||||
# 项目根目录(以本文件所在位置为基准,与从哪个目录启动脚本无关)
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
# __file__ = <root>/inbound_verify/paths.py → 上两级 = 项目根
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# 统一的下载 / 输出目录
|
||||
DOWNLOAD_DIR = os.path.join(BASE_DIR, "downloads")
|
||||
@@ -21,22 +21,18 @@ from datetime import datetime, timedelta
|
||||
import yaml
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
import state_store
|
||||
import site_shunxin
|
||||
import site_baishi
|
||||
import site_zto
|
||||
import site_yunda
|
||||
import site_anneng
|
||||
import expected_undelivered # dispatch 的 compare 任务用
|
||||
from inbound_verify import state_store
|
||||
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
|
||||
from inbound_verify import expected_undelivered # dispatch 的 compare 任务用
|
||||
|
||||
# 各网页站点首页 URL(单一来源,取自各站点模块 HOME_URL)
|
||||
SITES_CONFIG = {
|
||||
"顺心": site_shunxin.HOME_URL,
|
||||
"百世": site_baishi.HOME_URL,
|
||||
"中通": site_zto.HOME_URL,
|
||||
"韵达": site_yunda.HOME_URL,
|
||||
"顺心": shunxin.HOME_URL,
|
||||
"百世": baishi.HOME_URL,
|
||||
"中通": zto.HOME_URL,
|
||||
"韵达": yunda.HOME_URL,
|
||||
}
|
||||
|
||||
# 站点就绪特征:登录成功进入工作台后的标志性控件
|
||||
@@ -114,8 +110,10 @@ def launch_anneng(app_path):
|
||||
anneng_env.pop("NODE_OPTIONS", None)
|
||||
port = _find_free_port()
|
||||
print(f">> 以调试模式启动【安能】应用(端口 {port}):{app_path}")
|
||||
proc = subprocess.Popen([app_path, f"--remote-debugging-port={port}"], env=anneng_env)
|
||||
site_anneng.set_cdp_port(port)
|
||||
proc = subprocess.Popen(
|
||||
[app_path, f"--remote-debugging-port={port}"], env=anneng_env
|
||||
)
|
||||
anneng.set_cdp_port(port)
|
||||
if not _wait_cdp_up(port):
|
||||
raise RuntimeError(
|
||||
f"安能应用调试端口 {port} 未就绪——可能应用已在运行(单实例),"
|
||||
@@ -136,7 +134,7 @@ def probe_site_login(site_name, pages_map):
|
||||
if site_name not in pages_map:
|
||||
return False
|
||||
if site_name == "安能":
|
||||
return site_anneng.anneng_ready()
|
||||
return anneng.anneng_ready()
|
||||
if site_name == "顺心":
|
||||
return all(
|
||||
pg.locator(READY_SELECTORS["顺心"]).is_visible(timeout=500)
|
||||
@@ -333,7 +331,7 @@ def launch_and_prepare(debug_mode=False, debug_target=""):
|
||||
if "韵达" in pages_map:
|
||||
try:
|
||||
pages_map["韵达"].bring_to_front()
|
||||
site_yunda.yunda_login(pages_map["韵达"])
|
||||
yunda.yunda_login(pages_map["韵达"])
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 韵达前置自动登录模块发生波动: {e}")
|
||||
|
||||
@@ -403,9 +401,9 @@ def _dismiss_initial_popups(pages_map):
|
||||
bs_page = pages_map["百世"]
|
||||
bs_page.bring_to_front()
|
||||
print(">> 正在处理【百世】初始弹窗(阅读消息 / 配置检查 / 优惠券广告)...")
|
||||
# 委托给 site_baishi 的专用清理:优惠券广告是全屏居中 modal,
|
||||
# 委托给 baishi 的专用清理:优惠券广告是全屏居中 modal,
|
||||
# 关闭键为 .ant-modal-close(纯图标无文字),必须点它才能真正关掉。
|
||||
site_baishi.dismiss_baishi_popups(bs_page)
|
||||
baishi.dismiss_baishi_popups(bs_page)
|
||||
print(" ✅ 【百世】初始弹窗处理完成。")
|
||||
except Exception:
|
||||
pass
|
||||
@@ -414,7 +412,7 @@ def _dismiss_initial_popups(pages_map):
|
||||
yd_page = pages_map["韵达"]
|
||||
yd_page.bring_to_front()
|
||||
print(">> 正在检查【韵达】音频设备授权提示...")
|
||||
site_yunda.dismiss_audio_prompt(yd_page)
|
||||
yunda.dismiss_audio_prompt(yd_page)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -460,20 +458,20 @@ def _site_undelivered_handler(site):
|
||||
|
||||
|
||||
TASK_HANDLERS = {
|
||||
("顺心", "expected"): _web_handler("顺心", site_shunxin.shunxin_expected_download),
|
||||
("顺心", "actual"): _web_handler("顺心", site_shunxin.shunxin_actual_download),
|
||||
("顺心", "expected"): _web_handler("顺心", shunxin.shunxin_expected_download),
|
||||
("顺心", "actual"): _web_handler("顺心", shunxin.shunxin_actual_download),
|
||||
("顺心", "undelivered"): _site_undelivered_handler("顺心"),
|
||||
("百世", "undelivered"): _web_handler(
|
||||
"百世", site_baishi.baishi_download_undelivered_data
|
||||
"百世", baishi.baishi_download_undelivered_data
|
||||
),
|
||||
("中通", "expected"): _web_handler("中通", site_zto.zto_expected_download),
|
||||
("中通", "actual"): _web_handler("中通", site_zto.zto_actual_download),
|
||||
("中通", "expected"): _web_handler("中通", zto.zto_expected_download),
|
||||
("中通", "actual"): _web_handler("中通", zto.zto_actual_download),
|
||||
("中通", "undelivered"): _site_undelivered_handler("中通"),
|
||||
("韵达", "expected"): _web_handler("韵达", site_yunda.yunda_expected_download),
|
||||
("韵达", "actual"): _web_handler("韵达", site_yunda.yunda_actual_download),
|
||||
("韵达", "expected"): _web_handler("韵达", yunda.yunda_expected_download),
|
||||
("韵达", "actual"): _web_handler("韵达", yunda.yunda_actual_download),
|
||||
("韵达", "undelivered"): _site_undelivered_handler("韵达"),
|
||||
("安能", "expected"): lambda ctx: site_anneng.anneng_expected_download(),
|
||||
("安能", "actual"): lambda ctx: site_anneng.anneng_actual_download(),
|
||||
("安能", "expected"): lambda ctx: anneng.anneng_expected_download(),
|
||||
("安能", "actual"): lambda ctx: anneng.anneng_actual_download(),
|
||||
("安能", "undelivered"): _site_undelivered_handler("安能"),
|
||||
("__compare__", "compare"): lambda ctx: (expected_undelivered.main() or True),
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# site_anneng.py
|
||||
# sites/anneng.py
|
||||
#
|
||||
# 安能全网门户(Electron 应用)—— 应到货物数据下载。
|
||||
#
|
||||
@@ -7,7 +7,7 @@
|
||||
# 本模块通过该端口的 CDP 驱动它;「进站交接单查询」「导出下载」等右侧 tab 是
|
||||
# **独立 webContents**(远程网页),在 /json 里是独立目标。
|
||||
# 也可脱离 main_router 独立运行(此时用默认端口 9222,需已自行启动应用):
|
||||
# .venv/Scripts/python.exe site_anneng.py
|
||||
# python -m inbound_verify.sites.anneng expected # 或 actual
|
||||
#
|
||||
# 应到 = 运单信息导出(每张交接单下应到的运单明细)。整体流程:
|
||||
# 主页(重庆鱼洞镇) → 运营管理 → 进站管理 → 进站交接单查询(tab)
|
||||
@@ -39,8 +39,8 @@ import yaml
|
||||
# Windows 控制台默认 GBK,打印中文/emoji 会崩,强制 UTF-8。
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -73,7 +73,7 @@ def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
return False
|
||||
|
||||
|
||||
CDP_PORT = 9222 # 默认端口(独立运行 site_anneng.py 时用);main_router 启动时会用 set_cdp_port 覆盖
|
||||
CDP_PORT = 9222 # 默认端口(独立运行(python -m inbound_verify.sites.anneng)时用);main_router 启动时会用 set_cdp_port 覆盖
|
||||
POLL_INTERVAL = 0.5
|
||||
DEFAULT_TIMEOUT = 25.0
|
||||
|
||||
@@ -973,6 +973,7 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None):
|
||||
返回 True 表示写入并校验成功,否则 False。
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
ph = json.dumps(placeholder)
|
||||
val_json = json.dumps(value)
|
||||
if target_ymd is None:
|
||||
@@ -998,7 +999,9 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None):
|
||||
"(() => {const inp=[...document.querySelectorAll('input')]"
|
||||
f".find(i=>i.placeholder==={ph}); if(!inp) return false; inp.focus();"
|
||||
"let sel=true; try{ sel=document.execCommand('selectAll'); }catch(e){ try{inp.select();}catch(_){ sel=false; } }"
|
||||
"let done=false; try{ done=document.execCommand('insertText',false," + val_json + "); }catch(e){ done=false; }"
|
||||
"let done=false; try{ done=document.execCommand('insertText',false,"
|
||||
+ val_json
|
||||
+ "); }catch(e){ done=false; }"
|
||||
"if(!done){ const s=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;"
|
||||
f" s.call(inp,{val_json}); inp.dispatchEvent(new Event('input',{{bubbles:true}})); }}"
|
||||
"return inp.value;})()"
|
||||
@@ -1016,7 +1019,9 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None):
|
||||
return True
|
||||
time.sleep(0.4)
|
||||
# 全部尝试失败:告警,避免静默下成「今天」
|
||||
print(f" ⚠ set_scan_date 未能将 {placeholder} 设为目标日期 {target_ymd}(请检查 DatePicker 是否就绪)")
|
||||
print(
|
||||
f" ⚠ set_scan_date 未能将 {placeholder} 设为目标日期 {target_ymd}(请检查 DatePicker 是否就绪)"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
@@ -1158,8 +1163,12 @@ def wait_scan_form_ready(cdp, timeout=20.0):
|
||||
f".find(i=>i.placeholder==={json.dumps(ph)}); if(!inp) return false; inp.focus(); return true;}})()"
|
||||
)
|
||||
try:
|
||||
wait_until(cdp, "!!document.querySelector('.ant-picker-panel')",
|
||||
"预热面板打开", timeout=4.0)
|
||||
wait_until(
|
||||
cdp,
|
||||
"!!document.querySelector('.ant-picker-panel')",
|
||||
"预热面板打开",
|
||||
timeout=4.0,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
cdp.eval(
|
||||
@@ -1,11 +1,11 @@
|
||||
# site_baishi.py
|
||||
# sites/baishi.py
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -181,8 +181,12 @@ def baishi_download_undelivered_data_impl(page):
|
||||
# 10(应扫) 11(已扫) 12(未扫) 13(率)。(与下方 nth(12) 未扫同源)
|
||||
try:
|
||||
_first_row = page.locator(".ant-table-tbody > tr").first
|
||||
_exp_txt = _first_row.locator("td").nth(10).inner_text().strip().replace(",", "")
|
||||
_arr_txt = _first_row.locator("td").nth(11).inner_text().strip().replace(",", "")
|
||||
_exp_txt = (
|
||||
_first_row.locator("td").nth(10).inner_text().strip().replace(",", "")
|
||||
)
|
||||
_arr_txt = (
|
||||
_first_row.locator("td").nth(11).inner_text().strip().replace(",", "")
|
||||
)
|
||||
|
||||
def _to_int(v):
|
||||
try:
|
||||
@@ -1,4 +1,4 @@
|
||||
# site_shunxin.py
|
||||
# sites/shunxin.py
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -7,8 +7,8 @@ import yaml
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -1,4 +1,4 @@
|
||||
# site_yunda.py
|
||||
# sites/yunda.py
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -7,8 +7,8 @@ import yaml
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -1,4 +1,4 @@
|
||||
# site_zto.py
|
||||
# sites/zto.py
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -7,8 +7,8 @@ import yaml
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
from inbound_verify import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -9,7 +9,7 @@ import os
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
from paths import STATE_DB_PATH
|
||||
from inbound_verify.paths import STATE_DB_PATH
|
||||
|
||||
# 登录态枚举
|
||||
LOGIN_UNKNOWN = "unknown" # 尚未探测过
|
||||
@@ -28,9 +28,11 @@ import psycopg
|
||||
import yaml
|
||||
from psycopg.types.json import Jsonb
|
||||
|
||||
from paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR
|
||||
from inbound_verify.paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR
|
||||
|
||||
import expected_undelivered as eu # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源)
|
||||
from inbound_verify import (
|
||||
expected_undelivered as eu,
|
||||
) # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源)
|
||||
|
||||
SCHEMA_PATH = os.path.join(BASE_DIR, "schema.sql")
|
||||
|
||||
@@ -376,7 +378,7 @@ def ingest(site=None):
|
||||
# ============================== 命令行 ==============================
|
||||
|
||||
|
||||
def _cli():
|
||||
def main():
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "all"
|
||||
site = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
if cmd == "createdb":
|
||||
@@ -395,4 +397,4 @@ def _cli():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_cli()
|
||||
main()
|
||||
Reference in New Issue
Block a user