#!/usr/bin/env python3 # -*- coding: utf-8 -*- """bs_daily_main.py — sanguo-bs-daily (VPS schtask 18:05) 单入口: 串行 bs_eod → bs_fundamentals. 2026-08-20 数据任务重组(用户拍板「bs 每天」合并): 根治当日撞点 —— bs_eod 主循环后 新增指数段后单日跑 5~5.5h(ETA 23:00+), 与旧 sanguo-bs-fund 23:05 重叠 = 同 IP 双 baostock 连接 = 封禁红线(6-24h)。合并进同一 schtask 后两腿先后子进程, 连接天然互斥: leg1 进程退出(finally bs.logout + 进程消亡)连接才彻底释放, leg2 才 login。 编排原则(两脚本幂等/断点/配额逻辑原样复用零改动): - 只做顺序 + 退出码透传 + 时间线日志([BS-DAILY] 腿起止行, 供验收"eod logout 之后 fund 才 login"的直接证据) - leg2 无条件跑: leg1 无论成败, 进程退出即连接释放; fund 是独立作业(自带登录重试/ 日预算), leg1 致命不该连坐饿死当日财务回灌 - 退出码语义沿用两脚本: 0=完成/让路, 1=致命, 2=登录失败, 3=日预算/防线 graceful stop (3 是"按预算收工", 不算失败不上报); 任务级退出码 = 第一个非 OK 腿的码 配额(单 IP 48000/天铁律): wrapper 传 BS_DAILY_LIMIT=15000 收紧 eod 病态重试上限 (常态 ~11.1k 不受影响) + fund 自有 DAILY_CAP=30000 → 最坏 45.1k < 48k。 """ import argparse import logging import subprocess import sys import time from pathlib import Path HERE = Path(__file__).resolve().parent LEGS = ( ("bs_eod", HERE / "bs_eod.py"), ("bs_fund", HERE / "bs_fundamentals.py"), ) OK_CODES = {0, 3} # 3=按预算 graceful stop, 属正常收工 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", handlers=[logging.StreamHandler(sys.stdout)]) log = logging.getLogger(__name__) def run_leg(name, script): t0 = time.time() log.info("[BS-DAILY] leg=%s start script=%s", name, script.name) rc = subprocess.run( [sys.executable, "-X", "utf8", str(script)]).returncode log.info("[BS-DAILY] leg=%s exit=%d 耗时%.0fs", name, rc, time.time() - t0) return rc def main(argv=None): ap = argparse.ArgumentParser() ap.add_argument("--smoke", action="store_true", help="只验 schtask→wrapper→python 链路可达, 两腿不跑(零配额)") args = ap.parse_args(argv) if args.smoke: log.info("[BS-DAILY] smoke ok 编排入口可达") return 0 rc1 = run_leg(*LEGS[0]) if rc1 not in OK_CODES: log.warning("[BS-DAILY] leg1 非常规退出 %d —— 连接已随进程释放, " "fund 独立作业照跑", rc1) rc2 = run_leg(*LEGS[1]) if rc1 not in OK_CODES: return rc1 return 0 if rc2 in OK_CODES else rc2 if __name__ == "__main__": sys.exit(main())