顺心站点支持双账号(双归属地)下载与融合
- 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:
22
CLAUDE.md
22
CLAUDE.md
@@ -40,7 +40,9 @@ playwright install chromium
|
||||
|
||||
### 两套驱动模态 —— 这是理解全局的关键
|
||||
- **网页 4 站**(顺心/百世/中通/韵达):`main_router` 用 Playwright 开 chromium,
|
||||
每站一个 `page`,流程函数签名为 `xxx_download_impl(page)`。
|
||||
每站一个 `page`,流程函数签名为 `xxx_download_impl(page)`。**例外:顺心是双账号**
|
||||
——同一窗口开两个标签页(两个归属地账号),`pages_map["顺心"]` 存为 page **列表**,
|
||||
`shunxin_download(pages)` 接收列表(详见下文「顺心双账号」)。
|
||||
- **安能**:Electron 桌面应用,**不走 Playwright**。`main_router` 以
|
||||
`--remote-debugging-port=<动态空闲端口>` 启动 exe(`launch_anneng`),
|
||||
通过 `site_anneng.set_cdp_port` 告知模块;`site_anneng.py` 用裸 CDP(websocket)
|
||||
@@ -49,7 +51,7 @@ playwright install chromium
|
||||
|
||||
### 分层:路由纯调度,站点模块自洽
|
||||
- `main_router.py` **只调度**:启动浏览器/安能、就绪轮询、登录检测、菜单分发。
|
||||
菜单项直接调 `site_xxx.xxx_download(page)`,**不关心**重试/重置。
|
||||
菜单项直接调 `site_xxx.xxx_download(page)`(顺心传 page 列表),**不关心**重试/重置。
|
||||
- 每个 `site_xxx.py` 对外只暴露"把任务做了"的入口,内部自洽:
|
||||
- `xxx_download(...)` —— 公开入口,= `with_retry(站点, 标签, xxx_download_impl, xxx_reset)`
|
||||
- `xxx_download_impl(...)` —— 单次执行、**无重试**(自动化测试刻意调它以探测原始失败)
|
||||
@@ -67,6 +69,20 @@ playwright install chromium
|
||||
- 例外:**百世**是同步下载(`expect_download`,无队列);**安能-实到**页面无导出按钮,
|
||||
直接抓表格 DOM 翻页。
|
||||
|
||||
### 顺心双账号(双归属地)
|
||||
顺心业务上要同时处理**两个归属地网点**(两个账号)。程序在同一窗口开两个标签页,
|
||||
人工分别登录两个账号(顺心站点支持同浏览器双账号并存,无需独立 context/窗口)。
|
||||
- `main_router` 启动时为顺心开 2 个 `context.new_page()`,`pages_map["顺心"]` 为列表;
|
||||
就绪轮询要求**两个标签页都进主页**才算就绪;初始弹窗对两个标签页各处理一遍。
|
||||
- `shunxin_expected_download(pages)` / `shunxin_actual_download(pages)` 接收 page 列表:
|
||||
先用 `shunxin_belonging(page)` 读各账号归属地(首页「切换网点」控件 `.site___3o7nH`),
|
||||
**去重校验**(两账号同归属地则报错中止,防数据翻倍),再顺序对各账号跑一遍
|
||||
`xxx_download_impl(page, out_tag=归属地)`(产物 `顺心-{归属}-{应到/实到}货物数据.xlsx`),
|
||||
最后 `shunxin_merge_final` 把两份 `pd.concat` 成统一的 `顺心-{应到/实到}货物数据.xlsx`
|
||||
并删中间文件。比对层 `expected_undelivered` **零改动**(仍读同名文件)。
|
||||
- 导出队列不串扰:双账号**顺序执行**,账号 A 走完完整下载流程(远超 40s)后 B 才提交,
|
||||
配合每账号独立 `export_times` + ≤40s 容差,B 不会误匹配 A 的任务。
|
||||
|
||||
### 比对
|
||||
`expected_undelivered.py`(菜单 [9])纯离线:读 `downloads/` 下各站应到/实到 xlsx,
|
||||
比对生成 `output/应到未到数据.xlsx`(汇总 + 各站明细)。
|
||||
@@ -79,6 +95,8 @@ playwright install chromium
|
||||
|
||||
- **登录是手动的**:`main_router` 启动后会停在就绪轮询(`READY_SELECTORS` / `anneng_ready`),
|
||||
直到检测到所有站点进入工作台才进菜单。仅韵达支持凭 `config.yaml` 凭据自动登录。
|
||||
**顺心需登录两个账号**:同一窗口的两个标签页分别登录两个不同归属地账号,两个标签页
|
||||
都进主页后才算就绪(顺心站点支持同浏览器双账号并存,故用同 context 标签页而非独立窗口)。
|
||||
- **安能单实例**:启动前必须先关闭已打开的安能窗口,否则调试端口起不来。
|
||||
- **安能重置绝不能用 `Page.reload`**:reload 会让已开业务 webContents 失去 app 引用、
|
||||
变成 `Target.closeTarget` / `window.close` 都杀不掉的僵尸。重置只能走 tab 条 X 关 tab + 收菜单。
|
||||
|
||||
@@ -101,9 +101,10 @@ python main_router.py
|
||||
|
||||
程序会:
|
||||
|
||||
1. 用 Playwright 打开 4 个网页站点(韵达若配了凭据会尝试自动登录,其余手动登录);
|
||||
1. 用 Playwright 打开各网页站点(**顺心为双账号**:同一窗口开两个标签页,分别登录两个
|
||||
不同归属地账号;韵达若配了凭据会尝试自动登录,其余手动登录);
|
||||
2. 以调试模式启动安能 Electron 应用(动态空闲端口),**需在应用内手动登录**;
|
||||
3. **轮询各站点登录就绪状态**——全部登录完成后自动进入主菜单;
|
||||
3. **轮询各站点登录就绪状态**——全部登录完成后自动进入主菜单(顺心需两个标签页都进主页);
|
||||
4. 弹出主菜单,按编号选择任务。
|
||||
|
||||
主菜单:
|
||||
@@ -140,6 +141,10 @@ python main_router.py
|
||||
站点的 `xxx_reset` 回到初始态 → 重试,**最多 3 次(含首次)**;每次失败都重置
|
||||
(含最终放弃那次),确保环境不残留脏状态。
|
||||
|
||||
> **顺心是双账号特例**:同一窗口开两个标签页(两个归属地账号),`shunxin_download`
|
||||
> 接收 page 列表,内部读各账号归属地 → 去重校验 → 顺序下载 → `pd.concat` 融合成
|
||||
> 统一的 `顺心-{应到/实到}货物数据.xlsx`,比对层无感(仍读同名文件)。
|
||||
|
||||
> 安能是 Electron,由 CDP(远程调试端口)驱动而非 Playwright page。其重置**不用
|
||||
> `Page.reload`**——reload 会让已开的业务 webContents 失去引用、变成无法清理的僵尸,
|
||||
> 故改用"走 tab 条 X 关 tab + 收起菜单"。
|
||||
|
||||
@@ -125,18 +125,38 @@ def run_automation_test(pages_map):
|
||||
},
|
||||
}
|
||||
|
||||
# 构建测试计划:[(站点, 流程中文名, 流程函数), ...]
|
||||
# 构建测试计划:[(站点, 流程中文名, 流程函数, 绑定page, 归属标签), ...]
|
||||
# 顺心为双账号:两个 page 各自读归属地后跑一遍交叉序列;其余站点单 page。
|
||||
plan = []
|
||||
for site_name, flows in flow_table.items():
|
||||
if site_name == "顺心":
|
||||
if "顺心" not in pages_map:
|
||||
continue
|
||||
for sx_idx, sx_page in enumerate(pages_map["顺心"], start=1):
|
||||
try:
|
||||
tag = site_shunxin.shunxin_belonging(sx_page)
|
||||
except Exception:
|
||||
tag = f"账号{sx_idx}" # 读不到归属地时用序号占位,不阻断测试
|
||||
for flow_key in CROSS_TEST_SEQUENCE:
|
||||
label, func = flows[flow_key]
|
||||
plan.append((f"顺心·{tag}", label, func, sx_page, tag))
|
||||
continue
|
||||
if site_name not in pages_map:
|
||||
continue
|
||||
bound_page = pages_map[site_name]
|
||||
for flow_key in CROSS_TEST_SEQUENCE:
|
||||
label, func = flows[flow_key]
|
||||
plan.append((site_name, label, func))
|
||||
plan.append((site_name, label, func, bound_page, ""))
|
||||
# 百世:单流程,跑一次即可
|
||||
if "百世" in pages_map:
|
||||
plan.append(
|
||||
("百世", "应到未到", site_baishi.baishi_download_undelivered_data_impl)
|
||||
(
|
||||
"百世",
|
||||
"应到未到",
|
||||
site_baishi.baishi_download_undelivered_data_impl,
|
||||
pages_map["百世"],
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
if not plan:
|
||||
@@ -150,7 +170,7 @@ def run_automation_test(pages_map):
|
||||
print("====================================================")
|
||||
|
||||
results = [] # [(站点, 流程, 状态, 耗时秒, 错误信息)]
|
||||
for idx, (site_name, label, func) in enumerate(plan, start=1):
|
||||
for idx, (site_name, label, func, bound_page, out_tag) in enumerate(plan, start=1):
|
||||
print("\n----------------------------------------------------")
|
||||
print(f"[步骤 {idx}/{total}] 站点【{site_name}】流程【{label}】")
|
||||
print("----------------------------------------------------")
|
||||
@@ -162,9 +182,9 @@ def run_automation_test(pages_map):
|
||||
# 安能:Electron 应用,无 Playwright page,函数不收 page 参数
|
||||
ret = func()
|
||||
else:
|
||||
page = pages_map[site_name]
|
||||
page.bring_to_front()
|
||||
ret = func(page)
|
||||
bound_page.bring_to_front()
|
||||
# 顺心 _impl 带 out_tag(归属地);其余站点 _impl 仅收 page
|
||||
ret = func(bound_page, out_tag=out_tag) if out_tag else func(bound_page)
|
||||
if ret is False:
|
||||
status = "FAIL"
|
||||
err = "流程返回失败状态"
|
||||
@@ -252,10 +272,21 @@ def run_multi_site_daemon():
|
||||
print("====================================================")
|
||||
|
||||
for site_name, url in active_sites.items():
|
||||
print(f">> 正在启动【{site_name}】页面: {url}")
|
||||
page = context.new_page()
|
||||
page.goto(url)
|
||||
pages_map[site_name] = page
|
||||
if site_name == "顺心":
|
||||
# 顺心:两个归属地账号在同一窗口各开一个标签页(顺心站点支持
|
||||
# 同浏览器双账号并存);pages_map["顺心"] 存为 page 列表。
|
||||
sx_pages = []
|
||||
for acct in range(1, 3):
|
||||
print(f">> 正在启动【顺心】账号{acct}标签页: {url}")
|
||||
sx_page = context.new_page()
|
||||
sx_page.goto(url)
|
||||
sx_pages.append(sx_page)
|
||||
pages_map["顺心"] = sx_pages
|
||||
else:
|
||||
print(f">> 正在启动【{site_name}】页面: {url}")
|
||||
page = context.new_page()
|
||||
page.goto(url)
|
||||
pages_map[site_name] = page
|
||||
|
||||
# 安能:以调试模式启动 Electron 应用(非 Playwright 网页)
|
||||
anneng_proc = None
|
||||
@@ -296,6 +327,12 @@ def run_multi_site_daemon():
|
||||
if site_name == "安能":
|
||||
# 安能走 CDP 判断主页是否就绪(连不上返回 False,不抛异常)
|
||||
ok = site_anneng.anneng_ready()
|
||||
elif site_name == "顺心":
|
||||
# 顺心双账号:两个 page 都进主页才算就绪
|
||||
ok = all(
|
||||
pg.locator(READY_SELECTORS["顺心"]).is_visible(timeout=500)
|
||||
for pg in pages_map["顺心"]
|
||||
)
|
||||
else:
|
||||
# 0.5 秒轻量探测,避免阻塞主循环
|
||||
ok = (
|
||||
@@ -321,20 +358,20 @@ def run_multi_site_daemon():
|
||||
print("【准备】所有站点已就绪,正在清理初始弹窗...")
|
||||
print("====================================================")
|
||||
|
||||
# 顺心:处理初始弹窗
|
||||
# 顺心:处理初始弹窗(双账号两个 page 各处理一遍)
|
||||
if "顺心" in pages_map:
|
||||
try:
|
||||
sx_page = pages_map["顺心"]
|
||||
sx_page.bring_to_front()
|
||||
print(">> 正在处理【顺心】弹窗与遮罩...")
|
||||
sx_page.locator("a").nth(4).click(timeout=2000)
|
||||
sx_page.wait_for_timeout(500)
|
||||
sx_page.get_by_role("button", name="Close").click(timeout=2000)
|
||||
sx_page.wait_for_timeout(500)
|
||||
sx_page.get_by_role("button", name="不再询问").click(timeout=2000)
|
||||
print(" ✅ 【顺心】初始弹窗处理完成。")
|
||||
except Exception:
|
||||
pass # 环境可能很干净没有弹窗,无视报错
|
||||
for acct, sx_page in enumerate(pages_map["顺心"], start=1):
|
||||
try:
|
||||
sx_page.bring_to_front()
|
||||
print(f">> 正在处理【顺心】账号{acct}弹窗与遮罩...")
|
||||
sx_page.locator("a").nth(4).click(timeout=2000)
|
||||
sx_page.wait_for_timeout(500)
|
||||
sx_page.get_by_role("button", name="Close").click(timeout=2000)
|
||||
sx_page.wait_for_timeout(500)
|
||||
sx_page.get_by_role("button", name="不再询问").click(timeout=2000)
|
||||
print(f" ✅ 【顺心】账号{acct}初始弹窗处理完成。")
|
||||
except Exception:
|
||||
pass # 环境可能很干净没有弹窗,无视报错
|
||||
|
||||
# 百世:循环关闭初始弹窗
|
||||
if "百世" in pages_map:
|
||||
@@ -423,13 +460,9 @@ def run_multi_site_daemon():
|
||||
|
||||
try:
|
||||
if choice == "1" and is_site_ready("顺心"):
|
||||
page = pages_map["顺心"]
|
||||
page.bring_to_front()
|
||||
site_shunxin.shunxin_expected_download(page)
|
||||
site_shunxin.shunxin_expected_download(pages_map["顺心"])
|
||||
elif choice == "2" and is_site_ready("顺心"):
|
||||
page = pages_map["顺心"]
|
||||
page.bring_to_front()
|
||||
site_shunxin.shunxin_actual_download(page)
|
||||
site_shunxin.shunxin_actual_download(pages_map["顺心"])
|
||||
elif choice == "3" and is_site_ready("百世"):
|
||||
page = pages_map["百世"]
|
||||
page.bring_to_front()
|
||||
|
||||
161
site_shunxin.py
161
site_shunxin.py
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user