diff --git a/main_router.py b/main_router.py index 281dab8..84f05f7 100644 --- a/main_router.py +++ b/main_router.py @@ -5,6 +5,8 @@ import yaml import pandas as pd from playwright.sync_api import sync_playwright +from paths import DOWNLOAD_DIR, CONFIG_PATH + # 导入抽离出去的各个网点模块 import site_shunxin import site_baishi @@ -34,7 +36,7 @@ def task_process_undelivered_data(site_name="顺心"): """全局模块:应到未到异常件比对引擎 (支持动态网点前缀)""" print(f"\n▶ 开始执行【{site_name} - 应到未到数据处理】任务...") - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR expected_path = os.path.join(download_dir, f"{site_name}-应到货物数据.xlsx") actual_path = os.path.join(download_dir, f"{site_name}-实到货物数据.xlsx") output_path = os.path.join(download_dir, f"{site_name}-应到未到货物数据.xlsx") @@ -49,8 +51,9 @@ def task_process_undelivered_data(site_name="顺心"): try: print(">> 正在载入本地 Excel 文档...") - df_expected = pd.read_excel(expected_path) - df_actual = pd.read_excel(actual_path) + # 全部按字符串读取并保留空串:避免 18 位运单号被当成浮点数丢精度,也避免空单元格变成 NaN。 + df_expected = pd.read_excel(expected_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: print("❌ 核心资产校验失败:数据源中缺失【运单号】字段,请检查导出配置。") @@ -58,8 +61,16 @@ def task_process_undelivered_data(site_name="顺心"): print(">> 正在启动多维数据集比对引擎...") + # 统一运单号为去空白字符串,消除 int/float 与 str 混读导致 isin 永不命中的隐患 + df_expected["运单号"] = df_expected["运单号"].astype(str).str.strip() + df_actual["运单号"] = df_actual["运单号"].astype(str).str.strip() + + # 剔除空白运单号,避免空值被误判为“应到未到” + df_expected = df_expected[df_expected["运单号"] != ""] + actual_set = set(df_actual["运单号"]) - {""} + # Left Anti-Join:在应到中找出不存在于实到里的运单号 - df_undelivered = df_expected[~df_expected["运单号"].isin(df_actual["运单号"])] + df_undelivered = df_expected[~df_expected["运单号"].isin(actual_set)] target_columns = ["班次号", "交接单号", "运单号"] available_columns = [ @@ -86,8 +97,8 @@ def run_multi_site_daemon(): debug_mode = False debug_target = "" try: - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) debug_mode = config.get("debug", {}).get("enabled", False) debug_target = config.get("debug", {}).get("target_site", "") @@ -191,7 +202,7 @@ def run_multi_site_daemon(): if read_btns.nth(i).is_visible(timeout=500): read_btns.nth(i).click() handled_any = True - except: + except Exception: pass try: if bs_page.locator("button:has-text('关 闭')").is_visible( @@ -199,7 +210,7 @@ def run_multi_site_daemon(): ): bs_page.locator("button:has-text('关 闭')").click() handled_any = True - except: + except Exception: pass if not handled_any: break diff --git a/paths.py b/paths.py new file mode 100644 index 0000000..bdef9d7 --- /dev/null +++ b/paths.py @@ -0,0 +1,15 @@ +# paths.py +# 统一的路径锚点:所有路径都以本项目所在目录为基准,避免依赖运行时的工作目录(cwd)。 +# 这样无论从哪个目录启动脚本(IDE / 命令行 / 计划任务 / 双击), +# 下载目录与配置文件都能稳定定位,不会出现“文件落到别处”或“读不到密码”的隐蔽故障。 + +import os + +# 项目根目录(以本文件所在位置为基准,与从哪个目录启动脚本无关) +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# 统一的下载 / 输出目录 +DOWNLOAD_DIR = os.path.join(BASE_DIR, "downloads") + +# 统一的配置文件路径(注意:config.yaml 需与本项目脚本放在同一目录下) +CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml") diff --git a/site_baishi.py b/site_baishi.py index 5c215bb..392da3f 100644 --- a/site_baishi.py +++ b/site_baishi.py @@ -4,12 +4,14 @@ import os import yaml from playwright.sync_api import sync_playwright +from paths import DOWNLOAD_DIR, CONFIG_PATH + def baishi_download_undelivered_data(page): """百世:一键提取应到未到(当日未扫)数据""" print("\n▶ 开始执行【百世 - 一键提取应到未到数据】任务...") - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -60,7 +62,7 @@ def baishi_download_undelivered_data(page): print(">> 正在读取本地配置文件并进行授权验证...") password = "" try: - with open("config.yaml", "r", encoding="utf-8") as f: + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) password = config.get("baishi", {}).get("password", "") diff --git a/site_shunxin.py b/site_shunxin.py index 6f7b6aa..88ec5ad 100644 --- a/site_shunxin.py +++ b/site_shunxin.py @@ -5,6 +5,8 @@ import re from datetime import datetime import pandas as pd +from paths import DOWNLOAD_DIR + def _close_tab(page, tab_name): """关闭指定名称的标签页(Ant Design Tabs)。 @@ -34,7 +36,7 @@ def shunxin_expected_download(page): print("\n▶ 开始执行【顺心 - 应到货物数据下载】任务...") # 初始化并创建下载目录 - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) print(f">> 已创建专属下载文件夹: {download_dir}") @@ -189,10 +191,13 @@ def shunxin_expected_download(page): ).click() download = download_info.value - save_path = os.path.join(download_dir, download.suggested_filename) + # 用带微秒的时间戳生成唯一临时文件名,避免同名任务相互覆盖导致丢数据 + safe_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + custom_filename = f"顺心_temp_{safe_timestamp}.xlsx" + save_path = os.path.join(download_dir, custom_filename) download.save_as(save_path) downloaded_files.append(save_path) - print(f" ⬇️ 文件已落盘: downloads/{download.suggested_filename}") + print(f" ⬇️ 文件已落盘: downloads/{custom_filename}") except Exception as e: print(f" ❌ 下载任务 [{time_str}] 失败: {e}") @@ -202,7 +207,7 @@ def shunxin_expected_download(page): all_data_frames = [] for file_path in downloaded_files: try: - df = pd.read_excel(file_path) + df = pd.read_excel(file_path, dtype=str) if not df.empty: all_data_frames.append(df) except Exception as e: @@ -233,7 +238,7 @@ def shunxin_actual_download(page): """顺心:实到货物数据下载""" print("\n▶ 开始执行【顺心 - 实到货物数据下载】任务...") - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -358,10 +363,13 @@ def shunxin_actual_download(page): ).click() download = download_info.value - save_path = os.path.join(download_dir, download.suggested_filename) + # 用带微秒的时间戳生成唯一临时文件名,避免同名任务相互覆盖导致丢数据 + safe_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + custom_filename = f"顺心_temp_{safe_timestamp}.xlsx" + save_path = os.path.join(download_dir, custom_filename) download.save_as(save_path) downloaded_files.append(save_path) - print(f" ⬇️ 文件已落盘: downloads/{download.suggested_filename}") + print(f" ⬇️ 文件已落盘: downloads/{custom_filename}") except Exception as e: print(f" ❌ 下载任务 [{time_str}] 失败: {e}") @@ -371,7 +379,7 @@ def shunxin_actual_download(page): all_data_frames = [] for file_path in downloaded_files: try: - df = pd.read_excel(file_path) + df = pd.read_excel(file_path, dtype=str) if not df.empty: all_data_frames.append(df) except Exception as e: diff --git a/site_yunda.py b/site_yunda.py index aa05752..efd902b 100644 --- a/site_yunda.py +++ b/site_yunda.py @@ -6,6 +6,8 @@ import yaml from datetime import datetime, timedelta import pandas as pd +from paths import DOWNLOAD_DIR, CONFIG_PATH + def yunda_login(page): """ @@ -26,8 +28,8 @@ def yunda_login(page): # 从配置读取凭证(默认空串;真实凭据仅存于被忽略的 config.yaml) username = "" password = "" - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} yd_cfg = config.get("yunda", {}) username = str(yd_cfg.get("username", "")) @@ -91,7 +93,7 @@ def yunda_expected_download(page): print("\n▶ 开始执行【韵达 - 应到货物数据下载】任务...") target_task_title = "进站主单表" - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -116,8 +118,8 @@ def yunda_expected_download(page): # 2. 从配置文件中解析并计算绝对日期跨度 query_days = 1 try: - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} query_days = int(config.get("yunda", {}).get("query_days", 1)) except Exception as e: @@ -307,7 +309,7 @@ def yunda_actual_download(page): print("\n▶ 开始执行【韵达 - 实到货物数据下载】任务...") target_task_title = "扫描记录数据" - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -331,8 +333,8 @@ def yunda_actual_download(page): query_days = 1 try: - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} query_days = int(config.get("yunda", {}).get("query_days", 1)) except Exception: @@ -578,7 +580,7 @@ def _yunda_poll_and_download_tasks( ".el-icon-close" ).click() print(" ✅ 【导出服务】工作区已安全关闭。") - except: + except Exception: pass if downloaded_files: @@ -589,7 +591,7 @@ def _yunda_poll_and_download_tasks( df = pd.read_excel(file_path, dtype=str) if not df.empty: all_dfs.append(df) - except: + except Exception: pass if all_dfs: diff --git a/site_zto.py b/site_zto.py index 04c6769..5d2bee8 100644 --- a/site_zto.py +++ b/site_zto.py @@ -6,6 +6,8 @@ import yaml from datetime import datetime import pandas as pd +from paths import DOWNLOAD_DIR, CONFIG_PATH + def _wait_and_get_frame(page, text_indicator, timeout_ms=20000): """动态雷达探测器:全域扫描所有视窗""" @@ -14,14 +16,14 @@ def _wait_and_get_frame(page, text_indicator, timeout_ms=20000): try: if page.get_by_text(text_indicator).count() > 0: return page - except: + except Exception: pass for frame in page.frames: try: if frame.get_by_text(text_indicator).count() > 0: return frame - except: + except Exception: pass page.wait_for_timeout(300) @@ -49,7 +51,7 @@ def zto_expected_download(page): print("\n▶ 开始执行【中通 - 应到货物数据下载】任务...") target_task_title = "进站交接单查询-运单信息" - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -67,8 +69,8 @@ def zto_expected_download(page): # 读取 YAML 配置设定天数 query_days = 1 try: - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} query_days = int(config.get("zto", {}).get("query_days", 1)) except Exception as e: @@ -146,7 +148,7 @@ def zto_expected_download(page): page.locator(".mini-tab", has_text="进站交接单查询").locator( ".mini-tab-close" ).click() - except: + except Exception: pass return else: @@ -155,7 +157,7 @@ def zto_expected_download(page): page.locator(".mini-tab", has_text="进站交接单查询").locator( ".mini-tab-close" ).click() - except: + except Exception: pass return else: @@ -257,7 +259,7 @@ def zto_actual_download(page): print("\n▶ 开始执行【中通 - 实到货物数据下载】任务...") target_task_title = "到件扫描管理" - download_dir = os.path.join(os.getcwd(), "downloads") + download_dir = DOWNLOAD_DIR if not os.path.exists(download_dir): os.makedirs(download_dir) @@ -275,8 +277,8 @@ def zto_actual_download(page): # 2. 读取 YAML 并设定时间范围 query_days = 1 try: - if os.path.exists("config.yaml"): - with open("config.yaml", "r", encoding="utf-8") as f: + if os.path.exists(CONFIG_PATH): + with open(CONFIG_PATH, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} query_days = int(config.get("zto", {}).get("query_days", 1)) except Exception: @@ -344,7 +346,7 @@ def zto_actual_download(page): page.locator(".mini-tab", has_text="到件扫描监控").locator( ".mini-tab-close" ).click() - except: + except Exception: pass return