顺心站点支持双账号(双归属地)下载与融合

- main_router: 顺心在同一窗口开两个标签页登录两个归属地账号;就绪轮询、
  初始弹窗、菜单 [1][2]、自动化测试均改为按 page 列表处理
- site_shunxin: download 入口改为接收 page 列表;新增 shunxin_belonging
  读归属地、shunxin_merge_final 融合两账号数据;impl 加 out_tag 参数化
  各账号产物文件名;两账号同归属地时去重校验中止以防数据翻倍
- expected_undelivered: 零改动(融合后产物仍为同名文件)
- 更新 CLAUDE.md / README.md 文档说明

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-07-15 22:36:58 +08:00
parent 75abbdf14c
commit 55e9e9e931
4 changed files with 230 additions and 55 deletions

View File

@@ -71,19 +71,109 @@ def _close_tab(page, tab_name):
print(f" ⚠️ 关闭标签页【{tab_name.strip()}】时出错: {e}")
def shunxin_expected_download(page):
"""顺心:应到货物数据下载(内部含异常兜底重试,路由层无感)"""
def _sanitize_for_filename(name):
"""剔除 Windows 文件名非法字符,避免归属地名含特殊字符导致落盘失败"""
return re.sub(r'[\\/:*?"<>|]', "", str(name)).strip()
return with_retry(
"顺心",
"应到",
lambda: shunxin_expected_download_impl(page),
lambda: shunxin_reset(page),
def shunxin_belonging(page):
"""读取顺心当前账号的归属网点名(仅在首页可见,须在导航离开首页前调用)。
取首页「切换网点」下拉框选中项的 title形如「【SX】重庆巴南龙海大道店」
去掉【XX】前缀得到归属地如「重庆巴南龙海大道店」并做文件名安全处理。
读取失败时抛异常,交由上层处理。
"""
item = page.locator(".site___3o7nH .ant-select-selection-item").first
title = (item.get_attribute("title") or item.inner_text() or "").strip()
if not title:
raise RuntimeError("未能读取顺心归属网点(首页「切换网点」控件为空)")
tag = re.sub(r"^【[^】]*】", "", title).strip() or title
return _sanitize_for_filename(tag)
def shunxin_merge_final(kind, tags):
"""把各归属地的中间产物融合成统一的「顺心-{kind}货物数据.xlsx」。
kind ∈ {"应到","实到"}tags 为各账号归属地列表。逐个读取
「顺心-{tag}-{kind}货物数据.xlsx」缺失则跳过容错空数据账号pd.concat
后写出统一文件,并删除中间带 tag 的文件;全部缺失则仅提示、不产出。
"""
final_name = f"顺心-{kind}货物数据.xlsx"
final_path = os.path.join(DOWNLOAD_DIR, final_name)
frames = []
mid_paths = []
for tag in tags:
mid_name = f"顺心-{tag}-{kind}货物数据.xlsx"
mid_path = os.path.join(DOWNLOAD_DIR, mid_name)
if not os.path.exists(mid_path):
print(f" 归属【{tag}】无{kind}中间文件(可能本次无数据),跳过。")
continue
mid_paths.append(mid_path)
try:
df = pd.read_excel(mid_path, dtype=str)
if not df.empty:
frames.append(df)
except Exception as e:
print(f" ⚠️ 读取中间文件 {mid_name} 失败: {e}")
if not frames:
print(f">> ⚠️ 所有归属地均无{kind}数据,未生成 {final_name}")
return
combined = pd.concat(frames, ignore_index=True)
combined.to_excel(final_path, index=False)
print("====================================================")
print(
f" {kind}数据融合完成(共 {len(combined)} 行),输出: downloads/{final_name}"
)
print("====================================================")
for mid_path in mid_paths:
try:
os.remove(mid_path)
except Exception:
pass
def shunxin_expected_download_impl(page):
"""顺心:应到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
def shunxin_expected_download(pages):
"""顺心:应到货物数据下载(双账号/双归属地,内部含异常兜底重试与数据融合)。
pages 为该站点的 page 列表(双账号在同一窗口的各一个标签页)。
先在首页读取各账号归属地并去重校验(两账号登同一归属地则中止,防数据翻倍),
再顺序对各账号跑一遍下载impl 用归属地作输出文件后缀),最后融合成统一的
「顺心-应到货物数据.xlsx」。路由层只需传入 page 列表,对双账号无感。
"""
tags = []
for idx, pg in enumerate(pages, start=1):
tag = shunxin_belonging(pg)
print(f">> 【顺心】账号{idx} 归属网点:{tag}")
tags.append(tag)
if len(set(tags)) != len(tags):
raise RuntimeError(
f"顺心两个账号归属地相同({tags}),疑似登录了同一账号,已中止以防数据翻倍。"
)
for idx, (pg, tag) in enumerate(zip(pages, tags), start=1):
pg.bring_to_front()
print(f"\n========== 顺心 · 账号{idx}{tag})应到数据下载 ==========")
with_retry(
f"顺心-{tag}",
"应到",
lambda p=pg, t=tag: shunxin_expected_download_impl(p, out_tag=t),
lambda p=pg: shunxin_reset(p),
)
shunxin_merge_final("应到", tags)
def shunxin_expected_download_impl(page, out_tag=""):
"""顺心:应到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。
out_tag 为归属地标签时,合并产物命名为「顺心-{out_tag}-应到货物数据.xlsx」
作为双账号融合前的各账号中间文件;为空时退化为「顺心-应到货物数据.xlsx」。
"""
print("\n▶ 开始执行【顺心 - 应到货物数据下载】任务...")
# 初始化并创建下载目录
@@ -344,7 +434,10 @@ def shunxin_expected_download_impl(page):
if all_data_frames:
combined_df = pd.concat(all_data_frames, ignore_index=True)
final_output_path = os.path.join(download_dir, "顺心-应到货物数据.xlsx")
suffix = f"-{out_tag}" if out_tag else ""
final_output_path = os.path.join(
download_dir, f"顺心{suffix}-应到货物数据.xlsx"
)
combined_df.to_excel(final_output_path, index=False)
print(f"====================================================")
print(f" 合并完成,输出文件: {final_output_path}")
@@ -364,19 +457,42 @@ def shunxin_expected_download_impl(page):
return False
def shunxin_actual_download(page):
"""顺心:实到货物数据下载(内部含异常兜底重试,路由层无感)。"""
def shunxin_actual_download(pages):
"""顺心:实到货物数据下载(双账号/双归属地,内部含异常兜底重试与数据融合)。
return with_retry(
"顺心",
"实到",
lambda: shunxin_actual_download_impl(page),
lambda: shunxin_reset(page),
)
与 shunxin_expected_download 同构:读归属地 → 去重校验 → 顺序各账号下载 →
融合成统一的「顺心-实到货物数据.xlsx」。
"""
tags = []
for idx, pg in enumerate(pages, start=1):
tag = shunxin_belonging(pg)
print(f">> 【顺心】账号{idx} 归属网点:{tag}")
tags.append(tag)
if len(set(tags)) != len(tags):
raise RuntimeError(
f"顺心两个账号归属地相同({tags}),疑似登录了同一账号,已中止以防数据翻倍。"
)
for idx, (pg, tag) in enumerate(zip(pages, tags), start=1):
pg.bring_to_front()
print(f"\n========== 顺心 · 账号{idx}{tag})实到数据下载 ==========")
with_retry(
f"顺心-{tag}",
"实到",
lambda p=pg, t=tag: shunxin_actual_download_impl(p, out_tag=t),
lambda p=pg: shunxin_reset(p),
)
shunxin_merge_final("实到", tags)
def shunxin_actual_download_impl(page):
"""顺心:实到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
def shunxin_actual_download_impl(page, out_tag=""):
"""顺心:实到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。
out_tag 为归属地标签时,合并产物命名为「顺心-{out_tag}-实到货物数据.xlsx」
作为双账号融合前的各账号中间文件;为空时退化为「顺心-实到货物数据.xlsx」。
"""
print("\n▶ 开始执行【顺心 - 实到货物数据下载】任务...")
download_dir = DOWNLOAD_DIR
@@ -608,7 +724,10 @@ def shunxin_actual_download_impl(page):
if all_data_frames:
combined_df = pd.concat(all_data_frames, ignore_index=True)
final_output_path = os.path.join(download_dir, "顺心-实到货物数据.xlsx")
suffix = f"-{out_tag}" if out_tag else ""
final_output_path = os.path.join(
download_dir, f"顺心{suffix}-实到货物数据.xlsx"
)
combined_df.to_excel(final_output_path, index=False)
print(f"====================================================")
print(f" 合并完成,输出文件: {final_output_path}")