你有没有过这种经历:早上睁眼先刷十分钟新闻,刷到的大半是同一个话题的不同标题,真正重要的进展却淹没在信息流里。我最近给自己搭了一套“AI 帮我追新闻,后续有进展直接推”的小系统:不在刷新闻上花时间,而是让程序盯着几个我关心的信息源,发现有实质变化,立刻用 AI 生成一份“新增了什么”的摘要,直接推到我的手机上。这篇文章就把这套方案从设计到落地完整拆开讲一遍,适合经常盯竞品动态、行业政策、技术趋势的从业者,也适合想用自动化减少信息噪音、又不想写复杂代码的朋友。
追新闻这件事,最累的不是“看”,而是“盯”。同一个事件,各家媒体的标题能写出七八种花样,点进去内容大同小异;等真出了关键进展,又常常混在一堆重复报道里,一不留神就错过。所以我决定把“盯”的工作交给程序,把“理解变化”的工作交给 AI,自己只负责看推送结果。
1. 先拆需求:AI 追新闻到底在追什么
1.1 追的是一个“变化信号”
很多人一听说“AI 追新闻”,第一反应是做个爬虫,把各大网站的头条抓下来。但仔细想想,我们要的不是“新闻列表”,而是“变化信号”。比如某个产品发布了新版本,某个公司换了核心负责人,某项政策出了实施细则。这些信息在时间线上是离散的,今天可能没有任何动静,明天突然来一条重量级更新。
与其说是追新闻,不如说是追“变化”:我关注的对象有没有出新的官方动态,有没有人发布了值得注意的解读,社区里有没有出现新的讨论方向。把需求拆成信号,系统设计就清楚多了——我只需要做三件事:定期检查目标信息源、把这次看到的内容和上次做对比、对有实质差异的部分生成说明。关键词也从“爬虫”变成了“监控”“对比”“摘要”。
1.2 关键不是“即时”而是“去伪存真”
刚开始做这套工具时,我最在意的是“快”,恨不得新闻发布后几秒钟就收到提醒。后来发现,快反而是个坑。一个事件刚发生时,信息往往是混乱的,错误消息、猜测、标题党的比例特别高。如果系统只按“出现新内容”来推送,那你的手机会被大量噪音塞满,几周之后你可能就把通知权限关了。
所以我对推送加了一道“实质变化判定”:新内容和已有内容相比,主题是否真的不同,关键实体是否发生变化,数字、时间、结论有没有实质更新。这一步用传统脚本写规则会很痛苦,因为要穷举各种变化类型;但交给 AI 就轻松很多,让模型直接判断“这次内容和上次相比,有没有值得关注的增量信息”。用下来体感很明显,推送量至少砍掉了一半以上,剩下的基本都是值得点开的。
1.3 推送不等于通知轰炸
很多人以为推送就是把内容发出去,其实推送的体验设计才是用户是否愿意长期使用的分水岭。我给自己定的标准是这样:每条推送必须能在不打开全文的情况下,让我判断“这条要不要看”。换句话说,推送内容本身就要包含“发生了什么变化、为什么重要、原始链接在哪”。
这就要求推送文案由 AI 生成,而不是简单拼接摘要。我会给模型一个固定的输出模板,限制在三到五行:开头概括事件,中间说明新增信息点,末尾附上原始链接。标题写成“你关注的 XX 又有进展了”这种样式,正文控制在 150 字以内。这样即便一天收到三五条推送,扫一眼就能完成筛选,不会产生“广告轰炸”的抵触感。
2. 整体方案:一条自动化的信息流水线
2.1 四个模块:采集、对比、摘要、推送
我的整体方案把流程拆成四个模块,像一条小型流水线。第一个模块是采集,负责从固定的信息源抓取原始内容;第二个模块是对比,负责把新内容和历史记录做差异判断;第三个模块是摘要,负责把差异部分交给 AI 生成通俗说明;第四个模块是推送,负责把摘要送到你习惯使用的渠道。
这里有个容易被忽略的设计原则:模块和模块之间尽量解耦。采集模块跑挂了,不能影响已经生成的摘要;推送模块暂时不可用,也不能丢采集结果。我实现时用了队列中转,每一模块的输出都先落盘或写入待处理队列,再由下一模块消费。这样整个链路某一个环节故障,修复后还能继续处理积压内容,不会因为一个小 bug 丢掉一整天的关键信息。
2.2 为什么把摘要放在对比之后
很多二次开发者在做类似系统时,会把顺序搞反:先把所有抓到的内容都让 AI 生成摘要,再筛掉重复的。这个做法很费 token,而且摘要生成是有延迟的,几十篇文章一起处理时,光等模型返回就要好几分钟。
更合理的顺序是先轻量过滤,再重量处理。文本相似度计算、关键词贡献率评估这一步成本极低,几毫秒就能跑完;AI 摘要或变化识别的成本高,应该只对“真正可能有用”的内容调用。在我这个项目里,“对比”模块负责把相似度高于阈值的内容直接挡掉,只有相似度低于阈值或者关键实体发生变化的内容,才进入“摘要”模块。成本从每轮几十次模型调用降到了几次,响应速度也跟着上来了。
2.3 工具选型:越轻越好
技术选型上,我有几条基本原则:学习成本低、部署方便、后期改起来容易。数据源抓取用 Python 的 requests 加 feedparser 就能覆盖绝大多数 RSS 和页面场景;变化检测用字符串相似度算法,再配合大模型 API 做语义层面的判断;计划任务用 cron 或者系统自带调度器;推送渠道则优先用邮件和群机器人 Webhook。
这套组合的最大特点是“随处可跑”,一个普通的云主机甚至家里的旧电脑都能扛住。我没有引入复杂的消息队列、容器编排、数据库集群,因为这些对个人追新闻的需求来说完全是负担。工具的体积和项目规模要匹配,是我反复强调的一点:能用脚本解决的事,就别急着上框架。
3. 核心细节与关键参数
3.1 轮询间隔:默认 30 分钟
轮询间隔是第一个需要拍板的参数。设得太短,信息源还没更新你就反复去抓,浪费流量还容易被对方服务器封 IP;设得太长,又会错过时效性强的进展。我实测下来,行业新闻和信息源的平均更新频率大约在 15 到 60 分钟之间,所以折中选择 30 分钟一轮。每天运行 48 轮,每轮抓取十几个源,请求量完全在合理范围内。
这个间隔不是死的。如果追的是突发事件,比如产品上线或版本发布,可以把间隔缩到 10 分钟,但也要接受更高频率的反爬风险;如果追的是周更类源,比如官方发布栏目,一天抓三次就够了。我的做法是把间隔做成配置文件,不同信息源用不同的轮询策略,避免一刀切。
3.2 相似度判定与去重
去重模块的技术细节,很多人会忽略,但它直接决定了推送质量。我采用的是“两阶段去重”:先算标题的字符相似度,再算正文关键句的向量相似度。字符相似度能挡住“换了个标题但内容完全一样”的重复新闻,向量相似度能挡掉“标题不同但内文事实相同”的深度重复。
具体到实现,我用了一段简洁的相似度计算逻辑。标题完全相同的直接丢弃;标题相似度超过 0.8 的进一步比正文;正文由 AI 提取出关键事件三元组之后再做比较。经过这套过滤,同一事件的重复新闻基本能压制到只剩一条。有一个参数需要注意:相似度阈值设太高,会把本来要紧的新增信息也滤掉;设太低,又会推一堆“同一件事换个说法”的重复内容。我比较了几十个样本之后,把标题阈值定在 0.82,正文关键句阈值定在 0.6,属于比较保守但不容易漏的状态。
3.3 提示词模板与输出约束
AI 摘要的质量,很大程度上取决于你怎么给它下达指令。我用的提示词模板经历了三次迭代,第一次只写“总结这段新闻”,模型经常输出发散内容;第二次限定“提取与历史相比的新增信息”,情况改善;第三次增加了“固定输出格式”的约束,效果才真正稳定下来。
我目前使用的模板大致是这样:先给模型一段“历史背景摘要”,再给一段“今日新增内容”,要求它只判断今日内容中哪些是历史上下文里没有的,并按照“事件概括 + 新增信息点 + 来源链接”的结构输出。如果模型认为没有实质新增,就只返回一句话提示,系统不会生成推送。加了这层约束之后,每条推送的信息密度高了很多,基本能保证“每推必有增量”。
4. 实操过程:从零搭一套“进展直推”系统
4.1 环境准备与依赖安装
动手前先把环境装好。我的建议是准备一台常开的 Linux 云主机,最低配置就够用,CPU 一核、内存 1GB 都没问题。系统我用的是常见的 Debian 系发行版,Python 版本选 3.10 以上,因为后面要用到的几个库对版本有要求。
接下来安装依赖。核心库只有四个:requests 用来抓网页和调接口,feedparser 用来解析 RSS 源,openai 库用来调用大模型,apscheduler 用来做定时调度。安装命令非常简单,确认 pip 正常后一次性装齐即可。如果你对虚拟环境不熟悉,建议用 Python 自带的 venv 模块创建独立环境,避免依赖版本互相冲突。
4.2 主流程代码实现
下面这段代码是整个系统的核心调度逻辑,我把它压缩到了一个文件里,方便你跑通流程后再拆开优化。
import feedparser import requests import json import time from difflib import SequenceMatcher SOURCES = [ {"name": "source_a", "url": "https://example.com/rss"}, {"name": "source_b", "url": "https://example.org/feed"}, ] HISTORY_FILE = "history.json" def fetch_rss(url): feed = feedparser.parse(url) results = [] for entry in feed.entries[:10]: results.append({ "title": entry.get("title", ""), "link": entry.get("link", ""), "summary": entry.get("summary", "")[:500], }) return results def load_history(): try: with open(HISTORY_FILE, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return {} def save_history(history): with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(history, f, ensure_ascii=False, indent=2) def is_duplicate(title, seen_titles): for seen in seen_titles: ratio = SequenceMatcher(None, title, seen).ratio() if ratio > 0.82: return True return False def process_source(source): history = load_history() key = source["name"] seen_titles = history.get(key, []) items = fetch_rss(source["url"]) new_items = [] for item in items: if not is_duplicate(item["title"], seen_titles): new_items.append(item) seen_titles.insert(0, item["title"]) history[key] = seen_titles[:50] save_history(history) if new_items: # 这里把 new_items 交给 3.3 的提示词模板处理 post_to_ai_summary(new_items) def schedule(): while True: for source in SOURCES: try: process_source(source) except Exception as e: print(f"error: {source['name']} {e}") time.sleep(1800) if __name__ == "__main__": schedule()这段代码的意图很简单:每轮对每个信息源抓取最新条目,和历史标题做相似度对比,把真正的新条目交给下一步。注意我没有把 AI 调用直接写在 fetch 流程里,而是留了一个 post_to_ai_summary 入口函数,这样方便你在中间加队列或调整参数。
跑通这段逻辑之后,你可以做两个小改进:一是把相似度阈值做成配置文件,方便不同信息源单独调整;二是把 seen_titles 的保存长度控制在 50 条,避免历史记录无限膨胀。
4.3 推送配置:邮件与群机器人
推送通道我优先推荐两种:邮件和群机器人 Webhook。邮件的好处是稳定到几乎不会失败,而且天然适合存档;群机器人的好处是推送即时,手机上能直接弹出通知。我在邮件里把 AI 生成的摘要放在正文开头,链接放在下面;群机器人则直接把摘要作为一条消息发出,配合标题直达链接。
用 Python 发邮件需要配置 SMTP,这个步骤很容易出错,建议先在命令行测试一下端口连通性。群机器人就简单得多,只要拿到一个 Webhook 地址,往里面 POST 一段 JSON 就能完成推送。我在推送模块里加了一个失败重试机制:如果推送时网络报错,把消息内容先写入队列文件,下一次调度时自动重推,保证关键进展不丢。
4.4 部署与定时触发
代码跑通之后,把它放到云主机上,用系统自带的 crontab 做定时触发是最直接的方式。crontab 配置里建议记录输出日志,这样方便排查问题。输入 crontab -e,在其中设置 30 分钟执行一次主脚本。
*/30 * * * * cd /home/user/news-tracker && /usr/bin/python3 main.py >> run.log 2>&1有一个细节需要注意:cron 和 Python 的交互。cron 环境下 PATH 变量和手动登录终端时不同,Python 路径和依赖库路径都可能找不到。最稳妥的办法是在脚本开头写上解释器的绝对路径,或者先 source 虚拟环境的 activate 脚本再运行。我第一次部署时就在这个问题上卡了半小时,日志里反复报找不到依赖,实际上只是路径没配对。
5. 常见问题与排查技巧实录
5.1 误报偏多怎么办
误报偏多,基本是相似度阈值设低了。如果你发现自己一个上午收到十几条推送,点开却发现内容大同小异,先别急着改采集源,第一件事是查历史记录里的相似度数据。我给自己的系统加了一个 debug 模式,每次判定为重复时会把相似度数值写进日志。观察两三天,你能看到大多数重复内容的相似度集中在哪个区间,再把阈值调整到比那个区间略高一点即可。
还有一个容易忽略的来源:同一个媒体会把同一篇稿子发布在多个栏目里,RSS 就会产生多个非常像的条目。我在采集模块里做了按域名分组,同一域名下相似度超过 0.9 的直接丢弃,只有超过 0.75 的源之间做交叉比对。
5.2 漏掉关键进展怎么办
误报多让人烦,漏报更可怕,因为漏掉的东西你不会立刻意识到。排查漏报时,我会做三件事:检查信息源 URL 是否还在正常输出、检查历史记录容量够不够、检查抓取列表的前 N 条配置是否被一些不重要的更新占满了。
历史记录容量这个坑比较隐蔽。我早期把每个信息源的 seen_titles 设为 20 条,结果某个高频更新的信息源把 20 条全占满,真正的关键进展排在 21 位,处理时被当作重复丢弃。后来我把容量改成按内容时效性区分:对于高频源只保留 20 条,关键源保留 80 条,核心进展推送稳定了很多。
5.3 AI 摘要不稳定与推送失败
AI 摘要飘忽不定,通常和提示词里的历史背景信息过于简单有关。我给历史背景模板里增加了一段“上次推送结论”,模型判断新增信息时就有了明确的参照系,输出明显稳定。另外,模型返回的内容一定要做后处理,比如去掉多余引号、规范化标点,否则推送到手机上的文案会出现明显的格式违和感。
推送失败的情况主要有两种情况:一是当时网络抽风,二是服务商限流。我在代码里加了分级重试:第一次失败后等 5 秒重试,第二次失败等 30 秒,第三次直接写进失败队列。每天睡前看一眼日志,把推送失败列表里积压的消息重新推一次。
| 问题 | 可能原因 | 解决动作 |
|---|---|---|
| 误报推送偏多 | 相似度阈值太低 | 开启 debug 模式,观察相似度分布后上调阈值 |
| 关键进展漏推 | 历史记录容量太小 | 按源时效性分类调整容量,高频源和关键源分开 |
| 摘要格式乱 | 历史背景信息不足 | 补充上次推送结论,约定输出模板 |
| 邮件发不出 | SMTP 端口或账号验证问题 | 命令行测试端口连通,检查授权码 |
| 推送偶发失败 | 网络抖动或限流 | 分级重试 + 失败队列,第二天手动重推 |
| 重复内容仍多 | 同一媒体多栏目转载 | 按域名分组,同域高相似度直接丢弃 |
6. 后记:这套系统的边界与扩展
6.1 顺着这个思路还能做什么
“AI 追新闻”这套系统的本质是“信息管线”:采集、判定、摘要、推送。这四个模块不只适用于新闻。我把这套思路稍微改了改,就扩展出去两个方向:一个方向是盯竞品动态,把信息源换成竞品的官方栏目和版本发布页,推送文案改成“竞品发布了新功能、竞品官网上线了新页面”;另一个方向是盯论文和行业报告,上传一些资料站点的更新列表,AI 摘要换成了“这项研究提出什么方法、有什么结论”。
更进阶的玩法是让 AI 不止做摘要,而是做“标注”:自动把推送内容打上标签,比如“产品动态”“人事调整”“政策变化”“技术文章”,再按标签份发到不同分组。这样信息不只是推送,而是完成了初步的筛选和分类,长期累积下来还能形成个人的信息数据库。
6.2 踩过几次坑之后的体会
做完这套系统,我最大的感受是:自动化的价值不在于节省“刷新闻”的几分钟,而在于改变了我和信息的关系。以前我担心错过,所以我一直刷;现在我知道系统会帮我盯着,心态反而从容了。设计上最值得投入精力的地方,不是采集脚本有多少花活,而是判定变化和生成摘要这两个中间环节,它们决定了推送是“有用”还是“添乱”。
我也越来越认同一个原则:这种工具用不着做得特别“重”。我不会为了让它更智能化就引入一套复杂的评分机制,也不会为了性能去上分布式框架。十几行核心代码加一个好用的模型接口,已经覆盖了我 90% 的需求。剩下的 10%,靠定期调整阈值和偶尔看一眼日志来修修补补,完全够用。
如果你也想搭这么一套东西,我的建议是从最小可用的版本开始:先手动跑一遍采集,再手动调一次模型接口,最后把推送搞通。不要一上来就设计十个模块,先让它跑起来,你才会知道你真正需要的到底是哪个部分。跑通之后,剩下的都是顺水推舟的事。