Python 自动化接单实战(七):定时报告如何保证同一天只生成一次 本篇复用上一篇回写后的sync.db和下载摘要以load_completed_items()的查询结果作为report_job.py输入浏览器任务尚未完成的业务键不进入日报。新增的报告文件和完成标记又会成为下一篇测试与日志的观察对象。日报风险不在公式而在重复触发、数据晚到或状态未保存。一、用日期与配置生成唯一任务键importhashlibimportjsonfromdatetimeimportdatefrompathlibimportPathimportsqlite3 config{region:east,currency:CNY}rawjson.dumps(config,sort_keysTrue,ensure_asciiFalse)job_keyf{date.today()}-{hashlib.sha256(raw.encode()).hexdigest()[:8]}markerPath(state)/f{job_key}.donemarker.parent.mkdir(exist_okTrue)defload_completed_items(path:Path)-list[tuple[str,str]]:ifnotpath.is_file():raiseFileNotFoundError(先运行 article_076 回写 sync.db)withsqlite3.connect(path)asdb:returndb.execute(select id, payload from items where export_sha256 is not null order by id).fetchall()defmain()-int:ifmarker.exists():print(fskip completed{job_key})return0try:rowsload_completed_items(Path(sync.db))except(OSError,sqlite3.Error)asexc:print(freport_failed type{type(exc).__name__})return2print(frun{job_key}rows{len(rows)})return0if__name____main__:raiseSystemExit(main())输出示例run 2026-07-27-85d2434e配置变化会得到不同任务键同一配置在同一天重复触发则安全退出。二、先写临时文件再原子替换importosfrompathlibimportPath targetPath(reports/daily.csv)stagingtarget.with_suffix(.tmp)target.parent.mkdir(exist_okTrue)staging.write_text(metric,value\norders,42\n,encodingutf-8)os.replace(staging,target)marker.write_text(ok\n,encodingutf-8)运行输出publishedreports/daily.csv markerstate/2026-07-27-85d2434e.done只有报告完整落盘后才创建完成标记。发送邮件或上传网盘属于另一个步骤应记录独立状态避免重新计算报告。三、调度器只负责触发与收集退出码任务应支持手动运行退出码区分完成、已处理和失败。上线前模拟数据源缺失、磁盘不可写与重复触发确认不会留下半份报告。四、为什么原子替换仍不等于业务完成os.replace()保证读者看到旧文件或完整新文件不会看到半份内容但它不保证邮件已经发出也不保证网盘上传成功。生成、发布、通知是三个不同状态若用一个布尔值概括通知失败后的重跑可能重复生成或重复发送。状态表应记录generated_at、published_at和notified_at每一步只推进自己的状态。完成标记必须最后写而且内容应包含输入摘要、配置摘要和报告摘要。只用日期作为任务键会把同日不同区域互相覆盖把排序后的配置摘要加入键既能区分业务范围又不会受 JSON 字段顺序影响。记忆点是原子性保护一扇门状态机保护整条走廊。文件替换解决单次写入状态转换才解决跨系统流程。五、确定性让重跑成为恢复手段相同快照、配置和业务日期应生成相同字节。查询必须显式排序日期与小数格式必须固定报告中不应混入当前运行时间。这样发送失败后可以安全复用已有报告并用摘要证明内容未变。若上游数据迟到应产生新的输入版本而不是偷偷覆盖已验收日报。调度器只负责在约定时间调用命令并收集退出码业务代码自己判断已完成、可重试和永久失败。验收要模拟并发启动、磁盘不足、通知失败和重复触发。下一篇会复用job_key、状态标记和固定数据库样本为这些恢复分支建立测试与结构化事件。并发启动还需要互斥而不能只做marker.exists()后再写标记因为两个进程可能同时看到“不存在”。可用数据库唯一键或操作系统文件锁竞争执行权赢家生成报告输家读取既有状态后退出。锁必须包含持有者、启动时间和过期规则避免机器断电留下永久死锁清理过期锁前还要确认对应进程已不存在。数据晚到则是业务版本问题不应靠调度器不断覆盖当天报告。任务键加入数据截止时间或上游批次号补数会形成新版本并保留旧版摘要通知里明确标注“修订版”。这让客户能够解释两个同日期文件为什么不同也让审计者复现当时采用的数据快照。稳定的版本语义比“每天九点跑一次”更接近真正的报告契约。 本文用任务键、原子文件与完成标记设计了可重复触发的定时报告让生成和发送各自可恢复。 你的定时任务更常遇到重复执行、数据晚到、发送失败还是无人发现 关注《Python 自动化接单实战》下一篇继续用测试与结构化日志降低后续维护成本。参考来源Dev.toGitHub Actions CI/CD ExplainedHacker NewsWe have proof automation now