| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- # -*- 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)
|