fix: 修复补跑误含 cninfo 三步与时区不一致 (P1-2/P1-3)

- P1-2: 提取 DEFAULT_NEWS_STEPS(全链路去 report+cninfo),定时/补跑/默认三处统一;
  启动补跑不再误执行 cninfo 公告管道
- P1-3: 新增 scheduler/timeutil.py(调度时区统一入口,SCHEDULE_TZ 可覆盖,
  默认 Asia/Shanghai);run_scheduler 定时/补跑/--once、crawler 补跑保护、
  reporter 兜底日期全部改用调度时区,避免系统时区非上海时日期错位
- 新增 6 个测试;全量 282 passed
This commit is contained in:
2026-08-23 05:40:52 +08:00
parent 639d857ffb
commit 7b33182f67
7 changed files with 197 additions and 20 deletions
+2
View File
@@ -7,6 +7,7 @@
"""
from .pipeline import (
DEFAULT_NEWS_STEPS,
STEP_COMMANDS,
STEP_TIMEOUTS,
PipelineResult,
@@ -16,6 +17,7 @@ from .pipeline import (
)
__all__ = [
"DEFAULT_NEWS_STEPS",
"STEP_COMMANDS",
"STEP_TIMEOUTS",
"PipelineResult",
+14 -3
View File
@@ -16,11 +16,13 @@ import os
import subprocess
import time
from dataclasses import dataclass, field
from datetime import date, datetime
from datetime import datetime
from pathlib import Path
from loguru import logger
from .timeutil import today_str
# 断点状态文件(按日期隔离,记录每步骤结果)
DEFAULT_STATE_PATH = Path("data/pipeline/state.json")
@@ -74,6 +76,14 @@ STEP_COMMANDS: dict[str, list[str]] = {
"cninfo_pdf": ["uv", "run", "a-share", "cninfo", "--enrich-pdf", "--pdf-limit", "100"],
}
# 新闻链路默认步骤:全链路除去 report(单独追加)与 cninfo 独立管道(P1-2:
# 补跑/定时任务若误含 cninfo 三步,会重复执行整套公告管道,且 cninfo_crawl
# 无 --date 参数,补跑历史日期时会静默空转)。
DEFAULT_NEWS_STEPS: list[str] = [
k for k in STEP_COMMANDS
if k not in ("report", "cninfo_crawl", "cninfo_extract", "cninfo_pdf")
]
@dataclass
class StepResult:
@@ -223,7 +233,8 @@ def run_step(name: str, date_str: str) -> StepResult:
# 补跑保护:抓取类步骤只能产生当天数据(网站首页只含当前内容,历史文章已滚走),
# 补跑历史日期时跳过抓取并告警,复用已有 data/raw/*/{date_str} 数据。
# 注意:仅保护 crawler; xwlb 走 API 支持任意历史日期,无需跳过。
if name == "crawler" and date_str != date.today().strftime("%Y%m%d"):
# "今天"按调度时区判定(P1-3),避免系统时区与 cron 时区不一致时错判。
if name == "crawler" and date_str != today_str():
started = datetime.now()
logger.warning(
"补跑模式:首页只含当天内容,无法补抓 {};跳过抓取,复用已有 data/raw/*/{}",
@@ -330,7 +341,7 @@ def run_pipeline(
记录,跳过连续成功的步骤,从第一个失败/未执行步骤继续。
state_path: 断点状态文件路径(测试可注入)。
"""
names = steps or [k for k in STEP_COMMANDS if k not in ("report", "cninfo_crawl", "cninfo_extract", "cninfo_pdf")]
names = steps or list(DEFAULT_NEWS_STEPS)
result = PipelineResult(started_at=datetime.now())
state = _load_pipeline_state(state_path)
+4 -1
View File
@@ -1056,7 +1056,10 @@ def generate_report(day_str: str | None = None, *, upload: bool = True) -> int |
`upload` 参数保留以兼容 scheduler/pipeline.py 调用,已无实际作用。
返回 report_id(成功)或 None(无数据/失败)。
"""
day_str = day_str or date.today().strftime("%Y%m%d")
# 兜底日期按调度时区取(P1-3),与 pipeline 传入的 date_str 语义一致
if not day_str:
from .timeutil import today_str
day_str = today_str()
logger.info("生成日报: {}", day_str)
# 收集数据
+36
View File
@@ -0,0 +1,36 @@
"""调度时区工具。
背景(P1-3):
定时任务 cron 触发时区与"今天"的判定必须一致。此前 cron 用
Asia/Shanghai,而 `date.today()` / `datetime.now()` 取系统时区——
若系统时区不是 Asia/Shanghai(如 UTC),07:00 上海(=前一日 23:00 UTC)
触发时 `date.today()` 会返回错误日期,整条 pipeline 落在错日目录。
统一入口:
schedule_tz(): 调度时区(env SCHEDULE_TZ 可覆盖,默认 Asia/Shanghai)
today_str(): 按调度时区返回 YYYYMMDD
"""
from __future__ import annotations
import os
from datetime import datetime
from zoneinfo import ZoneInfo
# 默认调度时区(与 .env 的 SCHEDULE_TIMES 语义一致)
DEFAULT_SCHEDULE_TZ = "Asia/Shanghai"
def schedule_tz() -> ZoneInfo:
"""调度时区(env SCHEDULE_TZ 可覆盖,默认 Asia/Shanghai)。"""
return ZoneInfo(os.environ.get("SCHEDULE_TZ") or DEFAULT_SCHEDULE_TZ)
def now() -> datetime:
"""当前时刻(调度时区,aware)。"""
return datetime.now(schedule_tz())
def today_str() -> str:
"""按调度时区返回今天的 YYYYMMDD。"""
return now().strftime("%Y%m%d")