1. 当"日报"变成一种自动化流水线:我为什么开始折腾这件事
每天早上八点半,我端着咖啡坐到工位,第一件事不是看邮件,而是打开十几个信息源,把过去24小时里值得关注的AI动态一条条筛出来,整理成一份能直接发给团队看的日报。这件事我坚持了快两年,前一年半全靠手动,后半年开始用自动化工具辅助。说实话,手动整理日报这件事,看起来简单,实际上极其消耗精力——信息源分散、质量参差不齐、重复内容多、时效性要求高,而且你还得判断哪些是真正重要的,哪些只是噪音。
后来我意识到,与其每天花一个多小时做机械性的信息聚合,不如把这套流程工程化。于是就有了这个"AI日报自动化生成"的项目。它的核心目标很明确:把多源信息采集、去重、摘要、分类、排版这几个环节串成一条自动化流水线,最终输出一份结构清晰、可直接分发的日报文档。适合谁来参考?如果你也在做类似的信息聚合工作,比如行业简报、竞品动态追踪、技术周报,或者你单纯想学一套"采集-处理-输出"的自动化思路,这篇内容应该能给你不少可直接抄作业的东西。
需要先说明一点:这篇分享里涉及的所有工具选型、参数配置、代码片段,都是我在实际跑通这套流程之后沉淀下来的方案。有些地方我踩过坑,有些地方我换过三四套方案才找到相对稳定的做法,这些经验我会在对应章节里展开讲。另外,文中提到的具体信息源、项目名称、团队信息我都做了脱敏处理,用代称代替,你理解成"某类信息源"就行。
2. 整条流水线的骨架:从信息源到成品的五个环节
2.1 为什么是五个环节而不是三个
很多人做信息聚合,第一反应是"采集+输出"两步走。我一开始也是这么想的,结果跑了两周就发现根本行不通。问题出在中间:原始信息里充斥着重复内容、低质量内容、格式混乱的内容,如果不在中间加处理层,最终输出的日报会非常难看,读者根本不愿意看。
所以我最终把整条流水线拆成了五个环节:信息源采集、内容清洗与去重、智能摘要与分类、结构化排版、分发与归档。这五个环节各自独立,通过标准化的数据格式串联。这样设计的好处是,任何一个环节出问题,我只需要替换那一个环节,不用动整条链路。
具体来说,每个环节的职责是这样的:
| 环节 | 核心职责 | 输出物 | 常见失败模式 |
|---|---|---|---|
| 采集 | 从多个信息源拉取原始内容 | 原始条目列表(JSON) | 源站改版导致解析失败 |
| 清洗去重 | 去除广告、重复、低质内容 | 干净条目列表 | 去重阈值设置不当 |
| 摘要分类 | 生成摘要、打标签 | 带摘要和标签的条目 | 摘要质量不稳定 |
| 排版 | 按模板生成日报文档 | Markdown/HTML文档 | 模板变量缺失 |
| 分发归档 | 推送并存储历史版本 | 归档文件+推送记录 | 推送失败无重试 |
这张表是我在跑了三个月之后总结出来的,每一条失败模式我都至少遇到过一次。后面会逐个展开。
2.2 数据格式的统一是整个流水线的地基
在动手写任何代码之前,我做的第一件事是定义中间数据格式。这个决定后来被证明是整个项目里最正确的一个。我定义了一个统一的条目结构,所有环节都围绕这个结构来读写:
{ "id": "唯一标识,用于去重", "source": "来源标识", "title": "条目标题", "url": "原始链接", "raw_content": "原始正文", "clean_content": "清洗后正文", "summary": "生成的摘要", "tags": ["标签1", "标签2"], "published_at": "发布时间戳", "collected_at": "采集时间戳", "score": 0.0 }为什么要这么设计?因为去重需要id,摘要需要clean_content,分类需要tags,排序需要score和published_at。如果每个环节各自定义格式,环节之间的对接就会变成一场灾难。我见过太多项目死在"格式不统一"这件事上——采集模块输出一种结构,处理模块期望另一种结构,中间加一层转换代码,转换代码又引入新的bug,最后整个链路脆弱得不堪一击。
提示:定义中间格式时,宁可多留几个字段,也不要少留。多出来的字段不用不占什么成本,但少一个字段可能导致某个环节无法实现,到时候回头改格式,所有环节都得跟着改。
2.3 调度策略:为什么我放弃了"实时"改成了"定时批量"
一开始我追求实时性,想着信息一出来就立刻采集、立刻处理。跑了一周我就放弃了。原因有三个:第一,很多信息源本身就不是实时的,你实时去拉也是拉到旧内容;第二,实时处理意味着每个条目都要单独走一遍完整流水线,资源消耗大且难以批量化优化;第三,日报本身就是一个"日"级别的产物,追求秒级实时没有意义。
最终我采用的是定时批量策略:每天固定两个时间点触发采集(早上六点和下午两点),采集完成后统一走后续流程。这样做的另一个好处是,批处理可以做更复杂的去重和排序,因为你有全量数据在手,而不是一条一条孤立地处理。
调度工具我用的是系统自带的定时任务,没有引入额外的调度框架。对于这种一天跑几次的场景,引入重量级调度框架纯属杀鸡用牛刀。配置大概长这样:
# 每天早上6点和下午2点触发采集 0 6 * * * /path/to/collect.sh >> /var/log/ai_daily.log 2>&1 0 14 * * * /path/to/collect.sh >> /var/log/ai_daily.log 2>&1日志重定向这个细节很重要,我一开始没加,出了问题完全不知道哪里错了,排查起来非常痛苦。
3. 采集环节:多源信息怎么拉才不容易断
3.1 信息源分类与对应的采集策略
我把信息源分成了三类,每类用不同的采集策略:
第一类是结构化API源。这类源提供标准的接口,返回JSON或XML,采集最省心。你只需要处理分页、限流、鉴权这几个问题。我的做法是给每个API源写一个独立的适配器,适配器负责把API返回的数据转换成统一的条目格式。
第二类是RSS/Atom订阅源。这类源格式相对规范,用现成的解析库就能处理。但坑在于,很多源的RSS输出不完整,只有摘要没有全文,或者更新频率不稳定。我的处理方式是:RSS只用来发现新条目,拿到链接后再去抓取全文。
第三类是网页抓取源。这类源最麻烦,因为页面结构随时可能变。我的策略是尽量少用这类源,如果非用不可,就针对每个源写专门的解析规则,并且加上监控——一旦解析结果为空或者数量异常,立刻告警。
三类源的对比:
| 源类型 | 稳定性 | 开发成本 | 维护成本 | 推荐优先级 |
|---|---|---|---|---|
| 结构化API | 高 | 低 | 低 | 最高 |
| RSS/Atom | 中 | 低 | 中 | 高 |
| 网页抓取 | 低 | 高 | 高 | 谨慎使用 |
3.2 限流与重试:不做好这两件事,采集迟早出问题
限流这件事,我吃过亏。早期我并发拉取多个源,结果被某个源直接封了IP,导致连续三天采集不到数据。后来我加了限流机制:每个源独立维护一个请求队列,队列里控制并发数和请求间隔。
具体参数上,我的经验值是:同一域名下的请求,间隔不低于1秒,并发不超过2个。这个值不是拍脑袋定的,是我观察了多个源的响应情况后总结的。大部分源在这个频率下不会触发限流,同时采集速度也还能接受。
重试机制同样重要。网络请求失败是常态,不能因为一次失败就放弃。我的重试策略是指数退避:第一次失败等2秒重试,第二次等4秒,第三次等8秒,最多重试3次。超过3次就记录失败日志,等下一轮采集再试。
import time import requests def fetch_with_retry(url, max_retries=3): for attempt in range(max_retries): try: resp = requests.get(url, timeout=15) if resp.status_code == 200: return resp.text except requests.RequestException as e: wait = 2 ** (attempt + 1) print(f"请求失败,{wait}秒后重试: {e}") time.sleep(wait) return None这段代码看起来简单,但timeout这个参数千万别省。我遇到过好几次因为没设超时,某个请求卡死导致整个采集流程阻塞的情况。
3.3 采集结果的落盘与幂等性保证
采集到的数据我统一落盘成JSON文件,按日期分目录存放。为什么要落盘而不是直接进内存处理?因为落盘之后,后续环节可以独立重跑,不用重新采集。这在调试阶段特别有用——你改摘要逻辑的时候,不需要每次都重新拉一遍数据。
落盘时有个关键点:幂等性。同一轮采集如果跑了两次,不应该产生重复数据。我的做法是用条目的id做文件名或者做去重键,写入前先检查是否已存在。id的生成规则我用的是来源标识+原始链接的哈希,这样即使同一内容从不同源采集到,只要链接相同就能识别出来。
注意:有些源的链接会带跟踪参数(比如
?utm_source=xxx),这些参数会导致同一内容产生不同的链接。我的处理是在生成id之前先清洗掉常见的跟踪参数,只保留核心路径。
4. 清洗与去重:把噪音挡在摘要之前
4.1 正文提取:为什么不能直接用原始HTML
原始HTML里包含大量噪音:导航栏、侧边栏、广告、评论区、相关推荐。如果直接把这些内容丢给摘要环节,摘要质量会惨不忍睹。所以清洗的第一步是正文提取。
正文提取我试过几种方案。最简单的方案是用正则匹配特定标签,但通用性太差。后来我改用基于密度的提取算法,核心思路是:正文区域通常文字密度高、链接密度低,通过计算每个区块的文字/链接比例,找出最可能是正文的区域。
实际用下来,这类算法对大部分新闻类、博客类页面效果不错,但对结构特殊的页面(比如论坛、问答)效果一般。我的做法是:通用算法打底,特殊源单独写规则。不要指望一个算法解决所有问题,那是幻想。
4.2 去重的三个层次
去重不是简单的"标题相同就删掉",我把它分成了三个层次:
第一层是精确去重。基于id或者URL完全匹配,这个最简单,直接哈希比对。
第二层是近似去重。同一件事可能被多个源报道,标题措辞不同但内容高度相似。我用的是文本相似度算法,把标题和正文前200字拼起来算相似度,超过阈值就认为是重复内容,保留来源权重更高的那条。
第三层是语义去重。有些内容表述完全不同,但讲的是同一件事。这一层我用的是向量相似度,把内容转成向量后计算余弦相似度。这一层计算成本高,所以我只对前两层没去掉的条目做语义去重,而且只在条目数量超过一定阈值时才触发。
三层去重的参数配置:
| 层次 | 方法 | 阈值 | 触发条件 |
|---|---|---|---|
| 精确去重 | ID/URL哈希 | 完全匹配 | 始终 |
| 近似去重 | 文本相似度 | 0.85 | 始终 |
| 语义去重 | 向量余弦相似度 | 0.92 | 条目数>50 |
阈值这个东西需要根据你的实际数据调。0.85这个值是我试了0.7、0.8、0.9之后选出来的,0.7太激进会误删,0.9太保守去不干净。
4.3 低质量内容的过滤规则
去重之后还要过滤低质量内容。我定义的"低质量"包括:正文长度过短(少于100字)、纯图片无文字、标题党特征明显、内容与AI主题无关。
过滤规则我用的是规则引擎,每条规则一个函数,返回True表示保留,False表示过滤。这样设计的好处是规则可以灵活增删,不用改主流程。举几个我实际在用的规则:
- 正文长度小于100字,过滤
- 标题包含"震惊""速看""必看"等词,降权而非直接过滤
- 正文中链接数量超过文字数量的一半,过滤
- 内容与关键词列表的匹配度低于阈值,过滤
最后一条规则需要解释一下。我维护了一个AI领域的核心关键词列表,计算每条内容与这个列表的匹配度。匹配度太低说明内容可能跑题了。这个规则帮我过滤掉了不少"蹭热点"的内容。
5. 摘要与分类:让机器帮你读懂内容
5.1 摘要生成的两种路线与我的选择
摘要生成有两条路线:抽取式和生成式。抽取式是从原文中挑出最重要的句子拼成摘要,生成式是用模型重新组织语言生成摘要。
抽取式的优点是稳定、快、不会产生事实错误,缺点是读起来不够流畅,有时候句子之间衔接生硬。生成式的优点是流畅、可读性好,缺点是有时候会"幻觉",生成原文没有的内容。
我的选择是两者结合:先用抽取式生成一个基础摘要,再用生成式模型做润色。这样既保证了事实准确性,又提升了可读性。实际跑下来,这个组合方案的效果比单用任何一种都好。
摘要长度我控制在80到120字之间。太短说不清楚,太长读者没耐心看。这个长度范围是我根据团队反馈调整出来的,一开始我设的是50字,结果大家反映信息量不够;后来设到200字,又嫌太长。
5.2 分类标签体系的设计
分类这件事,我一开始想用模型自动打标签,后来发现纯自动的效果不稳定,同一个内容今天打这个标签明天打那个标签。所以我改成了半自动:模型先打候选标签,然后我用一个映射表把候选标签归一到固定的标签体系里。
我的标签体系分两级:一级标签是大的领域(比如"模型发布""行业动态""技术论文""产品更新"),二级标签是具体的细分方向。这样设计的好处是,日报排版时可以先按一级标签分组,组内再按二级标签排序,结构非常清晰。
标签映射表我维护在一个单独的配置文件里,格式大概是这样:
mapping: "大模型": "模型发布" "LLM": "模型发布" "语言模型": "模型发布" "融资": "行业动态" "收购": "行业动态" "论文": "技术论文" "arxiv": "技术论文"这个映射表需要持续维护,遇到新的候选标签就加进去。虽然有点繁琐,但比纯自动的稳定性好太多。
5.3 重要性打分:怎么决定哪条内容放头条
日报的排版顺序很重要,头条位置放什么内容,直接决定了读者对这份日报的评价。我设计了一个打分公式,综合考虑几个因素:
打分 = 来源权重 × 0.3 + 时效性 × 0.2 + 内容质量 × 0.3 + 关键词匹配度 × 0.2
来源权重是我手动给每个源打的,权威源权重高。时效性是发布时间越近分数越高,但超过24小时的内容分数会快速衰减。内容质量包括正文长度、结构完整度等。关键词匹配度是内容与核心关键词列表的匹配程度。
这个公式里的权重不是固定的,我会根据实际效果调整。比如有段时间我发现头条总是被某个源霸占,就把那个源的权重调低了。打分这件事没有标准答案,关键是你要有一个可调、可解释的机制。
6. 排版与分发:最后一百米决定读者体验
6.1 日报模板的设计原则
排版环节我用的是模板引擎,把处理好的数据填充到模板里生成最终文档。模板设计我遵循三个原则:
第一,信息层级清晰。一级标题、二级标题、正文、来源标注,层级分明,读者扫一眼就知道结构。
第二,关键信息前置。每条内容先放标题,再放摘要,最后放来源链接。读者如果只看标题就能获取大部分信息,想看细节再往下读。
第三,留白充足。不要把所有内容挤在一起,条目之间要有明显的分隔。我见过一些日报排版密密麻麻,读起来非常累。
模板我用的是Markdown格式,因为Markdown通用性强,可以很方便地转成HTML、PDF或者其他格式。模板大概长这样:
## {{ date }} AI日报 ### 今日头条 **{{ top_item.title }}** {{ top_item.summary }} 来源:{{ top_item.source }} ### 模型发布 {% for item in model_items %} - **{{ item.title }}** {{ item.summary }} {% endfor %}6.2 分发渠道与失败处理
分发我目前支持两个渠道:邮件和文档归档。邮件用SMTP发送,文档归档就是存到指定目录。
分发环节最容易出问题的是失败处理。邮件发送可能因为网络问题失败,文档写入可能因为磁盘满失败。我的做法是:每次分发都记录状态,失败的话记录失败原因,并且支持手动重发。
def send_daily_report(report_path, recipients): try: # 发送逻辑 status = "success" except Exception as e: status = f"failed: {e}" # 记录状态 log_dispatch_status(report_path, status) return status这个状态记录很重要,我遇到过好几次邮件没发出去但没人发现的情况,后来加了状态记录和失败告警,才没再出问题。
6.3 归档与历史检索
归档这件事,短期看没什么用,长期看价值很大。我把每天的日报都按日期存下来,同时维护一个索引文件,记录每天的日报路径和头条内容。这样需要检索历史内容时,直接查索引就行,不用翻遍所有文件。
索引文件我用的是JSON Lines格式,每行一条记录,方便追加和查询:
{"date": "2026-10-03", "path": "/archive/2026-10-03.md", "top": "某模型发布新版本"} {"date": "2026-10-02", "path": "/archive/2026-10-02.md", "top": "某公司发布财报"}7. 跑通之后才明白的几个道理
7.1 稳定性比功能丰富更重要
这个项目我做了三个月,前两个月一直在加功能,后一个月全在修稳定性。回头看,如果一开始就把稳定性放在第一位,能省下不少时间。
具体来说,稳定性包括:每个环节都要有失败处理、每个关键操作都要有日志、每个外部依赖都要有降级方案。比如摘要模型调用失败时,应该降级到抽取式摘要,而不是直接报错中断整条流水线。
7.2 数据质量决定输出质量
我花了很多时间优化摘要模型,后来发现效果提升有限。真正让日报质量上台阶的,是清洗和去重环节的优化。把噪音挡在外面,摘要环节自然就能出好结果。这个道理说起来简单,但实际做的时候很容易本末倒置。
7.3 人工介入不可耻,而且必要
我一开始追求全自动,后来发现完全不现实。现在我的流程里保留了两个人工介入点:一是标签映射表的维护,二是每天日报发出前的快速审核。这两个介入点花不了多少时间,但能显著提升质量。
全自动是个美好的目标,但在信息聚合这个场景下,完全去掉人工,输出质量很难保证。接受这一点,反而能把精力放在真正需要自动化的环节上。
7.4 监控和告警是流水线的安全带
没有监控的自动化流水线,就像没有安全带的汽车。我现在的监控覆盖了几个关键指标:每轮采集的条目数、去重后的条目数、摘要成功率、分发成功率。任何一个指标异常,都会触发告警。
告警阈值我设得比较宽松,避免频繁误报。比如采集条目数低于历史均值的50%才告警,而不是稍微波动就告警。告警太频繁会导致"狼来了"效应,最后没人看告警。
这套流程跑到现在,每天早上我只需要花十分钟审核一下,日报就能发出去。相比之前一个多小时的手动整理,效率提升非常明显。如果你也在做类似的事情,希望这些经验能帮你少走点弯路。