# -*- coding: utf-8 -*- # Author : Charley # Python : 3.12.10 # Date : 2026/09/09 r"""得卡统一部署守护:一键拉起全部常驻任务(采集 + 报告调度 + 运维)+ 崩溃自动重启。 各任务独立进程(采集都已接账号池,多进程共用 20 号池,线程/进程隔离安全)。守护主循环监控 子进程,任一崩溃退出即自动重启。这是部署到服务器的统一入口(放项目根目录,一眼可见)。 任务清单(TASKS): 采集:buy_record(购买记录5家)/ onsale_alert(上架提醒)/ sold_daily(已售每日)/ team(选队随机) 报告:onsale_report(在售报告 每天 09/15/20/01 四次)/ sold_report(已售 日报09:10 + 周报周一09:20 + 月报1号09:30) 运维:maintenance(账号池 cooling 巡检 + 报告 Excel 定时清理)/ replenish(账号池自动补货 每2h) 用法: python start_all.py # 启动全部 python start_all.py buy_record team # 只启动指定任务(名字见 TASKS) Ctrl+C # 优雅终止所有子任务 上线前确认:新目录采集真跑会用账号池 20 号;若旧目录 D:\work\2026-08-02(deca_spider) 生产仍在跑, 需先协调(两边同时跑会重复请求、双倍量)。 """ import os import sys import time import subprocess _ROOT = os.path.dirname(os.path.abspath(__file__)) os.chdir(_ROOT) from loguru import logger logger.remove() logger.add(sys.stderr, format="[{time:HH:mm:ss}] {level} {message}", level="INFO") logger.add("./logs/start_all_{time:YYYYMMDD}.log", encoding="utf-8", rotation="00:00", format="[{time:YYYY-MM-DD HH:mm:ss}] {level} {message}", level="INFO", retention="7 day") # 任务名 -> 启动命令 argv(相对 _ROOT 的脚本 + 参数;spawn 时前置 sys.executable -u)。 # 各脚本自身 bootstrap 会 chdir 到各自所需目录,故这里统一 cwd=_ROOT 即可。 TASKS = { # ---- 常驻采集(已接账号池)---- "buy_record": ["spiders/buy_record_spider.py"], # 购买记录(5 家) "onsale_alert": ["spiders/onsale_alert_spider.py", "13:00"], # 上架提醒(新上架监控推送) "sold_daily": ["spiders/sold_daily_spider.py"], # 已售每日采集(每天 08:00) "team": ["spiders/deca_team_spider.py"], # 选队随机总价(5 分钟/轮) # ---- 报告调度(各自 schedule 常驻)---- "onsale_report": ["reports/onsale_report/deca_on_sale_report.py", "loop"], # 在售报告:每天 09/15/20/01 四次 "sold_report": ["reports/sold_report/run_scheduler.py"], # 已售:日报09:10 + 周报周一09:20 + 月报1号09:30 # ---- 运维 ---- "maintenance": ["spiders/maintenance.py"], # 账号池 cooling 巡检 + Excel 定时清理 "replenish": ["spiders/replenish_accounts.py", "--loop"], # 账号池自动补货:每2h巡检,healthy不足即补(补前先释放dead号占的IP) } RESTART_DELAY = 15 # 子进程崩溃退出后,重启前等待秒数(避免疯狂重启刷屏) def spawn(name: str, argv: list, procs: dict) -> None: """启动一个任务为独立子进程并登记到 procs。 Args: name (str): 任务名(TASKS 的 key)。 argv (list): 相对 _ROOT 的脚本路径 + 参数(不含解释器)。 procs (dict): 进程登记表 name -> Popen,就地更新。 """ p = subprocess.Popen([sys.executable, "-u"] + argv, cwd=_ROOT) procs[name] = p logger.info(f"启动 {name} pid={p.pid} -> {' '.join(argv)}") def main(names: list) -> None: """启动指定任务并守护:子进程崩溃退出即自动重启,Ctrl+C 优雅终止全部。 Args: names (list): 要启动的任务名列表(须是 TASKS 的 key)。 """ procs = {} for name in names: spawn(name, TASKS[name], procs) logger.info(f"已启动 {len(procs)} 个任务,进入守护监控(崩溃自动重启):{names}") try: while True: for name in names: p = procs.get(name) if p and p.poll() is not None: # 子进程已退出(正常结束或崩溃) logger.warning(f"{name} 已退出(returncode={p.returncode}),{RESTART_DELAY}s 后重启") time.sleep(RESTART_DELAY) spawn(name, TASKS[name], procs) time.sleep(5) except KeyboardInterrupt: logger.info("收到中断信号,正在终止所有子任务……") for name, p in procs.items(): try: p.terminate() logger.info(f"已终止 {name} pid={p.pid}") except Exception as e: logger.warning(f"终止 {name} 失败: {e}") if __name__ == "__main__": args = sys.argv[1:] if args: bad = [a for a in args if a not in TASKS] if bad: logger.error(f"未知任务名 {bad},可选: {list(TASKS)}") sys.exit(1) selected = args else: selected = list(TASKS) main(selected)