Add per-site exception fallback (reset + retry), router stays pure dispatch
每个站点模块自带"失败→重置回初始态→重试"兜底(最多 3 次,含首次;每次 失败都重置,含最终放弃那次清场),对外只暴露 xxx_download 公开入口,路由 层无感、只负责调度。 - 各站点模块新增:HOME_URL、xxx_reset、内联 with_retry;原流程拆为 xxx_download(带重试) + xxx_download_impl(单次,供自动化测试探测原始失败)。 - 网页站重置=goto 首页 URL;安能(Electron)重置=关业务 tab+收菜单(实测 reload 会留下杀不掉的僵尸 webContents,故不用 reload)。 - main_router:SITES_CONFIG 改引用各模块 HOME_URL(单一来源);菜单分派回归 朴素调用;run_automation_test 改走 _impl 以保留"暴露问题"的测试本意。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -20,12 +20,12 @@ import site_anneng
|
||||
# 应到未到比对(全站点,离线处理 downloads/ 下的应到/实到数据)
|
||||
import expected_undelivered
|
||||
|
||||
# 定义网点及对应的初始登录 URL
|
||||
# 各网页站点首页 URL 统一取自对应站点模块的 HOME_URL(单一来源,不在两处维护)。
|
||||
SITES_CONFIG = {
|
||||
"顺心": "https://sxne.sxjdfreight.com",
|
||||
"百世": "https://v5.800best.com",
|
||||
"中通": "https://ws.zto56.com/",
|
||||
"韵达": "https://ky-sso.yunda56.com",
|
||||
"顺心": site_shunxin.HOME_URL,
|
||||
"百世": site_baishi.HOME_URL,
|
||||
"中通": site_zto.HOME_URL,
|
||||
"韵达": site_yunda.HOME_URL,
|
||||
}
|
||||
|
||||
# ====================================================================
|
||||
@@ -104,22 +104,24 @@ def run_automation_test(pages_map):
|
||||
判定规则:流程函数返回 False 或抛出异常记为 FAIL,其余记为 PASS。
|
||||
"""
|
||||
# 站点 -> {流程键: (中文名, 流程函数)};百世为单流程,单独处理
|
||||
# 自动化测试刻意走各站点的 _impl(单次执行、无兜底重试),以便探测原始失败、
|
||||
# 不被模块内部"失败→重置→重试"机制掩盖。
|
||||
flow_table = {
|
||||
"顺心": {
|
||||
"expected": ("应到", site_shunxin.shunxin_expected_download),
|
||||
"actual": ("实到", site_shunxin.shunxin_actual_download),
|
||||
"expected": ("应到", site_shunxin.shunxin_expected_download_impl),
|
||||
"actual": ("实到", site_shunxin.shunxin_actual_download_impl),
|
||||
},
|
||||
"中通": {
|
||||
"expected": ("应到", site_zto.zto_expected_download),
|
||||
"actual": ("实到", site_zto.zto_actual_download),
|
||||
"expected": ("应到", site_zto.zto_expected_download_impl),
|
||||
"actual": ("实到", site_zto.zto_actual_download_impl),
|
||||
},
|
||||
"韵达": {
|
||||
"expected": ("应到", site_yunda.yunda_expected_download),
|
||||
"actual": ("实到", site_yunda.yunda_actual_download),
|
||||
"expected": ("应到", site_yunda.yunda_expected_download_impl),
|
||||
"actual": ("实到", site_yunda.yunda_actual_download_impl),
|
||||
},
|
||||
"安能": {
|
||||
"expected": ("应到", site_anneng.anneng_expected_download),
|
||||
"actual": ("实到", site_anneng.anneng_actual_download),
|
||||
"expected": ("应到", site_anneng.anneng_expected_download_impl),
|
||||
"actual": ("实到", site_anneng.anneng_actual_download_impl),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -133,7 +135,9 @@ def run_automation_test(pages_map):
|
||||
plan.append((site_name, label, func))
|
||||
# 百世:单流程,跑一次即可
|
||||
if "百世" in pages_map:
|
||||
plan.append(("百世", "应到未到", site_baishi.baishi_download_undelivered_data))
|
||||
plan.append(
|
||||
("百世", "应到未到", site_baishi.baishi_download_undelivered_data_impl)
|
||||
)
|
||||
|
||||
if not plan:
|
||||
print("\n⚠️ 当前没有已就绪的站点,无法执行自动化测试。")
|
||||
@@ -419,26 +423,33 @@ def run_multi_site_daemon():
|
||||
|
||||
try:
|
||||
if choice == "1" and is_site_ready("顺心"):
|
||||
pages_map["顺心"].bring_to_front()
|
||||
site_shunxin.shunxin_expected_download(pages_map["顺心"])
|
||||
page = pages_map["顺心"]
|
||||
page.bring_to_front()
|
||||
site_shunxin.shunxin_expected_download(page)
|
||||
elif choice == "2" and is_site_ready("顺心"):
|
||||
pages_map["顺心"].bring_to_front()
|
||||
site_shunxin.shunxin_actual_download(pages_map["顺心"])
|
||||
page = pages_map["顺心"]
|
||||
page.bring_to_front()
|
||||
site_shunxin.shunxin_actual_download(page)
|
||||
elif choice == "3" and is_site_ready("百世"):
|
||||
pages_map["百世"].bring_to_front()
|
||||
site_baishi.baishi_download_undelivered_data(pages_map["百世"])
|
||||
page = pages_map["百世"]
|
||||
page.bring_to_front()
|
||||
site_baishi.baishi_download_undelivered_data(page)
|
||||
elif choice == "4" and is_site_ready("中通"):
|
||||
pages_map["中通"].bring_to_front()
|
||||
site_zto.zto_expected_download(pages_map["中通"])
|
||||
page = pages_map["中通"]
|
||||
page.bring_to_front()
|
||||
site_zto.zto_expected_download(page)
|
||||
elif choice == "5" and is_site_ready("中通"):
|
||||
pages_map["中通"].bring_to_front()
|
||||
site_zto.zto_actual_download(pages_map["中通"])
|
||||
page = pages_map["中通"]
|
||||
page.bring_to_front()
|
||||
site_zto.zto_actual_download(page)
|
||||
elif choice == "6" and is_site_ready("韵达"):
|
||||
pages_map["韵达"].bring_to_front()
|
||||
site_yunda.yunda_expected_download(pages_map["韵达"])
|
||||
page = pages_map["韵达"]
|
||||
page.bring_to_front()
|
||||
site_yunda.yunda_expected_download(page)
|
||||
elif choice == "7" and is_site_ready("韵达"):
|
||||
pages_map["韵达"].bring_to_front()
|
||||
site_yunda.yunda_actual_download(pages_map["韵达"])
|
||||
page = pages_map["韵达"]
|
||||
page.bring_to_front()
|
||||
site_yunda.yunda_actual_download(page)
|
||||
elif choice == "10" and is_site_ready("安能"):
|
||||
site_anneng.anneng_expected_download()
|
||||
elif choice == "11" and is_site_ready("安能"):
|
||||
|
||||
@@ -41,6 +41,35 @@ sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
"""异常兜底:flow 失败 → 重置回初始态 → 重试,最多 max_attempts 次(含首次)。
|
||||
|
||||
每次失败都重置(含最终放弃那一次):既是重试前的清场,也保证最终放弃时环境干净。
|
||||
flow 为零参可调用;返回 False 视为失败,其余视为成功。
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
ret = flow()
|
||||
if ret is False:
|
||||
raise RuntimeError("流程返回失败状态")
|
||||
if attempt > 1:
|
||||
print(f">> 【{site_name}-{label}】第 {attempt} 次尝试成功 ✅")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 【{site_name}-{label}】第 {attempt}/{max_attempts} 次失败: {e}")
|
||||
print(f" → 重置【{site_name}】到初始态,清理环境 ...")
|
||||
try:
|
||||
reset()
|
||||
except Exception as re:
|
||||
print(f" ⚠️ 重置异常: {re}")
|
||||
if attempt < max_attempts:
|
||||
continue
|
||||
print(
|
||||
f"❌ 【{site_name}-{label}】已达最大尝试次数 {max_attempts},放弃(环境已清理)。"
|
||||
)
|
||||
|
||||
|
||||
CDP_PORT = 9222 # 默认端口(独立运行 site_anneng.py 时用);main_router 启动时会用 set_cdp_port 覆盖
|
||||
POLL_INTERVAL = 0.5
|
||||
DEFAULT_TIMEOUT = 25.0
|
||||
@@ -799,7 +828,13 @@ def _load_query_days():
|
||||
|
||||
|
||||
def anneng_expected_download():
|
||||
"""安能:应到货物数据(运单信息)下载,完整流程。"""
|
||||
"""安能:应到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry("安能", "应到", anneng_expected_download_impl, anneng_reset)
|
||||
|
||||
|
||||
def anneng_expected_download_impl():
|
||||
"""安能:应到货物数据(运单信息)下载,完整流程(单次执行,无重试;供自动化测试用)。"""
|
||||
print("\n▶ 开始执行【安能 - 应到货物数据下载】任务 ...")
|
||||
download_dir = DOWNLOAD_DIR
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
@@ -1094,7 +1129,13 @@ def _save_actual(rows, download_dir):
|
||||
|
||||
|
||||
def anneng_actual_download():
|
||||
"""安能:实到数据(网点到件扫描,子单)下载,完整流程。"""
|
||||
"""安能:实到数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry("安能", "实到", anneng_actual_download_impl, anneng_reset)
|
||||
|
||||
|
||||
def anneng_actual_download_impl():
|
||||
"""安能:实到数据(网点到件扫描,子单)下载,完整流程(单次执行,无重试;供自动化测试用)。"""
|
||||
print("\n▶ 开始执行【安能 - 实到货物数据下载】任务 ...")
|
||||
download_dir = DOWNLOAD_DIR
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
@@ -1169,6 +1210,59 @@ def anneng_actual_download():
|
||||
main_cdp.close()
|
||||
|
||||
|
||||
def anneng_reset():
|
||||
"""异常兜底:把安能重置回刚登录的初始态,供失败重试调用。
|
||||
|
||||
关掉所有业务 tab(走 tab 条 X,由 app 销毁对应 webContents,含其上的弹窗)+ 收起
|
||||
展开的子菜单。不用 Page.reload——reload 会让业务 webContents 失去 app 引用、
|
||||
变成无法清理的僵尸(Target.closeTarget / window.close 都杀不掉)。
|
||||
"""
|
||||
try:
|
||||
main_cdp = find_main_page_cdp()
|
||||
except Exception as e:
|
||||
print(f">> 安能重置失败:找不到主壳页面: {e}")
|
||||
return False
|
||||
try:
|
||||
labels_json = main_cdp.eval(
|
||||
"JSON.stringify([...document.querySelectorAll('.w-tabs-draggable-item')]"
|
||||
".map(t => t.querySelector('span')?.textContent?.trim()).filter(Boolean))"
|
||||
)
|
||||
try:
|
||||
labels = json.loads(labels_json or "[]")
|
||||
except Exception:
|
||||
labels = []
|
||||
closed = 0
|
||||
for label in labels:
|
||||
if label and label != "工作台":
|
||||
try:
|
||||
close_tab_by_label(main_cdp, label)
|
||||
closed += 1
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 关闭 tab「{label}」失败: {e}")
|
||||
# 收起所有展开的子菜单(循环点掉每个 aria-expanded=true 的标题,含嵌套层级)
|
||||
loops = 0
|
||||
while main_cdp.eval(
|
||||
"(() => {const e = document.querySelector"
|
||||
"('div.rc-menu-submenu-title[aria-expanded=\"true\"]');"
|
||||
"if (!e) return false; e.click(); return true;})()"
|
||||
):
|
||||
loops += 1
|
||||
time.sleep(0.15)
|
||||
if loops > 15:
|
||||
break
|
||||
print(f">> 安能已重置到初始态:关闭 {closed} 个业务 tab,收起 {loops} 个子菜单")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f">> 安能重置异常: {e}")
|
||||
return False
|
||||
finally:
|
||||
try:
|
||||
main_cdp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
flow = sys.argv[1] if len(sys.argv) > 1 else "expected"
|
||||
if flow == "actual":
|
||||
|
||||
@@ -7,6 +7,44 @@ from playwright.sync_api import sync_playwright
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
"""异常兜底:flow 失败 → 重置回初始态 → 重试,最多 max_attempts 次(含首次)。
|
||||
|
||||
每次失败都重置(含最终放弃那一次):既是重试前的清场,也保证最终放弃时环境干净。
|
||||
flow 为零参可调用;返回 False 视为失败,其余视为成功。
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
ret = flow()
|
||||
if ret is False:
|
||||
raise RuntimeError("流程返回失败状态")
|
||||
if attempt > 1:
|
||||
print(f">> 【{site_name}-{label}】第 {attempt} 次尝试成功 ✅")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 【{site_name}-{label}】第 {attempt}/{max_attempts} 次失败: {e}")
|
||||
print(f" → 重置【{site_name}】到初始态,清理环境 ...")
|
||||
try:
|
||||
reset()
|
||||
except Exception as re:
|
||||
print(f" ⚠️ 重置异常: {re}")
|
||||
if attempt < max_attempts:
|
||||
continue
|
||||
print(
|
||||
f"❌ 【{site_name}-{label}】已达最大尝试次数 {max_attempts},放弃(环境已清理)。"
|
||||
)
|
||||
|
||||
|
||||
# 站点首页 URL:异常兜底重置用,也供 main_router 的 SITES_CONFIG 引用(单一来源)
|
||||
HOME_URL = "https://v5.800best.com"
|
||||
|
||||
|
||||
def baishi_reset(page):
|
||||
"""异常兜底:重置百世到初始态(跳首页 URL,丢弃当前页面状态,登录态保留)。"""
|
||||
page.goto(HOME_URL)
|
||||
page.wait_for_timeout(1500)
|
||||
|
||||
|
||||
def _close_tab(page, tab_name):
|
||||
"""关闭指定名称的百世标签页。
|
||||
|
||||
@@ -27,7 +65,18 @@ def _close_tab(page, tab_name):
|
||||
|
||||
|
||||
def baishi_download_undelivered_data(page):
|
||||
"""百世:一键提取应到未到(当日未扫)数据"""
|
||||
"""百世:一键提取应到未到(当日未扫)数据(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"百世",
|
||||
"应到未到",
|
||||
lambda: baishi_download_undelivered_data_impl(page),
|
||||
lambda: baishi_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def baishi_download_undelivered_data_impl(page):
|
||||
"""百世:一键提取应到未到(当日未扫)数据(单次执行,无重试;供自动化测试用)。"""
|
||||
print("\n▶ 开始执行【百世 - 一键提取应到未到数据】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
|
||||
@@ -9,6 +9,44 @@ import pandas as pd
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
"""异常兜底:flow 失败 → 重置回初始态 → 重试,最多 max_attempts 次(含首次)。
|
||||
|
||||
每次失败都重置(含最终放弃那一次):既是重试前的清场,也保证最终放弃时环境干净。
|
||||
flow 为零参可调用;返回 False 视为失败,其余视为成功。
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
ret = flow()
|
||||
if ret is False:
|
||||
raise RuntimeError("流程返回失败状态")
|
||||
if attempt > 1:
|
||||
print(f">> 【{site_name}-{label}】第 {attempt} 次尝试成功 ✅")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 【{site_name}-{label}】第 {attempt}/{max_attempts} 次失败: {e}")
|
||||
print(f" → 重置【{site_name}】到初始态,清理环境 ...")
|
||||
try:
|
||||
reset()
|
||||
except Exception as re:
|
||||
print(f" ⚠️ 重置异常: {re}")
|
||||
if attempt < max_attempts:
|
||||
continue
|
||||
print(
|
||||
f"❌ 【{site_name}-{label}】已达最大尝试次数 {max_attempts},放弃(环境已清理)。"
|
||||
)
|
||||
|
||||
|
||||
# 站点首页 URL:异常兜底重置用,也供 main_router 的 SITES_CONFIG 引用(单一来源)
|
||||
HOME_URL = "https://sxne.sxjdfreight.com"
|
||||
|
||||
|
||||
def shunxin_reset(page):
|
||||
"""异常兜底:重置顺心到初始态(跳首页 URL,丢弃当前页面状态,登录态保留)。"""
|
||||
page.goto(HOME_URL)
|
||||
page.wait_for_timeout(1500)
|
||||
|
||||
|
||||
def _close_tab(page, tab_name):
|
||||
"""关闭指定名称的标签页(Ant Design Tabs)。
|
||||
|
||||
@@ -33,7 +71,18 @@ def _close_tab(page, tab_name):
|
||||
|
||||
|
||||
def shunxin_expected_download(page):
|
||||
"""顺心:应到货物数据下载"""
|
||||
"""顺心:应到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"顺心",
|
||||
"应到",
|
||||
lambda: shunxin_expected_download_impl(page),
|
||||
lambda: shunxin_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def shunxin_expected_download_impl(page):
|
||||
"""顺心:应到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【顺心 - 应到货物数据下载】任务...")
|
||||
|
||||
# 初始化并创建下载目录
|
||||
@@ -308,7 +357,18 @@ def shunxin_expected_download(page):
|
||||
|
||||
|
||||
def shunxin_actual_download(page):
|
||||
"""顺心:实到货物数据下载"""
|
||||
"""顺心:实到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"顺心",
|
||||
"实到",
|
||||
lambda: shunxin_actual_download_impl(page),
|
||||
lambda: shunxin_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def shunxin_actual_download_impl(page):
|
||||
"""顺心:实到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【顺心 - 实到货物数据下载】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
|
||||
@@ -9,6 +9,44 @@ import pandas as pd
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
"""异常兜底:flow 失败 → 重置回初始态 → 重试,最多 max_attempts 次(含首次)。
|
||||
|
||||
每次失败都重置(含最终放弃那一次):既是重试前的清场,也保证最终放弃时环境干净。
|
||||
flow 为零参可调用;返回 False 视为失败,其余视为成功。
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
ret = flow()
|
||||
if ret is False:
|
||||
raise RuntimeError("流程返回失败状态")
|
||||
if attempt > 1:
|
||||
print(f">> 【{site_name}-{label}】第 {attempt} 次尝试成功 ✅")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 【{site_name}-{label}】第 {attempt}/{max_attempts} 次失败: {e}")
|
||||
print(f" → 重置【{site_name}】到初始态,清理环境 ...")
|
||||
try:
|
||||
reset()
|
||||
except Exception as re:
|
||||
print(f" ⚠️ 重置异常: {re}")
|
||||
if attempt < max_attempts:
|
||||
continue
|
||||
print(
|
||||
f"❌ 【{site_name}-{label}】已达最大尝试次数 {max_attempts},放弃(环境已清理)。"
|
||||
)
|
||||
|
||||
|
||||
# 站点首页 URL:异常兜底重置用,也供 main_router 的 SITES_CONFIG 引用(单一来源)
|
||||
HOME_URL = "https://ky-sso.yunda56.com"
|
||||
|
||||
|
||||
def yunda_reset(page):
|
||||
"""异常兜底:重置韵达到初始态(跳首页 URL,丢弃当前页面状态,登录态保留)。"""
|
||||
page.goto(HOME_URL)
|
||||
page.wait_for_timeout(1500)
|
||||
|
||||
|
||||
def yunda_login(page):
|
||||
"""韵达自动登录:未登录则填充表单并提交,已登录则跳过。"""
|
||||
print(">> 正在检查韵达登录状态...")
|
||||
@@ -79,7 +117,18 @@ def yunda_smart_menu_click(page, menu_path):
|
||||
|
||||
|
||||
def yunda_expected_download(page):
|
||||
"""韵达:应到货物数据下载"""
|
||||
"""韵达:应到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"韵达",
|
||||
"应到",
|
||||
lambda: yunda_expected_download_impl(page),
|
||||
lambda: yunda_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def yunda_expected_download_impl(page):
|
||||
"""韵达:应到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【韵达 - 应到货物数据下载】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
@@ -318,7 +367,18 @@ def yunda_expected_download(page):
|
||||
|
||||
|
||||
def yunda_actual_download(page):
|
||||
"""韵达:实到货物数据下载"""
|
||||
"""韵达:实到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"韵达",
|
||||
"实到",
|
||||
lambda: yunda_actual_download_impl(page),
|
||||
lambda: yunda_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def yunda_actual_download_impl(page):
|
||||
"""韵达:实到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【韵达 - 实到货物数据下载】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
|
||||
64
site_zto.py
64
site_zto.py
@@ -9,6 +9,44 @@ import pandas as pd
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
"""异常兜底:flow 失败 → 重置回初始态 → 重试,最多 max_attempts 次(含首次)。
|
||||
|
||||
每次失败都重置(含最终放弃那一次):既是重试前的清场,也保证最终放弃时环境干净。
|
||||
flow 为零参可调用;返回 False 视为失败,其余视为成功。
|
||||
"""
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
ret = flow()
|
||||
if ret is False:
|
||||
raise RuntimeError("流程返回失败状态")
|
||||
if attempt > 1:
|
||||
print(f">> 【{site_name}-{label}】第 {attempt} 次尝试成功 ✅")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 【{site_name}-{label}】第 {attempt}/{max_attempts} 次失败: {e}")
|
||||
print(f" → 重置【{site_name}】到初始态,清理环境 ...")
|
||||
try:
|
||||
reset()
|
||||
except Exception as re:
|
||||
print(f" ⚠️ 重置异常: {re}")
|
||||
if attempt < max_attempts:
|
||||
continue
|
||||
print(
|
||||
f"❌ 【{site_name}-{label}】已达最大尝试次数 {max_attempts},放弃(环境已清理)。"
|
||||
)
|
||||
|
||||
|
||||
# 站点首页 URL:异常兜底重置用,也供 main_router 的 SITES_CONFIG 引用(单一来源)
|
||||
HOME_URL = "https://ws.zto56.com/"
|
||||
|
||||
|
||||
def zto_reset(page):
|
||||
"""异常兜底:重置中通到初始态(跳首页 URL,丢弃当前页面状态,登录态保留)。"""
|
||||
page.goto(HOME_URL)
|
||||
page.wait_for_timeout(1500)
|
||||
|
||||
|
||||
def _wait_and_get_frame(page, text_indicator, timeout_ms=20000):
|
||||
"""在主页面与所有 iframe 中查找包含指定文本的窗口"""
|
||||
start_time = datetime.now()
|
||||
@@ -59,7 +97,18 @@ def zto_smart_menu_click(page, menu_path):
|
||||
|
||||
|
||||
def zto_expected_download(page):
|
||||
"""中通:应到货物数据下载"""
|
||||
"""中通:应到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"中通",
|
||||
"应到",
|
||||
lambda: zto_expected_download_impl(page),
|
||||
lambda: zto_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def zto_expected_download_impl(page):
|
||||
"""中通:应到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【中通 - 应到货物数据下载】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
@@ -266,7 +315,18 @@ def zto_expected_download(page):
|
||||
|
||||
|
||||
def zto_actual_download(page):
|
||||
"""中通:实到货物数据下载"""
|
||||
"""中通:实到货物数据下载(内部含异常兜底重试,路由层无感)。"""
|
||||
|
||||
return with_retry(
|
||||
"中通",
|
||||
"实到",
|
||||
lambda: zto_actual_download_impl(page),
|
||||
lambda: zto_reset(page),
|
||||
)
|
||||
|
||||
|
||||
def zto_actual_download_impl(page):
|
||||
"""中通:实到货物数据下载(单次执行,无重试;供自动化测试探测原始结果用)。"""
|
||||
print("\n▶ 开始执行【中通 - 实到货物数据下载】任务...")
|
||||
|
||||
download_dir = DOWNLOAD_DIR
|
||||
|
||||
Reference in New Issue
Block a user