Python 自动化接单实战(七):定时报告如何保证同一天只生成一次

本篇复用上一篇回写后的 sync.db 和下载摘要,以 load_completed_items() 的查询结果作为 report_job.py 输入;浏览器任务尚未完成的业务键不进入日报。新增的报告文件和完成标记又会成为下一篇测试与日志的观察对象。日报风险不在公式,而在重复触发、数据晚到或状态未保存。

一、用日期与配置生成唯一任务键

python 复制代码
import hashlib
import json
from datetime import date
from pathlib import Path
import sqlite3

config = {"region": "east", "currency": "CNY"}
raw = json.dumps(config, sort_keys=True, ensure_ascii=False)
job_key = f"{date.today()}-{hashlib.sha256(raw.encode()).hexdigest()[:8]}"
marker = Path("state") / f"{job_key}.done"
marker.parent.mkdir(exist_ok=True)

def load_completed_items(path: Path) -> list[tuple[str, str]]:
    if not path.is_file():
        raise FileNotFoundError("先运行 article_076 回写 sync.db")
    with sqlite3.connect(path) as db:
        return db.execute(
            "select id, payload from items where export_sha256 is not null order by id"
        ).fetchall()

def main() -> int:
    if marker.exists():
        print(f"skip completed {job_key}")
        return 0
    try:
        rows = load_completed_items(Path("sync.db"))
    except (OSError, sqlite3.Error) as exc:
        print(f"report_failed type={type(exc).__name__}")
        return 2
    print(f"run {job_key} rows={len(rows)}")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

输出示例:

text 复制代码
run 2026-07-27-85d2434e

配置变化会得到不同任务键,同一配置在同一天重复触发则安全退出。

二、先写临时文件,再原子替换

python 复制代码
import os
from pathlib import Path

target = Path("reports/daily.csv")
staging = target.with_suffix(".tmp")
target.parent.mkdir(exist_ok=True)
staging.write_text("metric,value\norders,42\n", encoding="utf-8")
os.replace(staging, target)
marker.write_text("ok\n", encoding="utf-8")

运行输出:

text 复制代码
published=reports/daily.csv marker=state/2026-07-27-85d2434e.done

只有报告完整落盘后才创建完成标记。发送邮件或上传网盘属于另一个步骤,应记录独立状态,避免重新计算报告。

三、调度器只负责触发与收集退出码

任务应支持手动运行,退出码区分完成、已处理和失败。上线前模拟数据源缺失、磁盘不可写与重复触发,确认不会留下半份报告。

四、为什么原子替换仍不等于业务完成

os.replace() 保证读者看到旧文件或完整新文件,不会看到半份内容,但它不保证邮件已经发出,也不保证网盘上传成功。生成、发布、通知是三个不同状态,若用一个布尔值概括,通知失败后的重跑可能重复生成或重复发送。状态表应记录 generated_atpublished_atnotified_at,每一步只推进自己的状态。

完成标记必须最后写,而且内容应包含输入摘要、配置摘要和报告摘要。只用日期作为任务键会把同日不同区域互相覆盖;把排序后的配置摘要加入键,既能区分业务范围,又不会受 JSON 字段顺序影响。记忆点是:原子性保护一扇门,状态机保护整条走廊。文件替换解决单次写入,状态转换才解决跨系统流程。

五、确定性让重跑成为恢复手段

相同快照、配置和业务日期应生成相同字节。查询必须显式排序,日期与小数格式必须固定,报告中不应混入当前运行时间。这样发送失败后可以安全复用已有报告,并用摘要证明内容未变。若上游数据迟到,应产生新的输入版本,而不是偷偷覆盖已验收日报。

调度器只负责在约定时间调用命令并收集退出码,业务代码自己判断已完成、可重试和永久失败。验收要模拟并发启动、磁盘不足、通知失败和重复触发。下一篇会复用 job_key、状态标记和固定数据库样本,为这些恢复分支建立测试与结构化事件。

并发启动还需要互斥,而不能只做 marker.exists() 后再写标记,因为两个进程可能同时看到"不存在"。可用数据库唯一键或操作系统文件锁竞争执行权,赢家生成报告,输家读取既有状态后退出。锁必须包含持有者、启动时间和过期规则,避免机器断电留下永久死锁;清理过期锁前还要确认对应进程已不存在。

数据晚到则是业务版本问题,不应靠调度器不断覆盖当天报告。任务键加入数据截止时间或上游批次号,补数会形成新版本并保留旧版摘要;通知里明确标注"修订版"。这让客户能够解释两个同日期文件为什么不同,也让审计者复现当时采用的数据快照。稳定的版本语义比"每天九点跑一次"更接近真正的报告契约。


📌 本文用任务键、原子文件与完成标记设计了可重复触发的定时报告,让生成和发送各自可恢复。

💬 你的定时任务更常遇到重复执行、数据晚到、发送失败还是无人发现?

👉 关注《Python 自动化接单实战》,下一篇继续用测试与结构化日志降低后续维护成本。

参考来源

相关推荐
仙逆GPT1 天前
Codex定时任务怎么设置?ChatGPT Plus用Worktree自动巡检项目
chatgpt·定时任务·codex·git worktree·chatgpt plus
CodexDave2 天前
Python 自动化接单实战(九):Windows 免环境交付如何打包与诊断
windows·python·自动化·python自动化·pyinstaller·软件交付·windows打包
CodexDave3 天前
Python 自动化接单实战(一):把 CSV 需求做成配置驱动解析器
java·python·自动化·json·数据清洗·python自动化·csv处理
CodexDave3 天前
Python 自动化接单实战(二):Excel 批量清洗如何把规则与异常分开
python·自动化·excel·python自动化·数据处理·excel清洗·异常报告
CodexDave3 天前
Python 自动化接单实战(四):PDF 批量提取先识别文本层还是扫描件
python·pdf·自动化·ocr·python自动化·文档处理·pdf提取
豆瓣鸡13 天前
XXL-JOB 定时任务——从 Docker 部署到分片广播,分布式任务调度落地
spring boot·分布式·docker·定时任务
Anastasiozzzz15 天前
从单机定时到分布式调度:深度剖析 XXL-JOB 的架构设计、核心机制与生产实战
定时任务
小森林之主2 个月前
秒级定时任务速查手册:核心语法与实用示例
定时任务·cron·定时调度·速查手册·秒级定时
遇事不決洛必達2 个月前
【数据库系列】本地映射云服务器Mysql的方法
服务器·数据库·mysql·定时任务