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:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
# 收集数据
|
||||
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user