1. 当一份日报只剩下日期:这个标题到底在说什么
看到"AI 日报 — 2026-09-21"这个标题,第一反应可能是:这不就是个日期吗?正文空的,关键词空的,摘要也空的。但恰恰是这种"三空"状态,反而暴露了一个非常典型的真实场景——日报类内容的自动化生产与归档。
我在过去几年里帮不少团队搭过内部信息聚合系统,也维护过几个面向特定圈子的资讯简报。这类"XX日报 + 日期"的命名方式,几乎成了自动化流水线的标准产物:每天定时抓取、筛选、排版、归档,文件名或标题就是"主题 + 日期"。所以这个标题背后真正值得聊的,不是"AI日报"这四个字有多新鲜,而是一套能稳定跑起来、不翻车、可回溯的日报生成机制。
它解决的核心问题很具体:信息源太多、人工筛选太慢、格式不统一、历史版本找不到。适合谁来参考?三类人最有用——一是想给自己或小团队做每日信息简报的技术同学;二是需要定期输出行业动态、但又不想每天手动复制粘贴的运营或研究岗;三是已经在跑自动化脚本、但经常遇到"今天抓空了""格式乱了""旧文件被覆盖了"这些破事的人。
我先把结论摆前面:日报系统的难点从来不在"抓取",而在"筛选规则的设计"和"归档结构的稳定性"。抓取是最容易的部分,随便一个请求加解析就能拿到一堆内容;真正让人头疼的是,怎么让每天产出的东西质量稳定、怎么让三个月后还能翻出某一天的内容、怎么在源站改版时不至于整个流程崩掉。下面我按实际搭建的顺序,把这套东西拆开讲。
2. 日报系统的骨架:从触发到落盘的完整链路
2.1 为什么定时触发要用"错峰 + 重试"而不是整点执行
很多人第一版都会写个整点定时任务,比如每天 08:00 跑。实测下来问题很明显:如果多个任务都挤在整点,资源争抢、接口限流、甚至脚本互相阻塞都会出现。我的做法是把触发时间错开到非整点,比如 07:43 或 08:17,并且加一层重试。
重试不是简单粗暴地循环三次,而是要有退避策略。我一般用指数退避:第一次失败等 30 秒,第二次等 2 分钟,第三次等 10 分钟。这样做的理由是,很多抓取失败是临时性的(对方服务抖动、网络瞬断),立刻重试大概率还是失败,等一会儿反而能成。
import time import random def fetch_with_retry(task, max_retry=3): delays = [30, 120, 600] for attempt in range(max_retry): try: return task() except Exception as e: if attempt == max_retry - 1: raise # 加一点随机抖动,避免多个任务同时重试 jitter = random.uniform(0, 10) time.sleep(delays[attempt] + jitter)提示:重试次数不要设太多。超过三次还失败,基本说明是源站结构变了或者被封了,继续重试只是浪费时间,应该直接告警。
2.2 抓取层:为什么我坚持"先存原始数据再解析"
新手最容易犯的错,是抓到页面直接解析、解析完就丢掉原始内容。我踩过这个坑:某次源站改版,解析规则全废,想回头重新解析历史数据,结果发现原始 HTML 根本没存,只能干瞪眼。
正确做法是两段式:第一段只负责把原始响应(HTML、JSON、RSS)原封不动落盘,按日期分目录存;第二段再从原始数据里解析出结构化字段。这样即使解析逻辑改了,也能对历史数据重新跑一遍,不用重新抓。
目录结构我一般这样设计:
data/ raw/ 2026-09-21/ source_a.html source_b.json parsed/ 2026-09-21/ items.json reports/ 2026-09-21.md这个结构的好处是按日期天然分区,找某一天的数据直接进对应目录,不用在几万条记录里翻。而且 raw 和 parsed 分离,清理时也能分别处理——raw 占空间大,可以只保留 30 天;parsed 体积小,长期留着做趋势分析。
2.3 解析层:字段设计决定了后面所有环节的天花板
解析出来的每条内容,我建议至少包含这几个字段,缺一个后面都会难受:
| 字段 | 类型 | 说明 | 为什么必须有 |
|---|---|---|---|
| id | string | 内容唯一标识 | 去重、回溯、更新都靠它 |
| title | string | 标题 | 展示和检索的核心 |
| url | string | 原文链接 | 溯源、核实 |
| source | string | 来源标识 | 按来源筛选和统计 |
| published_at | datetime | 发布时间 | 排序、时效过滤 |
| fetched_at | datetime | 抓取时间 | 排查延迟问题 |
| summary | string | 摘要 | 日报正文素材 |
| tags | list | 标签 | 分类和聚合 |
| score | float | 相关度评分 | 排序和筛选 |
其中id 的生成方式很关键。我一般用source + 原始url的哈希或者源站自带的唯一标识。千万别用标题做 id,因为标题可能重复、可能微调,会导致去重失效。
2.4 落盘与归档:为什么文件名要用 ISO 日期而不是"9月21日"
标题里那个"2026-09-21"就是标准 ISO 日期格式,这不是随便写的。用 ISO 格式(YYYY-MM-DD)的好处是字符串排序等于时间排序,文件列表天然按时间排列,脚本处理时不用额外解析。如果用"9月21日"这种,排序会乱,跨年跨月更是灾难。
归档时我还会额外维护一个索引文件,记录每天日报的元信息:条数、来源分布、生成时间、是否有异常。这个索引在排查"某天日报为什么是空的"时特别有用,一眼就能看出是抓取失败还是筛选规则把内容全过滤了。
3. 筛选规则:日报质量的分水岭
3.1 为什么"全量罗列"是最差的选择
我见过不少日报,就是把当天抓到的所有内容按时间倒序排一遍,几十条堆上去。这种日报没人看,因为信息密度太低,读者要自己从噪音里找信号。日报的价值在于"替读者做减法",而不是"把找到的都塞给他"。
筛选的核心逻辑是:先做硬性过滤,再做软性排序,最后做数量控制。硬性过滤是去重、去广告、去明显不相关的;软性排序是按相关度、时效、来源权重打分;数量控制是最终只保留 Top N,比如 10 到 15 条。
3.2 相关度评分:一个能落地的简单算法
评分不用搞得太复杂,我常用的是一套加权公式,实测效果够用:
score = w1 * keyword_match + w2 * source_weight + w3 * freshness + w4 * engagement- keyword_match:标题和摘要里命中关键词的数量,归一化到 0-1
- source_weight:来源权重,优质源给 1.0,普通源给 0.5,这个需要人工维护一个源清单
- freshness:时效分,越新越高,可以用
1 / (1 + 小时差/24)这种衰减函数 - engagement:如果有互动数据(阅读、点赞、评论),可以纳入,没有就设 0
权重我一般设 w1=0.4, w2=0.3, w3=0.2, w4=0.1。这个配比不是拍脑袋,是因为关键词匹配最能反映"是否相关",来源权重反映"是否可信",时效反映"是否新鲜",三者重要性依次递减。
注意:关键词匹配不要用简单的字符串包含,中文要做分词,英文要注意词形还原。否则"AI"会匹配到"said"里的"ai",闹笑话。
3.3 去重:为什么"标题完全相同"远远不够
去重是日报系统里最容易被低估的环节。同一件事,不同源站的标题可能完全不同,比如"某模型发布新版本"和"XX 公司推出升级版模型",字符串比对根本发现不了。
我的做法是三层去重:
- 精确去重:url 或 id 完全相同,直接去掉
- 近似去重:标题做归一化(去标点、转小写、去停用词)后算相似度,超过阈值(比如 0.85)视为重复
- 语义去重:对摘要做向量化,算余弦相似度,超过阈值视为同一事件
前两层能解决 90% 的问题,第三层成本高,只在内容量大、重复严重时才上。相似度计算可以用简单的编辑距离或 Jaccard 相似度,不一定非要上模型。
3.4 数量控制与"保底机制"
最终输出条数我一般控制在 10-15 条。太少显得单薄,太多读者看不完。但这里有个坑:如果某天优质内容不足,硬凑数量会拉低质量;如果某天内容爆炸,砍到 15 条又可能漏掉重要的。
我的处理是设一个"保底 + 弹性"机制:保底 8 条,上限 20 条。如果筛选后不足 8 条,就放宽阈值补足;如果超过 20 条,就提高阈值压缩。同时给每条打上"必读"和"可选"标记,必读的永远保留,可选的按分数取舍。
4. 日报正文的生成:从数据到可读文本
4.1 模板设计:为什么结构要固定但内容要灵活
日报正文我坚持用固定模板,因为固定结构让读者形成阅读习惯,知道去哪找什么。但固定不等于死板,模板里要留出灵活空间。
我常用的结构是这样的:
# AI 日报 — 2026-09-21 ## 今日要闻 (3-5 条最重要的,每条 2-3 句话) ## 行业动态 (按主题分组的若干条) ## 值得一读 (深度内容、长文、报告) ## 数据速览 (如果有可量化的指标)"今日要闻"是编辑重点,需要人工或规则挑出最重要的几条;"行业动态"可以按标签自动分组;"值得一读"放那些不适合快速浏览但价值高的内容。
4.2 摘要生成:抽取式还是生成式
摘要这块,我的建议是优先用抽取式,谨慎用生成式。抽取式就是从原文里挑出关键句,优点是绝对不会编造、不会失真;生成式虽然读起来更顺,但有幻觉风险,日报这种要求准确性的场景,一条编造的信息就可能毁掉整个日报的可信度。
抽取式的实现可以用 TextRank 或简单的关键词句打分:把原文分句,给每句按"包含关键词数量 + 位置权重(首句权重高)+ 长度适中"打分,取最高分的 1-2 句。这个方案简单、可控、不会出错。
如果确实要用生成式做润色,一定要加一道事实校验:生成的内容里出现的数字、名称、时间,必须能在原文里找到对应,找不到就退回抽取式结果。
4.3 排版细节:那些让日报"看起来专业"的小事
排版这事看着小,但直接影响阅读体验。我总结几个实测有效的细节:
- 每条内容之间留空行,不要挤在一起
- 标题加粗,但不要整段加粗
- 链接用文字锚点,不要直接贴长 url
- 来源标注统一格式,比如统一放在末尾的括号里
- 日期格式全文统一,要么全用 2026-09-21,要么全用 9月21日,别混
还有一个容易忽略的:移动端适配。很多人只在电脑上看日报,但实际很多读者是在手机上看。所以段落别太长,表格别太宽,代码块要能横向滚动。
5. 稳定性与可维护性:让系统能跑一年的关键
5.1 源站改版是必然事件,怎么把影响降到最低
我做过的日报系统,没有一个能逃过源站改版的。区别只在于:有的改版后半小时就恢复了,有的改版后一周都没发现日报已经空了。
关键在监控。我一般加三层检查:
- 抓取量监控:每天抓到的条数,如果比过去 7 天均值低 50% 以上,告警
- 解析成功率监控:解析出的有效字段比例,如果低于 80%,告警
- 内容质量监控:随机抽几条人工看,或者用规则检查(比如标题是否为空、url 是否有效)
告警不用搞得太复杂,发个消息到群里或者邮件就行。重点是要有人看到,否则监控等于没有。
5.2 配置与代码分离:改规则不该动代码
筛选阈值、来源权重、关键词列表这些东西,一定要放在配置文件里,不要硬编码在代码里。因为这些东西会频繁调整,每次调都改代码、重新部署,效率太低。
我一般用一个 YAML 或 JSON 配置文件:
sources: - name: source_a weight: 1.0 enabled: true - name: source_b weight: 0.5 enabled: true filter: min_score: 0.3 max_items: 20 min_items: 8 keywords: - 大模型 - 多模态 - 推理这样调整规则时只改配置,代码不用动,风险也小。
5.3 历史数据的价值:别把旧日报当垃圾
很多人日报生成完就不管了,旧文件堆在那占空间。其实历史日报是很有价值的:可以做趋势分析(某个话题的热度变化)、可以做回溯检索(三个月前那条重要消息是哪天发的)、可以做质量对比(现在的筛选规则比半年前好吗)。
所以我在归档时会额外做两件事:一是把每天的 parsed 数据存成结构化格式(JSON),方便后续分析;二是维护一个全局索引,记录每天的关键词分布和条数,做趋势时直接读索引就行。
6. 我踩过的几个真实坑
6.1 时区问题:为什么"今天"的日报里混进了昨天的内容
这个坑我踩过两次。第一次是服务器时区和源站时区不一致,导致"今天"的边界判断错了;第二次是发布时间和抓取时间混用,排序时把旧内容排到了前面。
解决办法是统一用 UTC 存储,展示时再转本地时区。所有时间字段都存 UTC,比较和排序也用 UTC,只在最终生成日报时转成读者所在时区。这样不管服务器在哪、读者在哪,逻辑都不会乱。
6.2 编码问题:中文乱码的几种成因
中文乱码看着简单,成因其实挺多:响应头没声明编码、HTML meta 里的编码和实际不符、解析时用了错误的编码。我的经验是不要相信任何声明,直接检测。用 chardet 之类的库检测实际编码,或者干脆先按 UTF-8 解,解出来有乱码再试 GBK。
还有一个隐蔽的坑:有些源站返回的内容里混了多种编码,整体检测会失败。这种情况只能按段落分别处理,或者直接放弃这个源。
6.3 空日报:最尴尬的故障
某天早上打开日报,发现是空的——这种故障最尴尬,因为读者会以为系统挂了。空日报的成因通常有三个:抓取全失败、筛选阈值太高把内容全过滤了、解析规则失效导致字段全空。
我的处理是加一个"空日报保护":如果最终条数为 0,不要直接输出空日报,而是输出一条说明,告诉读者"今日无符合条件的内容",同时触发告警。这样至少读者知道系统还在跑,只是今天确实没内容。
7. 关于这套系统,我个人的几点体会
搭日报系统这事,技术难度其实不高,难的是持续维护的耐心。我见过太多人第一版做得漂漂亮亮,跑了两周就荒废了,原因无非是源站改版没及时修、筛选规则没持续调、内容质量下滑没人管。
我的建议是:先把最小可用版本跑起来,别一上来就追求完美。第一版只要能抓、能筛、能输出就行,哪怕只有三个源、只有十条内容。跑起来之后,根据实际阅读体验慢慢调,比一开始憋大招要靠谱得多。
另外,日报的"编辑感"很重要。纯自动生成的日报,读起来总差点意思。我现在会在自动生成的基础上,每天花五分钟人工过一遍,调整一下顺序、补一句点评、删掉明显不合适的。这五分钟的投入,能让日报的可读性提升一大截。
最后分享一个小技巧:给日报加一个"昨日回顾"或"本周精选"的入口。读者看日报是碎片化的,容易漏掉重要内容。加一个回顾入口,让错过的人能补上,日报的长期价值会高很多。这个功能实现起来也简单,就是把历史 parsed 数据按时间范围聚合一下,成本很低但很实用。