Add cross-flow automation test and tone down log/comment wording
- main_router: add run_automation_test that runs each site's download flows in a cross sequence (expected/actual) plus a pass/fail report with timings; expose it as menu [8] - Site download functions now return False on their exception paths so the test harness can record failures (baishi also flags config-read failure) - Replace overblown log/comment phrasing across all modules with plain statements; trim docstrings Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
174
main_router.py
174
main_router.py
@@ -1,6 +1,7 @@
|
|||||||
# main_router.py
|
# main_router.py
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
@@ -22,7 +23,7 @@ SITES_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 网点特征注册表:定义每个网点登录成功、成功进入工作台的标志性控件
|
# 站点就绪特征:每个站点登录成功进入工作台后的标志性控件
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
READY_SELECTORS = {
|
READY_SELECTORS = {
|
||||||
"顺心": 'h1:has-text("盟商门户网")',
|
"顺心": 'h1:has-text("盟商门户网")',
|
||||||
@@ -33,7 +34,7 @@ READY_SELECTORS = {
|
|||||||
|
|
||||||
|
|
||||||
def task_process_undelivered_data(site_name="顺心"):
|
def task_process_undelivered_data(site_name="顺心"):
|
||||||
"""全局模块:应到未到异常件比对引擎 (支持动态网点前缀)"""
|
"""应到未到比对:找出应到但未实到的运单 (按站点前缀)"""
|
||||||
print(f"\n▶ 开始执行【{site_name} - 应到未到数据处理】任务...")
|
print(f"\n▶ 开始执行【{site_name} - 应到未到数据处理】任务...")
|
||||||
|
|
||||||
download_dir = DOWNLOAD_DIR
|
download_dir = DOWNLOAD_DIR
|
||||||
@@ -56,10 +57,10 @@ def task_process_undelivered_data(site_name="顺心"):
|
|||||||
df_actual = pd.read_excel(actual_path, dtype=str, keep_default_na=False)
|
df_actual = pd.read_excel(actual_path, dtype=str, keep_default_na=False)
|
||||||
|
|
||||||
if "运单号" not in df_expected.columns or "运单号" not in df_actual.columns:
|
if "运单号" not in df_expected.columns or "运单号" not in df_actual.columns:
|
||||||
print("❌ 核心资产校验失败:数据源中缺失【运单号】字段,请检查导出配置。")
|
print("❌ 校验失败:数据源缺少【运单号】字段,请检查导出配置。")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(">> 正在启动多维数据集比对引擎...")
|
print(">> 正在比对应到与实到数据...")
|
||||||
|
|
||||||
# 统一运单号为去空白字符串,消除 int/float 与 str 混读导致 isin 永不命中的隐患
|
# 统一运单号为去空白字符串,消除 int/float 与 str 混读导致 isin 永不命中的隐患
|
||||||
df_expected["运单号"] = df_expected["运单号"].astype(str).str.strip()
|
df_expected["运单号"] = df_expected["运单号"].astype(str).str.strip()
|
||||||
@@ -78,20 +79,120 @@ def task_process_undelivered_data(site_name="顺心"):
|
|||||||
]
|
]
|
||||||
df_output = df_undelivered[available_columns]
|
df_output = df_undelivered[available_columns]
|
||||||
|
|
||||||
print(f">> 筛选完毕!共捕获到异常【应到未到】货物数据: {len(df_output)} 条。")
|
print(f">> 比对完成,共筛选出【应到未到】运单: {len(df_output)} 条。")
|
||||||
|
|
||||||
df_output.to_excel(output_path, index=False)
|
df_output.to_excel(output_path, index=False)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 异常比对流完成!独立数据已安全输出。")
|
print(f" 比对完成,结果已输出。")
|
||||||
print(f" 📁 成果归档路径: {output_path}")
|
print(f" 📁 输出路径: {output_path}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 数据处理引擎在执行连接和输出时发生致命异常: {e}")
|
print(f"❌ 比对过程中发生异常: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# 自动化测试入口
|
||||||
|
# ====================================================================
|
||||||
|
# 每个(非百世)站点的交叉测试序列:覆盖两种下载流程之间的全部 4 种相邻转换,
|
||||||
|
# 用于验证无论上一个流程把页面留在什么状态,下一个流程都能正常运行:
|
||||||
|
# 应到->实到、实到->应到、应到->应到、实到->实到
|
||||||
|
CROSS_TEST_SEQUENCE = ["expected", "actual", "expected", "expected", "actual", "actual"]
|
||||||
|
|
||||||
|
|
||||||
|
def run_automation_test(pages_map):
|
||||||
|
"""自动化测试入口:按交叉序列逐个跑通各站点的下载流程,结束后打印统计报告。
|
||||||
|
|
||||||
|
判定规则:流程函数返回 False 或抛出异常记为 FAIL,其余记为 PASS。
|
||||||
|
"""
|
||||||
|
# 站点 -> {流程键: (中文名, 流程函数)};百世为单流程,单独处理
|
||||||
|
flow_table = {
|
||||||
|
"顺心": {
|
||||||
|
"expected": ("应到", site_shunxin.shunxin_expected_download),
|
||||||
|
"actual": ("实到", site_shunxin.shunxin_actual_download),
|
||||||
|
},
|
||||||
|
"中通": {
|
||||||
|
"expected": ("应到", site_zto.zto_expected_download),
|
||||||
|
"actual": ("实到", site_zto.zto_actual_download),
|
||||||
|
},
|
||||||
|
"韵达": {
|
||||||
|
"expected": ("应到", site_yunda.yunda_expected_download),
|
||||||
|
"actual": ("实到", site_yunda.yunda_actual_download),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建测试计划:[(站点, 流程中文名, 流程函数), ...]
|
||||||
|
plan = []
|
||||||
|
for site_name, flows in flow_table.items():
|
||||||
|
if site_name not in pages_map:
|
||||||
|
continue
|
||||||
|
for flow_key in CROSS_TEST_SEQUENCE:
|
||||||
|
label, func = flows[flow_key]
|
||||||
|
plan.append((site_name, label, func))
|
||||||
|
# 百世:单流程,跑一次即可
|
||||||
|
if "百世" in pages_map:
|
||||||
|
plan.append(("百世", "应到未到", site_baishi.baishi_download_undelivered_data))
|
||||||
|
|
||||||
|
if not plan:
|
||||||
|
print("\n⚠️ 当前没有已就绪的站点,无法执行自动化测试。")
|
||||||
|
return
|
||||||
|
|
||||||
|
total = len(plan)
|
||||||
|
print("\n====================================================")
|
||||||
|
print(f"自动化测试开始,共 {total} 个步骤。")
|
||||||
|
print("(双流程站点按应到/实到交叉序列执行,覆盖全部相邻转换)")
|
||||||
|
print("====================================================")
|
||||||
|
|
||||||
|
results = [] # [(站点, 流程, 状态, 耗时秒, 错误信息)]
|
||||||
|
for idx, (site_name, label, func) in enumerate(plan, start=1):
|
||||||
|
print("\n----------------------------------------------------")
|
||||||
|
print(f"[步骤 {idx}/{total}] 站点【{site_name}】流程【{label}】")
|
||||||
|
print("----------------------------------------------------")
|
||||||
|
page = pages_map[site_name]
|
||||||
|
start = time.time()
|
||||||
|
status = "PASS"
|
||||||
|
err = ""
|
||||||
|
try:
|
||||||
|
page.bring_to_front()
|
||||||
|
ret = func(page)
|
||||||
|
if ret is False:
|
||||||
|
status = "FAIL"
|
||||||
|
err = "流程返回失败状态"
|
||||||
|
except Exception as e:
|
||||||
|
status = "FAIL"
|
||||||
|
err = str(e)
|
||||||
|
elapsed = time.time() - start
|
||||||
|
results.append((site_name, label, status, elapsed, err))
|
||||||
|
print(f">> 步骤结果: {status} (耗时 {elapsed:.1f}s)")
|
||||||
|
|
||||||
|
_print_test_report(results)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_test_report(results):
|
||||||
|
"""打印自动化测试统计报告。"""
|
||||||
|
passed = sum(1 for r in results if r[2] == "PASS")
|
||||||
|
failed = len(results) - passed
|
||||||
|
|
||||||
|
print("\n====================================================")
|
||||||
|
print("自动化测试统计报告")
|
||||||
|
print("====================================================")
|
||||||
|
for i, (site_name, label, status, elapsed, err) in enumerate(results, start=1):
|
||||||
|
mark = "✅" if status == "PASS" else "❌"
|
||||||
|
print(f" {i:>2}. {mark} {status} {site_name} - {label} (耗时 {elapsed:.1f}s)")
|
||||||
|
if err:
|
||||||
|
note = err if len(err) <= 60 else err[:57] + "..."
|
||||||
|
print(f" 说明: {note}")
|
||||||
|
print("----------------------------------------------------")
|
||||||
|
print(f" 合计 {len(results)} 步:通过 {passed},失败 {failed}")
|
||||||
|
if failed == 0:
|
||||||
|
print(" ✅ 全部流程跑通。")
|
||||||
|
else:
|
||||||
|
print(" ❌ 存在失败流程,请结合上方说明与运行日志排查。")
|
||||||
|
print("====================================================")
|
||||||
|
|
||||||
|
|
||||||
def run_multi_site_daemon():
|
def run_multi_site_daemon():
|
||||||
"""多网点自动化主控引擎 (状态机卫语句驱动版)"""
|
"""多站点自动化主控流程"""
|
||||||
|
|
||||||
# 1. 读取配置文件
|
# 1. 读取配置文件
|
||||||
debug_mode = False
|
debug_mode = False
|
||||||
@@ -108,7 +209,7 @@ def run_multi_site_daemon():
|
|||||||
# 动态确定需要挂载启动的网页
|
# 动态确定需要挂载启动的网页
|
||||||
active_sites = {}
|
active_sites = {}
|
||||||
if debug_mode and debug_target in SITES_CONFIG:
|
if debug_mode and debug_target in SITES_CONFIG:
|
||||||
print(f"\n🛠️ 【调试模式激活】当前中控台仅挂载并开启目标站点: [{debug_target}]")
|
print(f"\n🛠️ 【调试模式】仅加载目标站点: [{debug_target}]")
|
||||||
active_sites = {debug_target: SITES_CONFIG[debug_target]}
|
active_sites = {debug_target: SITES_CONFIG[debug_target]}
|
||||||
else:
|
else:
|
||||||
active_sites = SITES_CONFIG
|
active_sites = SITES_CONFIG
|
||||||
@@ -120,7 +221,7 @@ def run_multi_site_daemon():
|
|||||||
pages_map = {}
|
pages_map = {}
|
||||||
|
|
||||||
print("\n====================================================")
|
print("\n====================================================")
|
||||||
print("【唤醒阶段】正在构建多网点并行运行环境...")
|
print("【启动】正在打开各站点页面...")
|
||||||
print("====================================================")
|
print("====================================================")
|
||||||
|
|
||||||
for site_name, url in active_sites.items():
|
for site_name, url in active_sites.items():
|
||||||
@@ -130,7 +231,7 @@ def run_multi_site_daemon():
|
|||||||
pages_map[site_name] = page
|
pages_map[site_name] = page
|
||||||
|
|
||||||
print("\n====================================================")
|
print("\n====================================================")
|
||||||
print("【状态自检】启动前置智能接管机制...")
|
print("【登录检测】正在准备各站点登录...")
|
||||||
print("====================================================")
|
print("====================================================")
|
||||||
|
|
||||||
# 针对支持纯代码自动登录的站点,在此处前置注入登录事件
|
# 针对支持纯代码自动登录的站点,在此处前置注入登录事件
|
||||||
@@ -142,37 +243,37 @@ def run_multi_site_daemon():
|
|||||||
print(f" ⚠️ 韵达前置自动登录模块发生波动: {e}")
|
print(f" ⚠️ 韵达前置自动登录模块发生波动: {e}")
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 就绪卫语句轮询器 (Ready Guard Polling)
|
# 就绪轮询 (Ready Guard Polling)
|
||||||
# 无需死板的输入回车,全自动识别状态并无缝放行
|
# 自动识别各站点登录完成状态,无需手动回车
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
ready_status = {site: False for site in active_sites.keys()}
|
ready_status = {site: False for site in active_sites.keys()}
|
||||||
|
|
||||||
print("\n>> 正在静默轮询全网点就绪状态 (自动免密/手工登录均可激活)...")
|
print("\n>> 正在轮询各站点就绪状态 (自动登录或手动登录均可)...")
|
||||||
while not all(ready_status.values()):
|
while not all(ready_status.values()):
|
||||||
for site_name, page in pages_map.items():
|
for site_name, page in pages_map.items():
|
||||||
if not ready_status[site_name]:
|
if not ready_status[site_name]:
|
||||||
try:
|
try:
|
||||||
# 0.5秒轻量级探针,避免阻塞主循环
|
# 0.5 秒轻量探测,避免阻塞主循环
|
||||||
if page.locator(READY_SELECTORS[site_name]).is_visible(
|
if page.locator(READY_SELECTORS[site_name]).is_visible(
|
||||||
timeout=500
|
timeout=500
|
||||||
):
|
):
|
||||||
ready_status[site_name] = True
|
ready_status[site_name] = True
|
||||||
print(f" 🎉 【{site_name}】探测到主页特征,状态已就绪!")
|
print(f" ✅ 【{site_name}】已检测到主页,登录就绪。")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pending_sites = [s for s, ready in ready_status.items() if not ready]
|
pending_sites = [s for s, ready in ready_status.items() if not ready]
|
||||||
if pending_sites:
|
if pending_sites:
|
||||||
print(
|
print(
|
||||||
f" ⏳ 等待以下网点登录验证: [{', '.join(pending_sites)}] ... (请在浏览器中操作)"
|
f" ⏳ 等待以下站点完成登录: [{', '.join(pending_sites)}] ... (请在浏览器中操作)"
|
||||||
)
|
)
|
||||||
page.wait_for_timeout(3000) # 挂起 3 秒后执行下一轮盘点
|
page.wait_for_timeout(3000) # 等待 3 秒后进行下一轮检查
|
||||||
|
|
||||||
print("\n====================================================")
|
print("\n====================================================")
|
||||||
print("【接管阶段】所有活跃网点均已就绪,正在执行环境净化...")
|
print("【准备】所有站点已就绪,正在清理初始弹窗...")
|
||||||
print("====================================================")
|
print("====================================================")
|
||||||
|
|
||||||
# 顺心环境净化
|
# 顺心:处理初始弹窗
|
||||||
if "顺心" in pages_map:
|
if "顺心" in pages_map:
|
||||||
try:
|
try:
|
||||||
sx_page = pages_map["顺心"]
|
sx_page = pages_map["顺心"]
|
||||||
@@ -183,11 +284,11 @@ def run_multi_site_daemon():
|
|||||||
sx_page.get_by_role("button", name="Close").click(timeout=2000)
|
sx_page.get_by_role("button", name="Close").click(timeout=2000)
|
||||||
sx_page.wait_for_timeout(500)
|
sx_page.wait_for_timeout(500)
|
||||||
sx_page.get_by_role("button", name="不再询问").click(timeout=2000)
|
sx_page.get_by_role("button", name="不再询问").click(timeout=2000)
|
||||||
print(" ✅ 【顺心】环境准备就绪!")
|
print(" ✅ 【顺心】初始弹窗处理完成。")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # 环境可能很干净没有弹窗,无视报错
|
pass # 环境可能很干净没有弹窗,无视报错
|
||||||
|
|
||||||
# 百世环境打地鼠
|
# 百世:循环关闭初始弹窗
|
||||||
if "百世" in pages_map:
|
if "百世" in pages_map:
|
||||||
try:
|
try:
|
||||||
bs_page = pages_map["百世"]
|
bs_page = pages_map["百世"]
|
||||||
@@ -215,27 +316,27 @@ def run_multi_site_daemon():
|
|||||||
if not handled_any:
|
if not handled_any:
|
||||||
break
|
break
|
||||||
bs_page.wait_for_timeout(800)
|
bs_page.wait_for_timeout(800)
|
||||||
print(" ✅ 【百世】界面净化完毕!")
|
print(" ✅ 【百世】初始弹窗处理完成。")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "中通" in pages_map:
|
if "中通" in pages_map:
|
||||||
print(" ✅ 【中通】状态就绪!")
|
print(" ✅ 【中通】已就绪。")
|
||||||
|
|
||||||
if "韵达" in pages_map:
|
if "韵达" in pages_map:
|
||||||
print(" ✅ 【韵达】工作区状态就绪!")
|
print(" ✅ 【韵达】已就绪。")
|
||||||
|
|
||||||
def is_site_ready(site_name):
|
def is_site_ready(site_name):
|
||||||
if site_name not in pages_map:
|
if site_name not in pages_map:
|
||||||
print(f"\n🚫 【安全拦截】当前处于局部调试,[{site_name}] 未挂载加载!")
|
print(f"\n🚫 站点 [{site_name}] 未加载(当前为调试模式),已跳过。")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
print("\n====================================================")
|
print("\n====================================================")
|
||||||
print(" 物流数据多端提取总枢纽 ")
|
print(" 物流数据下载主菜单 ")
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
print(f" [ 🛠️ 调试模式独立聚焦 : {debug_target} ]")
|
print(f" [ 调试模式,仅加载: {debug_target} ]")
|
||||||
print("====================================================")
|
print("====================================================")
|
||||||
print(" 模块一:【顺心】数据处理流")
|
print(" 模块一:【顺心】数据处理流")
|
||||||
print(" [1] 执行 - 应到货物数据下载")
|
print(" [1] 执行 - 应到货物数据下载")
|
||||||
@@ -252,7 +353,10 @@ def run_multi_site_daemon():
|
|||||||
print(" [6] 执行 - 应到货物数据下载")
|
print(" [6] 执行 - 应到货物数据下载")
|
||||||
print(" [7] 执行 - 实到货物数据下载")
|
print(" [7] 执行 - 实到货物数据下载")
|
||||||
print("-" * 52)
|
print("-" * 52)
|
||||||
print(" 全局离线数据引擎")
|
print(" 自动化测试")
|
||||||
|
print(" [8] 执行 - 全站点下载流程自动化测试 (交叉跑通校验)")
|
||||||
|
print("-" * 52)
|
||||||
|
print(" 全局离线数据处理")
|
||||||
print(" [9] 执行 - 异常数据清洗比对 (Left Anti-Join)")
|
print(" [9] 执行 - 异常数据清洗比对 (Left Anti-Join)")
|
||||||
print("-" * 52)
|
print("-" * 52)
|
||||||
print(" [0] 退出系统")
|
print(" [0] 退出系统")
|
||||||
@@ -282,6 +386,8 @@ def run_multi_site_daemon():
|
|||||||
elif choice == "7" and is_site_ready("韵达"):
|
elif choice == "7" and is_site_ready("韵达"):
|
||||||
pages_map["韵达"].bring_to_front()
|
pages_map["韵达"].bring_to_front()
|
||||||
site_yunda.yunda_actual_download(pages_map["韵达"])
|
site_yunda.yunda_actual_download(pages_map["韵达"])
|
||||||
|
elif choice == "8":
|
||||||
|
run_automation_test(pages_map)
|
||||||
elif choice == "9":
|
elif choice == "9":
|
||||||
site_name = input(
|
site_name = input(
|
||||||
"请输入要比对的网点名称 (如 顺心/中通/韵达): "
|
"请输入要比对的网点名称 (如 顺心/中通/韵达): "
|
||||||
@@ -290,16 +396,16 @@ def run_multi_site_daemon():
|
|||||||
site_name = "顺心"
|
site_name = "顺心"
|
||||||
task_process_undelivered_data(site_name)
|
task_process_undelivered_data(site_name)
|
||||||
elif choice == "0":
|
elif choice == "0":
|
||||||
print("\n正在释放浏览器并安全登出系统...")
|
print("\n正在关闭浏览器并退出...")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if choice not in ["1", "2", "3", "4", "5", "6", "7", "9", "0"]:
|
if choice not in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]:
|
||||||
print("\n⚠️ 无效输入,请查证后回车。")
|
print("\n⚠️ 无效输入,请查证后回车。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 调度枢纽异常: {e}")
|
print(f"❌ 任务调度异常: {e}")
|
||||||
|
|
||||||
browser.close()
|
browser.close()
|
||||||
print("中控守护进程安全退出。")
|
print("程序已退出。")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -21,19 +21,19 @@ def baishi_download_undelivered_data(page):
|
|||||||
# 打开基础服务菜单面板
|
# 打开基础服务菜单面板
|
||||||
page.locator("div.nav-level1", has_text="基础服务").click()
|
page.locator("div.nav-level1", has_text="基础服务").click()
|
||||||
|
|
||||||
# 【重要修复】:通过限制在菜单面板(nav-level2-wrapper)内查找,
|
# 限制在菜单面板(nav-level2-wrapper)内查找,
|
||||||
# 完美避开页面右侧同名 Tab 标签页的干扰
|
# 避免命中右侧同名标签页
|
||||||
page.locator(".nav-level2-wrapper").locator(
|
page.locator(".nav-level2-wrapper").locator(
|
||||||
"a", has_text="扫描综合查询"
|
"a", has_text="扫描综合查询"
|
||||||
).click()
|
).click()
|
||||||
|
|
||||||
# 验证表格主界面加载完毕
|
# 验证表格主界面加载完毕
|
||||||
page.get_by_role("tab", name="实时扫描率").wait_for(state="visible")
|
page.get_by_role("tab", name="实时扫描率").wait_for(state="visible")
|
||||||
print("✅ 扫描综合查询界面加载完毕")
|
print("✅ 扫描综合查询界面已加载")
|
||||||
|
|
||||||
# 2. 精准定位并点击【到/接件扫描 -> 当日 -> 未扫】的数字控件
|
# 2. 精准定位并点击【到/接件扫描 -> 当日 -> 未扫】的数字控件
|
||||||
print(">> 正在解析表格,提取当日到件未扫明细...")
|
print(">> 正在解析表格,提取当日到件未扫明细...")
|
||||||
# 表头结构复杂,精准定位第一行数据的第 13 列(索引 12)
|
# 定位第一行数据的第 13 列(索引 12)
|
||||||
target_cell = page.locator(".ant-table-tbody > tr").first.locator("td").nth(12)
|
target_cell = page.locator(".ant-table-tbody > tr").first.locator("td").nth(12)
|
||||||
|
|
||||||
# 特殊情况处理:检查未扫数量是否为0
|
# 特殊情况处理:检查未扫数量是否为0
|
||||||
@@ -51,15 +51,15 @@ def baishi_download_undelivered_data(page):
|
|||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
|
|
||||||
# 3. 触发导出设置模态框
|
# 3. 触发导出设置模态框
|
||||||
print(">> 正在呼出导出配置面板...")
|
print(">> 正在打开导出配置面板...")
|
||||||
# 锁定在明细区域内的导出按钮,防止误点主表的导出
|
# 使用明细区域内的导出按钮,避免误点主表导出
|
||||||
detail_section.locator(".export-wrap a[title='导出']").click()
|
detail_section.locator(".export-wrap a[title='导出']").click()
|
||||||
|
|
||||||
# 验证模态框弹出
|
# 验证模态框弹出
|
||||||
page.locator(".ant-modal-title", has_text="导出设置").wait_for(state="visible")
|
page.locator(".ant-modal-title", has_text="导出设置").wait_for(state="visible")
|
||||||
|
|
||||||
# 4. 加载 YAML 配置并输入密码
|
# 4. 加载 YAML 配置并输入密码
|
||||||
print(">> 正在读取本地配置文件并进行授权验证...")
|
print(">> 正在读取配置并填写密码...")
|
||||||
password = ""
|
password = ""
|
||||||
try:
|
try:
|
||||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||||
@@ -72,14 +72,14 @@ def baishi_download_undelivered_data(page):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 读取 config.yaml 失败,请确保文件存在且格式正确: {e}")
|
print(f" ⚠️ 读取 config.yaml 失败,请确保文件存在且格式正确: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
page.get_by_placeholder("请输入登录密码").fill(password)
|
page.get_by_placeholder("请输入登录密码").fill(password)
|
||||||
|
|
||||||
# 5. 执行最终下载
|
# 5. 执行最终下载
|
||||||
print(">> 验证就绪,正在触发下载流...")
|
print(">> 正在下载...")
|
||||||
with page.expect_download() as download_info:
|
with page.expect_download() as download_info:
|
||||||
# 精准锁定模态框底部的“导 出”大按钮
|
# 点击模态框底部的“导 出”按钮
|
||||||
page.locator(".ant-modal-footer").get_by_role(
|
page.locator(".ant-modal-footer").get_by_role(
|
||||||
"button", name="导 出"
|
"button", name="导 出"
|
||||||
).click()
|
).click()
|
||||||
@@ -93,11 +93,12 @@ def baishi_download_undelivered_data(page):
|
|||||||
|
|
||||||
download.save_as(save_path)
|
download.save_as(save_path)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 恭喜!数据提取成功!")
|
print(f" 提取成功。")
|
||||||
print(f" ⬇️ 文件已落盘: {save_path}")
|
print(f" 已下载: {save_path}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
print("\n🎉 【百世 - 应到未到数据提取】全流程测试完毕!")
|
print("\n【百世 - 应到未到数据提取】流程结束。")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ def shunxin_expected_download(page):
|
|||||||
download_dir = DOWNLOAD_DIR
|
download_dir = DOWNLOAD_DIR
|
||||||
if not os.path.exists(download_dir):
|
if not os.path.exists(download_dir):
|
||||||
os.makedirs(download_dir)
|
os.makedirs(download_dir)
|
||||||
print(f">> 已创建专属下载文件夹: {download_dir}")
|
print(f">> 已创建下载目录: {download_dir}")
|
||||||
|
|
||||||
export_times = []
|
export_times = []
|
||||||
target_task_timestamps = []
|
target_task_timestamps = []
|
||||||
@@ -124,7 +124,7 @@ def shunxin_expected_download(page):
|
|||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
|
|
||||||
# 7. 轮询任务状态
|
# 7. 轮询任务状态
|
||||||
print(">> 列表中已渲染,开始匹配并检查后端处理状态...")
|
print(">> 列表已加载,开始匹配并检查任务状态...")
|
||||||
while True:
|
while True:
|
||||||
rows = page.locator(".ant-table-tbody > tr.ant-table-row")
|
rows = page.locator(".ant-table-tbody > tr.ant-table-row")
|
||||||
row_count = rows.count()
|
row_count = rows.count()
|
||||||
@@ -173,7 +173,7 @@ def shunxin_expected_download(page):
|
|||||||
else:
|
else:
|
||||||
target_task_timestamps = current_ready_timestamps
|
target_task_timestamps = current_ready_timestamps
|
||||||
if len(target_task_timestamps) > 0:
|
if len(target_task_timestamps) > 0:
|
||||||
print(">> ✅ 所有目标任务已就绪!开始并行下载...")
|
print(">> 所有目标任务已就绪,开始下载...")
|
||||||
break
|
break
|
||||||
|
|
||||||
# 8. 下载逻辑
|
# 8. 下载逻辑
|
||||||
@@ -183,7 +183,7 @@ def shunxin_expected_download(page):
|
|||||||
target_row = page.locator(".ant-table-tbody > tr.ant-table-row").filter(
|
target_row = page.locator(".ant-table-tbody > tr.ant-table-row").filter(
|
||||||
has=page.locator(f"td:nth-child(3):has-text('{time_str}')")
|
has=page.locator(f"td:nth-child(3):has-text('{time_str}')")
|
||||||
)
|
)
|
||||||
print(f" 🎯 触发下载 -> 任务 [{time_str}] ...")
|
print(f" 开始下载任务 [{time_str}] ...")
|
||||||
|
|
||||||
with page.expect_download() as download_info:
|
with page.expect_download() as download_info:
|
||||||
target_row.locator("td").nth(8).locator(
|
target_row.locator("td").nth(8).locator(
|
||||||
@@ -197,13 +197,13 @@ def shunxin_expected_download(page):
|
|||||||
save_path = os.path.join(download_dir, custom_filename)
|
save_path = os.path.join(download_dir, custom_filename)
|
||||||
download.save_as(save_path)
|
download.save_as(save_path)
|
||||||
downloaded_files.append(save_path)
|
downloaded_files.append(save_path)
|
||||||
print(f" ⬇️ 文件已落盘: downloads/{custom_filename}")
|
print(f" 已下载: downloads/{custom_filename}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
||||||
|
|
||||||
# 9. 合并数据 (修改了文件名前缀)
|
# 9. 合并数据 (修改了文件名前缀)
|
||||||
if downloaded_files:
|
if downloaded_files:
|
||||||
print("\n>> 🧪 正在开始执行扁平数据高能合并流程...")
|
print("\n>> 正在合并下载的数据...")
|
||||||
all_data_frames = []
|
all_data_frames = []
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
try:
|
try:
|
||||||
@@ -218,20 +218,21 @@ def shunxin_expected_download(page):
|
|||||||
final_output_path = os.path.join(download_dir, "顺心-应到货物数据.xlsx")
|
final_output_path = os.path.join(download_dir, "顺心-应到货物数据.xlsx")
|
||||||
combined_df.to_excel(final_output_path, index=False)
|
combined_df.to_excel(final_output_path, index=False)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 恭喜!合并成功!最终输出路径: {final_output_path}")
|
print(f" 合并完成,输出文件: {final_output_path}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print("✅ 临时数据清理完毕。")
|
print("✅ 临时文件已清理。")
|
||||||
|
|
||||||
# 10. 关闭【数据导出】标签页(该页面工作已完成)
|
# 10. 关闭【数据导出】标签页(该页面工作已完成)
|
||||||
_close_tab(page, "数据导出")
|
_close_tab(page, "数据导出")
|
||||||
|
|
||||||
print("\n🎉 【顺心 - 应到货物数据下载】全流程测试完毕!")
|
print("\n【顺心 - 应到货物数据下载】流程结束。")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def shunxin_actual_download(page):
|
def shunxin_actual_download(page):
|
||||||
@@ -296,7 +297,7 @@ def shunxin_actual_download(page):
|
|||||||
page.wait_for_timeout(2000)
|
page.wait_for_timeout(2000)
|
||||||
|
|
||||||
# 6. 轮询任务状态
|
# 6. 轮询任务状态
|
||||||
print(">> 列表中已渲染,开始匹配并检查后端处理状态...")
|
print(">> 列表已加载,开始匹配并检查任务状态...")
|
||||||
while True:
|
while True:
|
||||||
rows = page.locator(".ant-table-tbody > tr.ant-table-row")
|
rows = page.locator(".ant-table-tbody > tr.ant-table-row")
|
||||||
row_count = rows.count()
|
row_count = rows.count()
|
||||||
@@ -345,7 +346,7 @@ def shunxin_actual_download(page):
|
|||||||
else:
|
else:
|
||||||
target_task_timestamps = current_ready_timestamps
|
target_task_timestamps = current_ready_timestamps
|
||||||
if len(target_task_timestamps) > 0:
|
if len(target_task_timestamps) > 0:
|
||||||
print(">> ✅ 目标任务已就绪!开始下载...")
|
print(">> 目标任务已就绪,开始下载...")
|
||||||
break
|
break
|
||||||
|
|
||||||
# 7. 下载逻辑
|
# 7. 下载逻辑
|
||||||
@@ -355,7 +356,7 @@ def shunxin_actual_download(page):
|
|||||||
target_row = page.locator(".ant-table-tbody > tr.ant-table-row").filter(
|
target_row = page.locator(".ant-table-tbody > tr.ant-table-row").filter(
|
||||||
has=page.locator(f"td:nth-child(3):has-text('{time_str}')")
|
has=page.locator(f"td:nth-child(3):has-text('{time_str}')")
|
||||||
)
|
)
|
||||||
print(f" 🎯 触发下载 -> 任务 [{time_str}] ...")
|
print(f" 开始下载任务 [{time_str}] ...")
|
||||||
|
|
||||||
with page.expect_download() as download_info:
|
with page.expect_download() as download_info:
|
||||||
target_row.locator("td").nth(8).locator(
|
target_row.locator("td").nth(8).locator(
|
||||||
@@ -369,13 +370,13 @@ def shunxin_actual_download(page):
|
|||||||
save_path = os.path.join(download_dir, custom_filename)
|
save_path = os.path.join(download_dir, custom_filename)
|
||||||
download.save_as(save_path)
|
download.save_as(save_path)
|
||||||
downloaded_files.append(save_path)
|
downloaded_files.append(save_path)
|
||||||
print(f" ⬇️ 文件已落盘: downloads/{custom_filename}")
|
print(f" 已下载: downloads/{custom_filename}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
||||||
|
|
||||||
# 8. 合并数据 (修改了文件名前缀)
|
# 8. 合并数据 (修改了文件名前缀)
|
||||||
if downloaded_files:
|
if downloaded_files:
|
||||||
print("\n>> 🧪 正在开始执行数据归档整理...")
|
print("\n>> 正在合并下载的数据...")
|
||||||
all_data_frames = []
|
all_data_frames = []
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
try:
|
try:
|
||||||
@@ -390,17 +391,18 @@ def shunxin_actual_download(page):
|
|||||||
final_output_path = os.path.join(download_dir, "顺心-实到货物数据.xlsx")
|
final_output_path = os.path.join(download_dir, "顺心-实到货物数据.xlsx")
|
||||||
combined_df.to_excel(final_output_path, index=False)
|
combined_df.to_excel(final_output_path, index=False)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 恭喜!处理成功!最终输出路径: {final_output_path}")
|
print(f" 合并完成,输出文件: {final_output_path}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print("✅ 临时数据清理完毕。")
|
print("✅ 临时文件已清理。")
|
||||||
|
|
||||||
# 9. 关闭【数据导出】标签页(该页面工作已完成)
|
# 9. 关闭【数据导出】标签页(该页面工作已完成)
|
||||||
_close_tab(page, "数据导出")
|
_close_tab(page, "数据导出")
|
||||||
|
|
||||||
print("\n🎉 【顺心 - 实到货物数据下载】全流程测试完毕!")
|
print("\n【顺心 - 实到货物数据下载】流程结束。")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|||||||
146
site_yunda.py
146
site_yunda.py
@@ -10,18 +10,15 @@ from paths import DOWNLOAD_DIR, CONFIG_PATH
|
|||||||
|
|
||||||
|
|
||||||
def yunda_login(page):
|
def yunda_login(page):
|
||||||
"""
|
"""韵达自动登录:未登录则填充表单并提交,已登录则跳过。"""
|
||||||
韵达大运系统智能自动登录流
|
print(">> 正在检查韵达登录状态...")
|
||||||
支持‘未登录自动填充提交’与‘已登录静默过客’双工模式
|
|
||||||
"""
|
|
||||||
print(">> 正在探测韵达当前的登录会话状态...")
|
|
||||||
try:
|
try:
|
||||||
# 定位“账号密码登录”切换按钮
|
# 定位“账号密码登录”切换按钮
|
||||||
switch_btn = page.locator("span", has_text="账号密码登录")
|
switch_btn = page.locator("span", has_text="账号密码登录")
|
||||||
|
|
||||||
# 设定 5 秒探针延迟。如果 5 秒内按钮可见,说明处于未登录的初始状态
|
# 5 秒内若出现该按钮,说明当前未登录
|
||||||
if switch_btn.is_visible(timeout=5000):
|
if switch_btn.is_visible(timeout=5000):
|
||||||
print(" -> 捕获到标准的未登录界面,正在强切至【账号密码登录】模式...")
|
print(" -> 检测到未登录界面,正在切换到【账号密码登录】...")
|
||||||
switch_btn.click()
|
switch_btn.click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ def yunda_login(page):
|
|||||||
username = str(yd_cfg.get("username", ""))
|
username = str(yd_cfg.get("username", ""))
|
||||||
password = str(yd_cfg.get("password", ""))
|
password = str(yd_cfg.get("password", ""))
|
||||||
|
|
||||||
print(f" -> 正在程序化充填表单凭证 (账号: {username})...")
|
print(f" -> 正在填充登录表单 (账号: {username})...")
|
||||||
page.locator("#username").fill(username)
|
page.locator("#username").fill(username)
|
||||||
page.locator("#password").fill(password)
|
page.locator("#password").fill(password)
|
||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
@@ -45,18 +42,15 @@ def yunda_login(page):
|
|||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
" -> 探针未发现登录按钮,判定当前会话已处于持久化登录状态,静默穿透。"
|
" -> 未发现登录按钮,判定为已登录,跳过。"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 自动登录状态机探测发生波动(可能已处于工作台内部): {e}")
|
print(f" ⚠️ 登录检测出错(可能已在工作台内): {e}")
|
||||||
|
|
||||||
|
|
||||||
def yunda_smart_menu_click(page, menu_path):
|
def yunda_smart_menu_click(page, menu_path):
|
||||||
"""
|
"""韵达多级菜单导航:展开父级菜单并点击目标项(已展开则跳过,避免误折叠)。"""
|
||||||
韵达排他性多级树形菜单智能导航器(带防折叠状态断言机制)
|
print(f">> 导航韵达菜单: {' -> '.join(menu_path)}")
|
||||||
采用 XPath 亲子轴绝对隔离技术,彻底斩断嵌套手风琴菜单的向下漏包干扰
|
|
||||||
"""
|
|
||||||
print(f">> 正在智能路由韵达菜单: {' -> '.join(menu_path)}")
|
|
||||||
|
|
||||||
for item in menu_path:
|
for item in menu_path:
|
||||||
title_locator = page.locator(
|
title_locator = page.locator(
|
||||||
@@ -74,16 +68,16 @@ def yunda_smart_menu_click(page, menu_path):
|
|||||||
is_opened = "is-opened" in current_class
|
is_opened = "is-opened" in current_class
|
||||||
|
|
||||||
if not is_opened:
|
if not is_opened:
|
||||||
print(f" -> 探测到父菜单 [{item}] 当前处于【收起】状态,执行点击展开")
|
print(f" -> 父菜单 [{item}] 处于收起状态,点击展开")
|
||||||
title_locator.click()
|
title_locator.click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f" -> 探测到父菜单 [{item}] 当前已经处于【展开】状态,安全跳过点击(防折叠保护激活)"
|
f" -> 父菜单 [{item}] 已展开,跳过点击"
|
||||||
)
|
)
|
||||||
|
|
||||||
elif leaf_locator.is_visible():
|
elif leaf_locator.is_visible():
|
||||||
print(f" -> 成功对焦目标叶子节点 [{item}],直接执行跳转点击")
|
print(f" -> 点击菜单项 [{item}]")
|
||||||
leaf_locator.click()
|
leaf_locator.click()
|
||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
|
|
||||||
@@ -100,20 +94,20 @@ def yunda_expected_download(page):
|
|||||||
export_times = []
|
export_times = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 验证首页并智能路由
|
# 1. 验证首页并导航菜单
|
||||||
page.locator(".el-menu-item", has_text="首页").wait_for(
|
page.locator(".el-menu-item", has_text="首页").wait_for(
|
||||||
state="visible", timeout=15000
|
state="visible", timeout=15000
|
||||||
)
|
)
|
||||||
print("✅ 韵达工作台首页成功加载")
|
print("✅ 韵达工作台首页已加载")
|
||||||
|
|
||||||
yunda_smart_menu_click(page, ["运营管理", "进站管理", "进站交接单查询"])
|
yunda_smart_menu_click(page, ["运营管理", "进站管理", "进站交接单查询"])
|
||||||
|
|
||||||
print(">> 正在跨域动态追踪【进站交接单查询】业务窗体...")
|
print(">> 正在定位【进站交接单查询】iframe...")
|
||||||
# 彻底移除会引起竞速超时坑的 custom 探测器,使用原生懒加载 frame_locator
|
# 使用 frame_locator 定位业务 iframe
|
||||||
ws_frame = page.frame_locator("section iframe")
|
ws_frame = page.frame_locator("section iframe")
|
||||||
|
|
||||||
ws_frame.locator("#startTime").wait_for(state="attached", timeout=15000)
|
ws_frame.locator("#startTime").wait_for(state="attached", timeout=15000)
|
||||||
print("✅ 进站交接单查询工作区就绪")
|
print("✅ 进站交接单查询页面就绪")
|
||||||
|
|
||||||
# 2. 从配置文件中解析并计算绝对日期跨度
|
# 2. 从配置文件中解析并计算绝对日期跨度
|
||||||
query_days = 1
|
query_days = 1
|
||||||
@@ -131,10 +125,10 @@ def yunda_expected_download(page):
|
|||||||
today_ymd = f"{today.year}-{today.month}-{today.day}"
|
today_ymd = f"{today.year}-{today.month}-{today.day}"
|
||||||
start_date_ymd = f"{start_date.year}-{start_date.month}-{start_date.day}"
|
start_date_ymd = f"{start_date.year}-{start_date.month}-{start_date.day}"
|
||||||
|
|
||||||
print(f">> 正在精准对焦时间区间: [{start_date_ymd}] 至 [{today_ymd}]")
|
print(f">> 设置查询时间范围: [{start_date_ymd}] 至 [{today_ymd}]")
|
||||||
|
|
||||||
# 设定起始时间
|
# 设定起始时间
|
||||||
print(" >> 呼出起始时间控件...")
|
print(" >> 设置起始时间...")
|
||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
ws_frame.locator("#startTime").click(force=True)
|
ws_frame.locator("#startTime").click(force=True)
|
||||||
|
|
||||||
@@ -145,7 +139,7 @@ def yunda_expected_download(page):
|
|||||||
page.wait_for_timeout(400)
|
page.wait_for_timeout(400)
|
||||||
|
|
||||||
# 设定截止时间
|
# 设定截止时间
|
||||||
print(" >> 呼出截止时间控件...")
|
print(" >> 设置截止时间...")
|
||||||
ws_frame.locator("#endTime").click(force=True)
|
ws_frame.locator("#endTime").click(force=True)
|
||||||
|
|
||||||
calendar2 = ws_frame.locator(".layui-laydate:visible").first
|
calendar2 = ws_frame.locator(".layui-laydate:visible").first
|
||||||
@@ -154,21 +148,21 @@ def yunda_expected_download(page):
|
|||||||
calendar2.locator(".laydate-btns-confirm").click()
|
calendar2.locator(".laydate-btns-confirm").click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
# 3. 多维状态机静默加载校验
|
# 3. 等待数据加载完成
|
||||||
print(">> 正在触发现场查询数据流...")
|
print(">> 正在执行查询...")
|
||||||
ws_frame.locator("a.btn-success", has_text="查询").click()
|
ws_frame.locator("a.btn-success", has_text="查询").click()
|
||||||
|
|
||||||
page.wait_for_timeout(800)
|
page.wait_for_timeout(800)
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 核心修复:将 Loading 蒙层和数据断言限制在当前的 #tab-1 结界内部
|
# 将 Loading 蒙层与数据判断限制在 #tab-1 内
|
||||||
# 彻底解决多标签页导致 Strict Mode (5 elements found) 的污染问题
|
# 避免多标签页命中多个元素 (Strict Mode)
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
loading_mask = ws_frame.locator(
|
loading_mask = ws_frame.locator(
|
||||||
"#tab-1 .fixed-table-loading", has_text="正在努力地加载数据中"
|
"#tab-1 .fixed-table-loading", has_text="正在努力地加载数据中"
|
||||||
).first
|
).first
|
||||||
if loading_mask.is_visible():
|
if loading_mask.is_visible():
|
||||||
print(" ⏳ 检测到专属数据加载罩,正在等待后端重载返回...")
|
print(" ⏳ 检测到数据加载遮罩,等待加载完成...")
|
||||||
loading_mask.wait_for(state="hidden", timeout=30000)
|
loading_mask.wait_for(state="hidden", timeout=30000)
|
||||||
|
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
@@ -182,16 +176,16 @@ def yunda_expected_download(page):
|
|||||||
if match_tickets and int(match_tickets.group(1)) > 0:
|
if match_tickets and int(match_tickets.group(1)) > 0:
|
||||||
has_data = True
|
has_data = True
|
||||||
print(
|
print(
|
||||||
f" ✅ 深度断言:局部统计面板加载完毕,实际票数: [{match_tickets.group(1)}],执行穿透。"
|
f" ✅ 统计面板已加载,实际票数: [{match_tickets.group(1)}]"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not has_data:
|
if not has_data:
|
||||||
# 同样将空数据提示隔离在当前 tab 中
|
# 空数据提示同样限制在当前 tab 内
|
||||||
if ws_frame.locator(
|
if ws_frame.locator(
|
||||||
"#tab-1 .no-records-found", has_text="没有找到匹配的记录"
|
"#tab-1 .no-records-found", has_text="没有找到匹配的记录"
|
||||||
).first.is_visible():
|
).first.is_visible():
|
||||||
print(
|
print(
|
||||||
" ⚠️ 确认为空数据环境:系统提示【没有找到匹配的记录】,正在执行复位净化..."
|
" ⚠️ 确认为空数据,正在关闭当前标签页..."
|
||||||
)
|
)
|
||||||
page.locator(".tags-view-item", has_text="进站交接单查询").locator(
|
page.locator(".tags-view-item", has_text="进站交接单查询").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
@@ -207,20 +201,20 @@ def yunda_expected_download(page):
|
|||||||
row_count = main_rows.count()
|
row_count = main_rows.count()
|
||||||
print(f">> 当前视窗共捕获到活跃交接单记录: {row_count} 条")
|
print(f">> 当前视窗共捕获到活跃交接单记录: {row_count} 条")
|
||||||
|
|
||||||
# 5. 循环双击穿透提交
|
# 5. 逐行双击并提交导出
|
||||||
for i in range(row_count):
|
for i in range(row_count):
|
||||||
print(f" ⏳ 正在处理第 {i+1}/{row_count} 个交接单模块...")
|
print(f" ⏳ 正在处理第 {i+1}/{row_count} 个交接单模块...")
|
||||||
current_row = ws_frame.locator("#exampleTable1 tbody tr[data-index]").nth(i)
|
current_row = ws_frame.locator("#exampleTable1 tbody tr[data-index]").nth(i)
|
||||||
|
|
||||||
raw_no = current_row.locator("td").nth(1).inner_text().strip()
|
raw_no = current_row.locator("td").nth(1).inner_text().strip()
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 状态机拦截卫语句:识别绑定状态,过滤已绑定交接单
|
# 跳过已绑定的交接单
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
bind_status = current_row.locator("td").nth(2).inner_text().strip()
|
bind_status = current_row.locator("td").nth(2).inner_text().strip()
|
||||||
print(f" -> 锁定提取单号: {raw_no} [绑定状态: {bind_status}]")
|
print(f" -> 交接单号: {raw_no} [绑定状态: {bind_status}]")
|
||||||
|
|
||||||
if bind_status == "已绑定":
|
if bind_status == "已绑定":
|
||||||
print(" ⏭️ 状态拦截:该交接单处于【已绑定】状态,安全跳过。")
|
print(" ⏭️ 该交接单已绑定,跳过。")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
@@ -248,7 +242,7 @@ def yunda_expected_download(page):
|
|||||||
try:
|
try:
|
||||||
confirm_link.wait_for(state="visible", timeout=6000)
|
confirm_link.wait_for(state="visible", timeout=6000)
|
||||||
if export_frame.get_by_text("导出任务建立成功").is_visible():
|
if export_frame.get_by_text("导出任务建立成功").is_visible():
|
||||||
print(" ✅ 判定通过:成功捕获到【导出任务建立成功】特征!")
|
print(" ✅ 已确认导出任务建立成功。")
|
||||||
confirm_link.click()
|
confirm_link.click()
|
||||||
task_success = True
|
task_success = True
|
||||||
break
|
break
|
||||||
@@ -256,7 +250,7 @@ def yunda_expected_download(page):
|
|||||||
"请选择格式相应的导出字段"
|
"请选择格式相应的导出字段"
|
||||||
).is_visible():
|
).is_visible():
|
||||||
print(
|
print(
|
||||||
" ⚠️ 警告:检测到【未选择字段】错误,重新触发补点全选..."
|
" ⚠️ 检测到未选择字段,重新点击全选..."
|
||||||
)
|
)
|
||||||
confirm_link.click()
|
confirm_link.click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
@@ -270,7 +264,7 @@ def yunda_expected_download(page):
|
|||||||
|
|
||||||
if not task_success:
|
if not task_success:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"致命异常:连续 5 次尝试均无法成功建立应到数据离线任务。"
|
"连续 5 次尝试均未能建立应到数据离线任务。"
|
||||||
)
|
)
|
||||||
|
|
||||||
ws_frame.locator(".layui-layer-close1").click()
|
ws_frame.locator(".layui-layer-close1").click()
|
||||||
@@ -279,16 +273,16 @@ def yunda_expected_download(page):
|
|||||||
page.wait_for_timeout(800)
|
page.wait_for_timeout(800)
|
||||||
export_times.append(datetime.now())
|
export_times.append(datetime.now())
|
||||||
|
|
||||||
print(">> 📤 任务提交流闭环,正在执行【进站交接单查询】工作台销毁...")
|
print(">> 任务提交完成,正在关闭【进站交接单查询】标签页...")
|
||||||
page.locator(".tags-view-item", has_text="进站交接单查询").locator(
|
page.locator(".tags-view-item", has_text="进站交接单查询").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
).click()
|
).click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
# 防线:如果全部记录都被跳过了,export_times 为空,直接结束
|
# 若所有记录都被跳过,export_times 为空,直接结束
|
||||||
if not export_times:
|
if not export_times:
|
||||||
print(
|
print(
|
||||||
">> ⚠️ 本次查询未产生任何有效的离线下载任务(全部空单或已被跳过),中止后端收割流。"
|
">> ⚠️ 本次未产生任何离线下载任务(无数据或已全部跳过),结束。"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -302,6 +296,7 @@ def yunda_expected_download(page):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def yunda_actual_download(page):
|
def yunda_actual_download(page):
|
||||||
@@ -319,17 +314,17 @@ def yunda_actual_download(page):
|
|||||||
page.locator(".el-menu-item", has_text="首页").wait_for(
|
page.locator(".el-menu-item", has_text="首页").wait_for(
|
||||||
state="visible", timeout=15000
|
state="visible", timeout=15000
|
||||||
)
|
)
|
||||||
print("✅ 韵达工作台首页成功加载")
|
print("✅ 韵达工作台首页已加载")
|
||||||
|
|
||||||
yunda_smart_menu_click(page, ["报表管理", "扫描记录查询"])
|
yunda_smart_menu_click(page, ["报表管理", "扫描记录查询"])
|
||||||
|
|
||||||
print(">> 正在跨域动态追踪【扫描记录查询】业务窗体...")
|
print(">> 正在定位【扫描记录查询】iframe...")
|
||||||
ws_frame = page.frame_locator("section iframe")
|
ws_frame = page.frame_locator("section iframe")
|
||||||
|
|
||||||
ws_frame.locator(
|
ws_frame.locator(
|
||||||
".no-records-found", has_text="没有找到匹配的记录"
|
".no-records-found", has_text="没有找到匹配的记录"
|
||||||
).first.wait_for(state="visible", timeout=15000)
|
).first.wait_for(state="visible", timeout=15000)
|
||||||
print("✅ 扫描记录查询工作区初始化完毕")
|
print("✅ 扫描记录查询页面已初始化")
|
||||||
|
|
||||||
query_days = 1
|
query_days = 1
|
||||||
try:
|
try:
|
||||||
@@ -343,7 +338,7 @@ def yunda_actual_download(page):
|
|||||||
today = datetime.now()
|
today = datetime.now()
|
||||||
start_date = today - timedelta(days=(query_days - 1))
|
start_date = today - timedelta(days=(query_days - 1))
|
||||||
|
|
||||||
print(f">> 正在精准对焦实到时间区间: [近 {query_days} 天]")
|
print(f">> 设置实到查询时间范围: [近 {query_days} 天]")
|
||||||
|
|
||||||
print(" >> 正在设定起始时间...")
|
print(" >> 正在设定起始时间...")
|
||||||
ws_frame.locator("#startDate").click()
|
ws_frame.locator("#startDate").click()
|
||||||
@@ -367,7 +362,7 @@ def yunda_actual_download(page):
|
|||||||
ws_frame.locator("#scanRecordTyp").select_option(value="03")
|
ws_frame.locator("#scanRecordTyp").select_option(value="03")
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
print(">> 正在触发现场查询数据流...")
|
print(">> 正在执行查询...")
|
||||||
ws_frame.locator('input[type="button"][value="查询"]').click()
|
ws_frame.locator('input[type="button"][value="查询"]').click()
|
||||||
|
|
||||||
page.wait_for_timeout(800)
|
page.wait_for_timeout(800)
|
||||||
@@ -376,7 +371,7 @@ def yunda_actual_download(page):
|
|||||||
".fixed-table-loading", has_text="正在努力地加载数据中"
|
".fixed-table-loading", has_text="正在努力地加载数据中"
|
||||||
).first
|
).first
|
||||||
if loading_mask.is_visible():
|
if loading_mask.is_visible():
|
||||||
print(" ⏳ 检测到专属数据加载罩,正在等待后端重载返回...")
|
print(" ⏳ 检测到数据加载遮罩,等待加载完成...")
|
||||||
loading_mask.wait_for(state="hidden", timeout=30000)
|
loading_mask.wait_for(state="hidden", timeout=30000)
|
||||||
|
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
@@ -390,7 +385,7 @@ def yunda_actual_download(page):
|
|||||||
if match_total and int(match_total.group(1)) > 0:
|
if match_total and int(match_total.group(1)) > 0:
|
||||||
has_records = True
|
has_records = True
|
||||||
print(
|
print(
|
||||||
f" ✅ 深度断言:实到数据渲染完毕,总记录数: [{match_total.group(1)}] 条。"
|
f" ✅ 实到数据已加载,总记录数: [{match_total.group(1)}] 条。"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not has_records:
|
if not has_records:
|
||||||
@@ -398,21 +393,21 @@ def yunda_actual_download(page):
|
|||||||
".no-records-found", has_text="没有找到匹配的记录"
|
".no-records-found", has_text="没有找到匹配的记录"
|
||||||
).first.is_visible():
|
).first.is_visible():
|
||||||
print(
|
print(
|
||||||
" ⚠️ 当前查询范围内确认为空数据:系统提示【没有找到匹配的记录】,终止并关闭环境。"
|
" ⚠️ 当前查询范围内为空数据,终止并关闭标签页。"
|
||||||
)
|
)
|
||||||
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
).click()
|
).click()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
print(" ⚠️ 发生渲染异常,未找到数据也未找到空记录提示。安全收尾...")
|
print(" ⚠️ 未找到数据,也未出现空数据提示,结束。")
|
||||||
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
).click()
|
).click()
|
||||||
return
|
return
|
||||||
|
|
||||||
# 5. 执行导出流
|
# 5. 执行导出流
|
||||||
print(">> 正在发起【导出】申请指令...")
|
print(">> 正在发起导出...")
|
||||||
ws_frame.locator('input[type="button"][id="export"]').click()
|
ws_frame.locator('input[type="button"][id="export"]').click()
|
||||||
|
|
||||||
ws_frame.locator(".layui-layer-title", has_text="数据导出").wait_for(
|
ws_frame.locator(".layui-layer-title", has_text="数据导出").wait_for(
|
||||||
@@ -431,12 +426,12 @@ def yunda_actual_download(page):
|
|||||||
try:
|
try:
|
||||||
confirm_link.wait_for(state="visible", timeout=6000)
|
confirm_link.wait_for(state="visible", timeout=6000)
|
||||||
if export_frame.get_by_text("导出任务建立成功").is_visible():
|
if export_frame.get_by_text("导出任务建立成功").is_visible():
|
||||||
print(" ✅ 判定通过:成功捕获到【导出任务建立成功】特征!")
|
print(" ✅ 已确认导出任务建立成功。")
|
||||||
confirm_link.click()
|
confirm_link.click()
|
||||||
task_success = True
|
task_success = True
|
||||||
break
|
break
|
||||||
elif export_frame.get_by_text("请选择格式相应的导出字段").is_visible():
|
elif export_frame.get_by_text("请选择格式相应的导出字段").is_visible():
|
||||||
print(" ⚠️ 警告:检测到字段未全选,执行强补点击...")
|
print(" ⚠️ 检测到字段未全选,重新点击全选...")
|
||||||
confirm_link.click()
|
confirm_link.click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
export_frame.locator(".allRight").click()
|
export_frame.locator(".allRight").click()
|
||||||
@@ -449,14 +444,14 @@ def yunda_actual_download(page):
|
|||||||
|
|
||||||
if not task_success:
|
if not task_success:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"致命异常:连续 5 次尝试均无法成功建立实到数据离线任务。"
|
"连续 5 次尝试均未能建立实到数据离线任务。"
|
||||||
)
|
)
|
||||||
|
|
||||||
ws_frame.locator(".layui-layer-close1").click()
|
ws_frame.locator(".layui-layer-close1").click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
export_times.append(datetime.now())
|
export_times.append(datetime.now())
|
||||||
|
|
||||||
print(">> 📤 任务提交流闭环,正在执行【扫描记录查询】工作台销毁...")
|
print(">> 任务提交完成,正在关闭【扫描记录查询】标签页...")
|
||||||
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
page.locator(".tags-view-item", has_text="扫描记录查询").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
).click()
|
).click()
|
||||||
@@ -464,10 +459,10 @@ def yunda_actual_download(page):
|
|||||||
|
|
||||||
# 防线:双重保护
|
# 防线:双重保护
|
||||||
if not export_times:
|
if not export_times:
|
||||||
print(">> ⚠️ 本次查询未产生有效的离线下载任务,中止后端收割流。")
|
print(">> ⚠️ 本次未产生任何离线下载任务,结束。")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 6. 收割下载
|
# 6. 轮询并下载
|
||||||
_yunda_poll_and_download_tasks(
|
_yunda_poll_and_download_tasks(
|
||||||
page,
|
page,
|
||||||
export_times,
|
export_times,
|
||||||
@@ -478,13 +473,14 @@ def yunda_actual_download(page):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _yunda_poll_and_download_tasks(
|
def _yunda_poll_and_download_tasks(
|
||||||
page, export_times, target_task_title, download_dir, final_filename
|
page, export_times, target_task_title, download_dir, final_filename
|
||||||
):
|
):
|
||||||
"""韵达专属离线任务轮询下载引擎"""
|
"""韵达离线任务的轮询与下载"""
|
||||||
print("\n>> 正在前往【导出服务】中心...")
|
print("\n>> 正在前往【导出服务】界面...")
|
||||||
yunda_smart_menu_click(page, ["基础数据", "导出服务"])
|
yunda_smart_menu_click(page, ["基础数据", "导出服务"])
|
||||||
|
|
||||||
export_ws_frame = page.frame_locator("section iframe")
|
export_ws_frame = page.frame_locator("section iframe")
|
||||||
@@ -494,7 +490,7 @@ def _yunda_poll_and_download_tasks(
|
|||||||
)
|
)
|
||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
|
|
||||||
print(">> 离线文件队列已对接,启动【1分钟高频精确校对+缺单局部重载刷新】断言...")
|
print(">> 开始轮询离线文件队列(定时刷新直到任务齐全)...")
|
||||||
total_expected = len(export_times)
|
total_expected = len(export_times)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -532,17 +528,17 @@ def _yunda_poll_and_download_tasks(
|
|||||||
|
|
||||||
total_found = len(ready_indices) + len(processing_indices)
|
total_found = len(ready_indices) + len(processing_indices)
|
||||||
print(
|
print(
|
||||||
f" 📊 状态研判:自建期望数 [{total_expected}],实际入表 [{total_found}] (完成 [{len(ready_indices)}],生成中 [{len(processing_indices)}])"
|
f" 📊 状态统计:期望 [{total_expected}],已入表 [{total_found}] (完成 [{len(ready_indices)}],生成中 [{len(processing_indices)}])"
|
||||||
)
|
)
|
||||||
|
|
||||||
if total_found < total_expected or len(processing_indices) > 0:
|
if total_found < total_expected or len(processing_indices) > 0:
|
||||||
print(" ⏳ 队列未齐,执行【点击查询按钮】触发局部无痕刷新...")
|
print(" ⏳ 队列未齐全,点击查询刷新...")
|
||||||
export_ws_frame.locator(
|
export_ws_frame.locator(
|
||||||
"#ydkyimport_basic_export_searchData1_ky_export_common"
|
"#ydkyimport_basic_export_searchData1_ky_export_common"
|
||||||
).click()
|
).click()
|
||||||
page.wait_for_timeout(3000)
|
page.wait_for_timeout(3000)
|
||||||
else:
|
else:
|
||||||
print(">> ✅ 所有目标离线任务全量就绪!开始依序接入文件流...")
|
print(">> 所有目标离线任务已就绪,开始依次下载...")
|
||||||
break
|
break
|
||||||
|
|
||||||
downloaded_files = []
|
downloaded_files = []
|
||||||
@@ -554,7 +550,7 @@ def _yunda_poll_and_download_tasks(
|
|||||||
time_flag = (
|
time_flag = (
|
||||||
target_row.locator("td[field='createdTime']").inner_text().strip()
|
target_row.locator("td[field='createdTime']").inner_text().strip()
|
||||||
)
|
)
|
||||||
print(f" 🎯 触发下载 -> 离线任务时间节点: [{time_flag}] ...")
|
print(f" 开始下载任务 [{time_flag}] ...")
|
||||||
|
|
||||||
with page.expect_download() as download_info:
|
with page.expect_download() as download_info:
|
||||||
target_row.locator("td[field='extreFile'] a").get_by_text(
|
target_row.locator("td[field='extreFile'] a").get_by_text(
|
||||||
@@ -569,22 +565,22 @@ def _yunda_poll_and_download_tasks(
|
|||||||
|
|
||||||
download.save_as(save_path)
|
download.save_as(save_path)
|
||||||
downloaded_files.append(save_path)
|
downloaded_files.append(save_path)
|
||||||
print(f" ⬇️ 文件已用安全序列号落盘: downloads/{custom_filename}")
|
print(f" 已下载: downloads/{custom_filename}")
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ❌ 文件流接收失败: {e}")
|
print(f" ❌ 下载失败: {e}")
|
||||||
|
|
||||||
print(">> 📥 【导出服务】数据提取链闭环,正在执行当前 Tab 窗口销毁...")
|
print(">> 【导出服务】下载完成,正在关闭标签页...")
|
||||||
try:
|
try:
|
||||||
page.locator(".tags-view-item", has_text="导出服务").locator(
|
page.locator(".tags-view-item", has_text="导出服务").locator(
|
||||||
".el-icon-close"
|
".el-icon-close"
|
||||||
).click()
|
).click()
|
||||||
print(" ✅ 【导出服务】工作区已安全关闭。")
|
print(" ✅ 【导出服务】标签页已关闭。")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if downloaded_files:
|
if downloaded_files:
|
||||||
print("\n>> 🧪 正在启动离线数据清洗与高能扁平合并流...")
|
print("\n>> 正在合并下载的数据...")
|
||||||
all_dfs = []
|
all_dfs = []
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
try:
|
try:
|
||||||
@@ -599,10 +595,10 @@ def _yunda_poll_and_download_tasks(
|
|||||||
final_output = os.path.join(download_dir, final_filename)
|
final_output = os.path.join(download_dir, final_filename)
|
||||||
combined_df.to_excel(final_output, index=False)
|
combined_df.to_excel(final_output, index=False)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 恭喜!韵达网点数据拉取合流大功告成!")
|
print(f" 合并完成。")
|
||||||
print(f" 📁 最终存储位置: {final_output}")
|
print(f" 📁 输出路径: {final_output}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print(" ✅ 临时缓存阵列已无缝净化。")
|
print(" 临时文件已清理。")
|
||||||
|
|||||||
106
site_zto.py
106
site_zto.py
@@ -10,7 +10,7 @@ from paths import DOWNLOAD_DIR, CONFIG_PATH
|
|||||||
|
|
||||||
|
|
||||||
def _wait_and_get_frame(page, text_indicator, timeout_ms=20000):
|
def _wait_and_get_frame(page, text_indicator, timeout_ms=20000):
|
||||||
"""动态雷达探测器:全域扫描所有视窗"""
|
"""在主页面与所有 iframe 中查找包含指定文本的窗口"""
|
||||||
start_time = datetime.now()
|
start_time = datetime.now()
|
||||||
while (datetime.now() - start_time).total_seconds() * 1000 < timeout_ms:
|
while (datetime.now() - start_time).total_seconds() * 1000 < timeout_ms:
|
||||||
try:
|
try:
|
||||||
@@ -27,11 +27,11 @@ def _wait_and_get_frame(page, text_indicator, timeout_ms=20000):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
raise TimeoutError(f"爆栈超时:全域未死守到包含 [{text_indicator}] 的弹窗视窗。")
|
raise TimeoutError(f"超时:未找到包含 [{text_indicator}] 的窗口。")
|
||||||
|
|
||||||
|
|
||||||
def zto_smart_menu_click(page, menu_path):
|
def zto_smart_menu_click(page, menu_path):
|
||||||
"""中通智能菜单导航器"""
|
"""中通菜单导航"""
|
||||||
print(f">> 正在导航: {' -> '.join(menu_path)}")
|
print(f">> 正在导航: {' -> '.join(menu_path)}")
|
||||||
for i in range(len(menu_path)):
|
for i in range(len(menu_path)):
|
||||||
current_menu = menu_path[i]
|
current_menu = menu_path[i]
|
||||||
@@ -58,12 +58,12 @@ def zto_expected_download(page):
|
|||||||
export_times = []
|
export_times = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 智能导航
|
# 1. 菜单导航
|
||||||
zto_smart_menu_click(page, ["运营管理", "进站管理", "进站交接单查询"])
|
zto_smart_menu_click(page, ["运营管理", "进站管理", "进站交接单查询"])
|
||||||
|
|
||||||
ewb_frame = page.frame_locator('iframe[src*="inEwbsListNoSearch"]')
|
ewb_frame = page.frame_locator('iframe[src*="inEwbsListNoSearch"]')
|
||||||
|
|
||||||
print(">> 正在探针检测表格统计面板 (#inEwbCount)...")
|
print(">> 正在检测表格统计面板 (#inEwbCount)...")
|
||||||
ewb_frame.locator("#inEwbCount").wait_for(state="attached", timeout=15000)
|
ewb_frame.locator("#inEwbCount").wait_for(state="attached", timeout=15000)
|
||||||
|
|
||||||
# 读取 YAML 配置设定天数
|
# 读取 YAML 配置设定天数
|
||||||
@@ -105,7 +105,7 @@ def zto_expected_download(page):
|
|||||||
|
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
# 3. 触发查询与深度状态机判定
|
# 3. 触发查询并判断数据状态
|
||||||
print(">> 正在点击【查询】按钮并等待数据响应...")
|
print(">> 正在点击【查询】按钮并等待数据响应...")
|
||||||
old_count = ewb_frame.locator("#inEwbCount").inner_text().strip()
|
old_count = ewb_frame.locator("#inEwbCount").inner_text().strip()
|
||||||
ewb_frame.locator("#searchbtn").click()
|
ewb_frame.locator("#searchbtn").click()
|
||||||
@@ -129,7 +129,7 @@ def zto_expected_download(page):
|
|||||||
wait_cycles += 1
|
wait_cycles += 1
|
||||||
|
|
||||||
ticket_count_str = ewb_frame.locator("#inEwbCount").inner_text().strip()
|
ticket_count_str = ewb_frame.locator("#inEwbCount").inner_text().strip()
|
||||||
print(f" ✅ 数据面板就绪!当前进站实际票数为: [{ticket_count_str}]")
|
print(f" ✅ 数据已加载,进站实际票数: [{ticket_count_str}]")
|
||||||
|
|
||||||
# 数据分流
|
# 数据分流
|
||||||
if (
|
if (
|
||||||
@@ -137,13 +137,13 @@ def zto_expected_download(page):
|
|||||||
or not ticket_count_str.isdigit()
|
or not ticket_count_str.isdigit()
|
||||||
or int(ticket_count_str) == 0
|
or int(ticket_count_str) == 0
|
||||||
):
|
):
|
||||||
print(" >> 票数为 0,正在验证是否为确切的空数据区...")
|
print(" >> 票数为 0,正在确认是否为空数据...")
|
||||||
empty_flag = ewb_frame.locator("#datagrid1").get_by_text(
|
empty_flag = ewb_frame.locator("#datagrid1").get_by_text(
|
||||||
"没有搜索到符合条件的数据记录"
|
"没有搜索到符合条件的数据记录"
|
||||||
)
|
)
|
||||||
if empty_flag.is_visible():
|
if empty_flag.is_visible():
|
||||||
print(" ⚠️ 确认为空数据区,正在关闭当前标签页清理环境...")
|
print(" ⚠️ 确认为空数据,正在关闭当前标签页...")
|
||||||
# 即使为空数据,我们也清理关闭当前已经打开的 Tab
|
# 即使无数据也关闭已打开的标签页
|
||||||
try:
|
try:
|
||||||
page.locator(".mini-tab", has_text="进站交接单查询").locator(
|
page.locator(".mini-tab", has_text="进站交接单查询").locator(
|
||||||
".mini-tab-close"
|
".mini-tab-close"
|
||||||
@@ -161,7 +161,7 @@ def zto_expected_download(page):
|
|||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
print(" >> 票数验证通过,等待主数据表格渲染实体行...")
|
print(" >> 票数校验通过,等待主表格渲染数据行...")
|
||||||
ewb_frame.locator(
|
ewb_frame.locator(
|
||||||
"#datagrid1 .mini-grid-rows-view .mini-grid-row"
|
"#datagrid1 .mini-grid-rows-view .mini-grid-row"
|
||||||
).first.wait_for(state="visible", timeout=15000)
|
).first.wait_for(state="visible", timeout=15000)
|
||||||
@@ -180,7 +180,7 @@ def zto_expected_download(page):
|
|||||||
raw_text = row.locator("td").nth(3).inner_text()
|
raw_text = row.locator("td").nth(3).inner_text()
|
||||||
match = re.search(r"\d{18}", raw_text)
|
match = re.search(r"\d{18}", raw_text)
|
||||||
handover_no = match.group(0) if match else raw_text.strip()
|
handover_no = match.group(0) if match else raw_text.strip()
|
||||||
print(f" -> 锁定提取单号:{handover_no}")
|
print(f" -> 当前交接单号:{handover_no}")
|
||||||
|
|
||||||
row.dblclick()
|
row.dblclick()
|
||||||
|
|
||||||
@@ -203,12 +203,12 @@ def zto_expected_download(page):
|
|||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
export_frame.locator(".mini-button-text", has_text="确定").click()
|
export_frame.locator(".mini-button-text", has_text="确定").click()
|
||||||
|
|
||||||
print(" >> [雷达扫描] 正在跨域动态追踪【温馨提示】所在的隐身视窗...")
|
print(" >> 正在查找【温馨提示】弹窗...")
|
||||||
ctx_alert = _wait_and_get_frame(page, "温馨提示")
|
ctx_alert = _wait_and_get_frame(page, "温馨提示")
|
||||||
ctx_alert.locator(
|
ctx_alert.locator(
|
||||||
".mini-messagebox-buttons .mini-button-text", has_text="确定"
|
".mini-messagebox-buttons .mini-button-text", has_text="确定"
|
||||||
).click()
|
).click()
|
||||||
print(" ✅ 【温馨提示】已成功通过父级过滤并确认!")
|
print(" ✅ 已确认【温馨提示】弹窗。")
|
||||||
|
|
||||||
print(" >> 正在等待服务器建立后台离线任务...")
|
print(" >> 正在等待服务器建立后台离线任务...")
|
||||||
try:
|
try:
|
||||||
@@ -218,9 +218,9 @@ def zto_expected_download(page):
|
|||||||
ctx_tips.locator(".mini-tips-success").wait_for(
|
ctx_tips.locator(".mini-tips-success").wait_for(
|
||||||
state="hidden", timeout=15000
|
state="hidden", timeout=15000
|
||||||
)
|
)
|
||||||
print(" ✅ 成功提示框已平滑隐藏。")
|
print(" ✅ 成功提示框已消失。")
|
||||||
except Exception:
|
except Exception:
|
||||||
print(" ✅ 成功提示框及其容器已自动销毁。")
|
print(" ✅ 成功提示框已关闭。")
|
||||||
|
|
||||||
export_times.append(datetime.now())
|
export_times.append(datetime.now())
|
||||||
|
|
||||||
@@ -229,19 +229,19 @@ def zto_expected_download(page):
|
|||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 优化优化:完成交接单主表的所有穿透提交后,立刻销毁本 Tab 净化内存
|
# 完成所有交接单导出提交后,关闭当前标签页
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
print(">> 📥 【进站交接单查询】任务已就绪,正在精准执行 Tab 窗口销毁复位...")
|
print(">> 【进站交接单查询】已完成,正在关闭标签页...")
|
||||||
try:
|
try:
|
||||||
page.locator(".mini-tab", has_text="进站交接单查询").locator(
|
page.locator(".mini-tab", has_text="进站交接单查询").locator(
|
||||||
".mini-tab-close"
|
".mini-tab-close"
|
||||||
).click()
|
).click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
print(" ✅ 【进站交接单查询】Tab 已成功关闭。")
|
print(" ✅ 【进站交接单查询】标签页已关闭。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 尝试关闭【进站交接单查询】Tab 时遇到轻微阻碍: {e}")
|
print(f" ⚠️ 关闭【进站交接单查询】标签页时出错: {e}")
|
||||||
|
|
||||||
# 核心逻辑交由统一的轮询下载引擎处理
|
# 交由统一的轮询下载流程处理
|
||||||
_zto_poll_and_download_tasks(
|
_zto_poll_and_download_tasks(
|
||||||
page,
|
page,
|
||||||
export_times,
|
export_times,
|
||||||
@@ -252,6 +252,7 @@ def zto_expected_download(page):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def zto_actual_download(page):
|
def zto_actual_download(page):
|
||||||
@@ -266,12 +267,12 @@ def zto_actual_download(page):
|
|||||||
export_times = []
|
export_times = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 智能导航
|
# 1. 菜单导航
|
||||||
zto_smart_menu_click(page, ["运营管理", "扫描操作与监控", "到件扫描监控"])
|
zto_smart_menu_click(page, ["运营管理", "扫描操作与监控", "到件扫描监控"])
|
||||||
|
|
||||||
arr_frame = page.frame_locator('iframe[src*="ArriveScan"]')
|
arr_frame = page.frame_locator('iframe[src*="ArriveScan"]')
|
||||||
|
|
||||||
print(">> 正在探针检测主页面 (#daterange)...")
|
print(">> 正在检测主页面 (#daterange)...")
|
||||||
arr_frame.locator("#daterange").wait_for(state="attached", timeout=15000)
|
arr_frame.locator("#daterange").wait_for(state="attached", timeout=15000)
|
||||||
|
|
||||||
# 2. 读取 YAML 并设定时间范围
|
# 2. 读取 YAML 并设定时间范围
|
||||||
@@ -322,7 +323,7 @@ def zto_actual_download(page):
|
|||||||
).locator(".mini-tree-checkbox").click()
|
).locator(".mini-tree-checkbox").click()
|
||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
|
|
||||||
# 4. 触发查询与深度状态机判定
|
# 4. 触发查询并判断数据状态
|
||||||
print(">> 正在点击【查询】按钮并等待数据响应...")
|
print(">> 正在点击【查询】按钮并等待数据响应...")
|
||||||
arr_frame.locator("#searchbtn").click()
|
arr_frame.locator("#searchbtn").click()
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ def zto_actual_download(page):
|
|||||||
)
|
)
|
||||||
if empty_flag.is_visible():
|
if empty_flag.is_visible():
|
||||||
print(
|
print(
|
||||||
" ⚠️ 当前查询范围内【没有搜索到符合条件的数据记录】,终止并关闭环境。"
|
" ⚠️ 当前查询范围内没有数据,终止并关闭标签页。"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
page.locator(".mini-tab", has_text="到件扫描监控").locator(
|
page.locator(".mini-tab", has_text="到件扫描监控").locator(
|
||||||
@@ -351,7 +352,7 @@ def zto_actual_download(page):
|
|||||||
return
|
return
|
||||||
|
|
||||||
arr_frame.locator("#page1").wait_for(state="visible", timeout=15000)
|
arr_frame.locator("#page1").wait_for(state="visible", timeout=15000)
|
||||||
print(" ✅ 数据渲染完毕!(底部分页统计控件已就绪)")
|
print(" ✅ 数据已加载。(底部分页控件已就绪)")
|
||||||
|
|
||||||
# 5. 执行导出流程
|
# 5. 执行导出流程
|
||||||
arr_frame.locator("#exportExcel").click()
|
arr_frame.locator("#exportExcel").click()
|
||||||
@@ -365,12 +366,12 @@ def zto_actual_download(page):
|
|||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
export_frame.locator(".mini-button-text", has_text="确定").click()
|
export_frame.locator(".mini-button-text", has_text="确定").click()
|
||||||
|
|
||||||
print(" >> [雷达扫描] 正在跨域动态追踪【温馨提示】所在的隐身视窗...")
|
print(" >> 正在查找【温馨提示】弹窗...")
|
||||||
ctx_alert = _wait_and_get_frame(page, "温馨提示")
|
ctx_alert = _wait_and_get_frame(page, "温馨提示")
|
||||||
ctx_alert.locator(
|
ctx_alert.locator(
|
||||||
".mini-messagebox-buttons .mini-button-text", has_text="确定"
|
".mini-messagebox-buttons .mini-button-text", has_text="确定"
|
||||||
).click()
|
).click()
|
||||||
print(" ✅ 【温馨提示】已成功通过父级过滤并确认!")
|
print(" ✅ 已确认【温馨提示】弹窗。")
|
||||||
|
|
||||||
print(" >> 正在等待服务器建立后台离线任务...")
|
print(" >> 正在等待服务器建立后台离线任务...")
|
||||||
try:
|
try:
|
||||||
@@ -380,26 +381,26 @@ def zto_actual_download(page):
|
|||||||
ctx_tips.locator(".mini-tips-success").wait_for(
|
ctx_tips.locator(".mini-tips-success").wait_for(
|
||||||
state="hidden", timeout=15000
|
state="hidden", timeout=15000
|
||||||
)
|
)
|
||||||
print(" ✅ 成功提示框已平滑隐藏。")
|
print(" ✅ 成功提示框已消失。")
|
||||||
except Exception:
|
except Exception:
|
||||||
print(" ✅ 成功提示框及其容器已自动销毁。")
|
print(" ✅ 成功提示框已关闭。")
|
||||||
|
|
||||||
export_times.append(datetime.now())
|
export_times.append(datetime.now())
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 优化优化:完成实到主表的数据导出提交后,立刻销毁本 Tab 恢复空态
|
# 完成实到数据导出提交后,关闭当前标签页
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
print(">> 📥 【到件扫描监控】任务已就绪,正在精准执行 Tab 窗口销毁复位...")
|
print(">> 【到件扫描监控】已完成,正在关闭标签页...")
|
||||||
try:
|
try:
|
||||||
page.locator(".mini-tab", has_text="到件扫描监控").locator(
|
page.locator(".mini-tab", has_text="到件扫描监控").locator(
|
||||||
".mini-tab-close"
|
".mini-tab-close"
|
||||||
).click()
|
).click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
print(" ✅ 【到件扫描监控】Tab 已成功关闭。")
|
print(" ✅ 【到件扫描监控】标签页已关闭。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 尝试关闭【到件扫描监控】Tab 时遇到轻微阻碍: {e}")
|
print(f" ⚠️ 关闭【到件扫描监控】标签页时出错: {e}")
|
||||||
|
|
||||||
# 核心逻辑交由统一的轮询下载引擎处理
|
# 交由统一的轮询下载流程处理
|
||||||
_zto_poll_and_download_tasks(
|
_zto_poll_and_download_tasks(
|
||||||
page,
|
page,
|
||||||
export_times,
|
export_times,
|
||||||
@@ -410,12 +411,13 @@ def zto_actual_download(page):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
print(f"\n❌ 任务执行过程中发生异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _zto_poll_and_download_tasks(
|
def _zto_poll_and_download_tasks(
|
||||||
page, export_times, target_task_title, download_dir, final_filename
|
page, export_times, target_task_title, download_dir, final_filename
|
||||||
):
|
):
|
||||||
"""进站中通任务专属轮询、验证及即选即隐下载引擎"""
|
"""中通离线任务的轮询与下载"""
|
||||||
print("\n>> 正在前往【导出任务管理】界面...")
|
print("\n>> 正在前往【导出任务管理】界面...")
|
||||||
zto_smart_menu_click(page, ["系统配置", "导出任务管理"])
|
zto_smart_menu_click(page, ["系统配置", "导出任务管理"])
|
||||||
|
|
||||||
@@ -425,7 +427,7 @@ def _zto_poll_and_download_tasks(
|
|||||||
)
|
)
|
||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
|
|
||||||
print(">> 列表中已渲染,开始匹配并检查后端处理状态...")
|
print(">> 列表已加载,开始匹配并检查任务状态...")
|
||||||
total_expected = len(export_times)
|
total_expected = len(export_times)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -464,23 +466,23 @@ def _zto_poll_and_download_tasks(
|
|||||||
|
|
||||||
total_found = len(ready_timestamps) + len(processing_timestamps)
|
total_found = len(ready_timestamps) + len(processing_timestamps)
|
||||||
print(
|
print(
|
||||||
f" 📊 状态盘点:期望任务数 [{total_expected}],实际入表 [{total_found}] (就绪 [{len(ready_timestamps)}],处理中 [{len(processing_timestamps)}])"
|
f" 📊 状态统计:期望 [{total_expected}],已入表 [{total_found}] (就绪 [{len(ready_timestamps)}],处理中 [{len(processing_timestamps)}])"
|
||||||
)
|
)
|
||||||
|
|
||||||
if total_found < total_expected or len(processing_timestamps) > 0:
|
if total_found < total_expected or len(processing_timestamps) > 0:
|
||||||
print(" ⏳ 发现任务缺失或正在生成,执行【点击查询按钮】触发局部重载...")
|
print(" ⏳ 任务尚未齐全或仍在生成,点击查询刷新...")
|
||||||
try:
|
try:
|
||||||
taskdone_frame.locator(
|
taskdone_frame.locator(
|
||||||
".mini-button-text", has_text="查询"
|
".mini-button-text", has_text="查询"
|
||||||
).first.click()
|
).first.click()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 局部刷新按钮失效,使用菜单后备刷新: {e}")
|
print(f" ⚠️ 查询按钮不可用,改用菜单刷新: {e}")
|
||||||
page.locator("li.leaf span.menu-name", has_text="导出任务管理").click()
|
page.locator("li.leaf span.menu-name", has_text="导出任务管理").click()
|
||||||
|
|
||||||
page.wait_for_timeout(3000)
|
page.wait_for_timeout(3000)
|
||||||
else:
|
else:
|
||||||
target_task_timestamps = list(ready_timestamps)
|
target_task_timestamps = list(ready_timestamps)
|
||||||
print(">> ✅ 目标任务已全部生成,解除锁定,开始接收文件流...")
|
print(">> 所有目标任务已生成,开始下载...")
|
||||||
break
|
break
|
||||||
|
|
||||||
# 8. 下载逻辑
|
# 8. 下载逻辑
|
||||||
@@ -498,7 +500,7 @@ def _zto_poll_and_download_tasks(
|
|||||||
)
|
)
|
||||||
.first
|
.first
|
||||||
)
|
)
|
||||||
print(f" 🎯 触发下载 -> 任务 [{time_str}] ...")
|
print(f" 开始下载任务 [{time_str}] ...")
|
||||||
|
|
||||||
checkbox = target_row.locator(".mini-grid-checkbox")
|
checkbox = target_row.locator(".mini-grid-checkbox")
|
||||||
if checkbox.is_visible():
|
if checkbox.is_visible():
|
||||||
@@ -513,32 +515,32 @@ def _zto_poll_and_download_tasks(
|
|||||||
save_path = os.path.join(download_dir, download.suggested_filename)
|
save_path = os.path.join(download_dir, download.suggested_filename)
|
||||||
download.save_as(save_path)
|
download.save_as(save_path)
|
||||||
downloaded_files.append(save_path)
|
downloaded_files.append(save_path)
|
||||||
print(f" ⬇️ 文件已落盘: downloads/{download.suggested_filename}")
|
print(f" 已下载: downloads/{download.suggested_filename}")
|
||||||
|
|
||||||
if checkbox.is_visible():
|
if checkbox.is_visible():
|
||||||
checkbox.click()
|
checkbox.click()
|
||||||
print(" -> 已取消勾选,清理现场进入下一条")
|
print(" -> 已取消勾选,继续下一条")
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
print(f" ❌ 下载任务 [{time_str}] 失败: {e}")
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 🛡️ 优化优化:所有目标任务文件全部下载完毕后,立刻销毁“导出任务管理” Tab
|
# 所有目标文件下载完成后,关闭“导出任务管理”标签页
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
print(">> 📥 【导出任务管理】所有数据拉取流闭环,正在执行 Tab 窗口销毁复位...")
|
print(">> 【导出任务管理】下载完成,正在关闭标签页...")
|
||||||
try:
|
try:
|
||||||
page.locator(".mini-tab", has_text="导出任务管理").locator(
|
page.locator(".mini-tab", has_text="导出任务管理").locator(
|
||||||
".mini-tab-close"
|
".mini-tab-close"
|
||||||
).click()
|
).click()
|
||||||
page.wait_for_timeout(500)
|
page.wait_for_timeout(500)
|
||||||
print(" ✅ 【导出任务管理】Tab 已安全关闭。页面归档空态。")
|
print(" ✅ 【导出任务管理】标签页已关闭。")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ 尝试收尾关闭【导出任务管理】Tab 时有波动: {e}")
|
print(f" ⚠️ 关闭【导出任务管理】标签页时出错: {e}")
|
||||||
|
|
||||||
# 9. 合并数据
|
# 9. 合并数据
|
||||||
if downloaded_files:
|
if downloaded_files:
|
||||||
print("\n>> 🧪 正在开始执行扁平数据高能合并与清洗流程...")
|
print("\n>> 正在合并下载的数据...")
|
||||||
all_data_frames = []
|
all_data_frames = []
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
try:
|
try:
|
||||||
@@ -553,10 +555,10 @@ def _zto_poll_and_download_tasks(
|
|||||||
final_output_path = os.path.join(download_dir, final_filename)
|
final_output_path = os.path.join(download_dir, final_filename)
|
||||||
combined_df.to_excel(final_output_path, index=False)
|
combined_df.to_excel(final_output_path, index=False)
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
print(f" 🎉 恭喜!数据流闭环完成!")
|
print(f" 合并完成。")
|
||||||
print(f" 📁 最终输出成果归档至: {final_output_path}")
|
print(f" 📁 输出路径: {final_output_path}")
|
||||||
print(f"====================================================")
|
print(f"====================================================")
|
||||||
|
|
||||||
for file_path in downloaded_files:
|
for file_path in downloaded_files:
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print(" ✅ 临时缓存数据清理完毕。")
|
print(" 临时文件已清理。")
|
||||||
|
|||||||
Reference in New Issue
Block a user