说实话,“AI 帮我追新闻,后续有进展直接推”这个需求,第一次听到的时候我第一反应是“这不就是订阅新闻吗”,但真上手做了一轮才发现,事情远没有这么简单。常规订阅器解决的是“有没有新文章”,而这里要解决的是“同一个事有没有新进展”。这两个问题看起来就差几个字,背后的技术选型和产品逻辑完全不同。
我前后花了大概三周时间,搭了一套“信息采集—语义理解—状态追踪—定向推送”的小系统。现在它每天都在帮我盯着几个长期跟踪的议题,只在真正出现值得关注的进展时提醒我,平时一声不吭。这篇文章就把整个设计思路和可落地的实操过程完整拆开讲一遍,内容包括怎么过滤噪音、怎么判断“这算不算进展”、推送怎么避免被当成垃圾消息,以及我在实际使用中踩过的坑。
1. 为什么“AI 追新闻”不是一个伪需求
1.1 信息过载背后的真实问题:进展无法被订阅
传统订阅工具的核心逻辑是“源进入、条目出”。我订阅一个科技媒体的 RSS,每天早上能收到几十条相关资讯,这些内容大部分都是重复报道、二手解读和关联性很强的周边信息。我要的其实是这么一件事:某个事件,比如“某公司发布了新款智能设备”,后续有没有更深入的评测、有没有供应链的回应、有没有官方修正,这些才是有价值的新进展。
但订阅器不会帮我做这个筛选。它一视同仁地把所有更新推到我的面前,我得自己挨个点开,在心里手动完成“这个是新的”“这个只是复述”“这个有进展”的分类,一天来来回回,信息焦虑一点没减,人还更累了。
更进一步说,当我要追踪的议题不止一个时,订阅器的无力感就很明显了。我如果同时关注五个议题,每天要过眼几百条信息,真正需要我及时反应的其实只有三五条,剩下的大多是无效阅读。“AI 帮我追新闻”要做的不是多抓内容,而是把“看新闻”这个手工活里的判断环节抽出来,交给我适合做语义比较的模型去完成。
1.2 “后续有进展直接推”的背后是一套状态机
“进展”是一个动态概念,它强调的不是“外面上新了”,而是“我关心的这件事往前走了一步”。那怎么定义往前走一步呢,我拆成了几个比较明确的状态:
- 无相关事件:此刻连首发消息都没有,处于观望状态。
- 事件萌芽:出现了一个可靠的源头首次报道,这是一次高价值提醒。
- 进展更新:事件已经存在,但产生了新的关键信息,比如官方回应、合作方确认、评测解禁。
- 持续发酵:没有实质性新信息,但大量媒体在讨论和转发,这通常意味着热度在上升。
- 暂时结束:事件走向明朗,短期内没有继续跟踪的必要。
我告诉 AI 模型:你每天帮我把新闻和这些状态做对照,如果从“无”到“萌芽”,必须第一时间告诉我;如果是“进展更新”,按重要程度决定是否打扰我;如果没有状态变化,哪怕抓到了几百篇相关文章,也不许发通知。
这样理解之后,系统需要的能力就清楚了:它不是关键词过滤器,而是一个持续运行的“事件状态跟踪器”。
1.3 这套思路适合谁,不适合谁
做完之后我总结了一下,这套“AI 追新闻”方案最适合的是这几类人:
| 场景 | 原痛点 | AI 追踪能带来什么 |
|---|---|---|
| 行业研究员 | 每天花两小时刷资讯,怕漏关键节点 | 按事件归档,只推状态变化,省出大量时间 |
| 产品运营 | 必须盯紧竞品动态,但竞品新闻经常埋在信息流里 | 自动识别竞品名字和产品型号,直接推进展摘要 |
| 做内容的人 | 追热点容易追成“嚼过的饭” | 第一时间拿到首发和实质性进展,避开洗稿 |
| 普通决策者 | 对一个政策或趋势需要持续了解,但没时间逐条看 | 每天一份合并版的进展简报 |
不太适合的场景也有,比如对某个行业只是随便看看,不要求时效性,那传统订阅就行,不需要上 AI;或者已经雇了专人做情报,那这套系统只是辅助,不能完全替代人的判断。
2. 系统整体设计:把“追新闻”拆成可实现的四个环节
2.1 采集层:用 RSS 而不是爬虫
我一开始讨论过要不要写爬虫去自动抓网页,后来放弃了。原因有三个:
一是维护成本。网页的 DOM 结构改版是常事,爬虫每次挂掉都要手动修选择器,长期维护非常不划算。二是合规风险。未经允许大规模抓取网站内容,即使是公开网页也存在明显的协议风险,没必要为了做一个辅助工具把自己的整个信息源置于不稳定状态。三是内容不完整。很多网站的正文是懒加载的,爬下来还得做正文提取,又是一层锅。
所以我的信息入口选择了 RSS 和公开 API。RSS 大略二十年历史的协议了,它虽然被很多人遗忘,但它把网站更新的“标题、链接、时间、摘要”整整齐齐排好,就是专门为机器消费设计的。系统只需要按小时去轮询这些订阅源,解析 XML,就能拿到结构化的更新列表,整个采集部分大概只需要三四十行代码。
如果你要跟踪的内容恰好没有官方 RSS,也可以用开源的 RSS 生成服务,把自己关注的网页转成 Feed。这样做的原理是“带外渲染”抓取,运行时去请求网页并转成订阅格式,等于把还页面的工作外包给了服务商,自己依然是消费 RSS 的姿势。
2.2 判定层:粗筛 + AI 精判的两段式架构
这是整个系统最核心的部分。如果每一篇新闻都交给大模型判断,成本会高到让我心疼,而且大部分抓下来的资讯跟我关心的议题根本无关,让 AI 去判断不相关的内容实属浪费。
所以我在 AI 判断之前加了一个“粗筛层”。粗筛层干两件事:
- 词面匹配:如果标题和摘要里没有出现议题关键词的任何一个变体,直接丢弃。
- 垃圾排除:有一个排除词表,比如“招聘”“财报”“股价”这些,对大部分议题来说是噪音,直接滤掉。
粗筛后的内容量级会从每天几百篇降到十几篇,这时候再让 AI 模型去分析每篇内容是否构成“新进展”,成本就很低了。我按每条粗筛结果配上当前事件的时间和摘要,发给模型,要求返回 JSON 格式的判断,包括:状态标签、进展描述和推送优先级。
为什么不直接让 AI 一次处理所有内容?原因之一是上下文太长,模型会“顾头不顾尾”,对每条内容的判断精度会下降;原因之二是把判断结果和原始帖绑定得更紧,后续调试时方便定位到具体哪次提示词出了问题。
2.3 存储层:一个小型 SQLite 完成事件建模
我选择 SQLite 而非 MySQL 等重型数据库,因为这是单人使用的工具,完全没有并发需求,一个数据库文件就已经足够。
数据库里需要两张核心表。一张是 events 表,记录每一个被跟踪的事件,包括它的标题、当前状态、最后更新时间、累计进度摘要;另一张是 articles 表,记录所有采集到的文章,通过事件 ID 和事件关联,同时保留文章的指纹 hash,用来去重。
存储层的一个关键点是“事件状态”必须持久化。AI 在判断当前新闻算不算“新进展”时,需要知道这个事件之前的摘要是什么,所以我不能只存最后状态,要把每次进展都存进一个 history 字段里,作为小型的“事件记忆”。这样每次判断时,AI 拿到的不是空白背景,而是此前整个事件的推进脉络。
2.4 推送层:克制是一种美德
推送逻辑是最容易做砸的部分。我最初的想法是每抓到一篇有进展的文章就立刻推到群里,结果真实体验极差:每天被震醒四五次,每条都是碎片信息,三天后我已经养成“直接屏蔽”的条件反射了。
后来的原则是:宁可一条不发,也不要虚发一条。只有满足以下条件之一才会触发推送:
- 事件从无到有的“首次报道”,这属于高价值信号,立即推。
- 事件从“萌芽”进入“进展更新”,有实质新事实出现,立即推。
- 持续发酵状态,但没有实质新信息,合并到每日一次的情绪摘要。
- 没有变化,什么都不推。
推送的消息模板我也会精心设计。它必须包含:事件代号、当前阶段、本次进展的一句话摘要、最关键的原文链接块。收到推送的人在三秒内能明白“是什么事、发生了什么、我要不要点进去”,这才算成功。
3. 实操落地:从零搭一个“AI 追新闻 + 进展推送”流水线
这一节我会把真正能跑的代码逻辑写出来,你可以按照这个思路直接复刻,或者按自己的源和场景去调整。
3.1 环境准备与依赖安装
我用了 Python 3.10,依赖库只有四个,轻到可以跑在任何一台常开的小主机上:
- feedparser:解析 RSS 和 Atom 订阅源。
- openai (或任何兼容 OpenAI 接口的 SDK):调用 AI 模型做语义判断。
- requests:发送 Webhook 推送。
- sqlite3:Python 内置,不需要额外安装。
安装命令很简单:
pip install feedparser openai requests3.2 初始化数据库与事件表
先建数据库。我下面用的是 SQLite 的 WAL 模式,这样定时任务在写入时不会阻塞读取。
import sqlite3 conn = sqlite3.connect("news_tracker.db") conn.execute("PRAGMA journal_mode=WAL") conn.execute(""" CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT UNIQUE, status TEXT DEFAULT 'observing', last_summary TEXT, history TEXT, source_name TEXT ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_id INTEGER, title TEXT, url TEXT UNIQUE, content TEXT, fingerprint TEXT, fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(event_id) REFERENCES events(id) ) """) conn.commit()这里的标题去使用 UNIQUE 约束来保证同一个事件标题不会重复创建。要注意的是我设置的是 title UNIQUE,但真实场景里标题可能稍微有变化,这一层约束只是为了阻止手工创建时的低级错误,真正的去重靠指纹。
3.3 采集订阅源并生成文章指纹
写一个函数,用来拉取 RSS 并解析出文章条目。我用了一个比较保守的超时时间:5 秒连接、10 秒读取。
import feedparser import re import hashlib def fetch_feed(feed_url): parsed = feedparser.parse(feed_url) items = [] for entry in parsed.entries: item = { "title": entry.get("title", "").strip(), "url": entry.get("link", "").strip(), "summary": entry.get("summary", entry.get("description", "")).strip() } if item["title"] and item["url"]: items.append(item) return items def normalize_title(title): title = title.lower() title = re.sub(r"\s+", " ", title) title = re.sub(r"[^\w\u4e00-\u9fa5]", "", title) return title def fingerprint(item): raw = normalize_title(item["title"]) + item["url"] return hashlib.sha256(raw.encode("utf-8")).hexdigest()指纹一定要包含标题和 URL 两边。因为有的网站同一篇文章会发布不同链接版本,或者转载方换一个标题贴同一个 URL,永远不能只依赖其中一边。
3.4 粗筛逻辑:关键词命中与排除词表
接下来做粗筛。维护两个表:
- 包含表:必须命中至少一个词,比如“智能设备”“半导体”“发布会”。
- 排除表:命中任意一个就拒绝,比如“招聘”“招聘公告”“股价”“优惠券”。
排除表非常重要。没有它的话,AI 的调用量会明显上升,而且大部分是没意义的判断。
INCLUDE_KEYWORDS = ["智能设备", "半导体", "发布会", "供应链", "新机型"] EXCLUDE_KEYWORDS = ["招聘", "财报", "股价", "优惠券", "双十一", "测评排行榜"] def coarse_filter(item): text = f"{item['title']} {item.get('summary', '')}" if not any(kw in text for kw in INCLUDE_KEYWORDS): return False if any(kw in text for kw in EXCLUDE_KEYWORDS): return False return True这一步能过滤掉大约七成的无用推送,过滤器本身没有任何 AI 成本,跑得飞快,是我整个系统里性价比最高的模块。
3.5 AI 精判:判断“算不算新进展”
粗筛通过的文章会被送进 AI 判断器。我设计的 prompt 是这样的:
你现在是一个事件追踪助手。下面有一篇刚抓到的新闻和你正在跟踪的事件摘要。 正在跟踪的事件:{event_title} 事件当前状态:{event_status} 事件已有进展摘要:{event_summary} 刚刚抓到的新闻: 标题:{article_title} 摘要:{article_summary} 请判断这篇新闻对这个事件来说属于哪种情况: 1. "new_initial":第一篇报道,事件刚刚出现,之前完全没记录。 2. "new_update":事件已有记录,这篇报道提供了新的实质信息(新事实、新数据、官方回应等)。 3. "new_repost":只是转载或复述已有信息,没有新增实质内容。 4. "not_relevant":虽然包含关键词,但实际内容与事件无关。 5. "heat_up":没有新事实,但大量媒体开始报道,热度明显上升。 要求: - 只返回 JSON,不要解释。 - 格式:{"judgement": "new_update", "reason": "一句话理由", "suggested_status": "更新状态,保持原状态也可以"}用代码去调用它,并解析返回的 JSON。
import json from openai import OpenAI client = OpenAI(api_key="your-api-key") def ai_judge(event, article): prompt = build_prompt(event, article) resp = client.chat.completions.create( model="gpt-4o-mini", # 用便宜模型即可 messages=[{"role": "user", "content": prompt}], temperature=0, response_format={"type": "json_object"} ) data = json.loads(resp.choices[0].message.content) return data我排查过很多次,把温度设成 0 最关键。追新闻这件事要的是稳定一致的判断,不需要任何“创造性”。如果模型输出带多余引号或注释导致 JSON 解析失败,我还在外层包了一个异常处理,解析失败时默认视作 not_relevant,宁可漏掉也不误报。
3.6 推进事件的状态存储
当 AI 返回 new_initial 时,我创建一个新事件;当返回 new_update 或 heat_up 时,我把这次判断结果追加进该事件的 history。历史摘要维护一个累计逻辑:每次新一轮进展摘要进来,旧摘要和新摘要拼在一起,但只保留最近三次的“变化最大”部分,防止事件演进成几千字的超长文本,反而干扰后续判断。
近三次的策略实测有效。之前我保留过整条事件全程摘要,模型调用成本高,而且因为输入太长,模型反而抓不住最新重点。只留最近几次核心变化,判断准确率反而上去了。
3.7 推送:组装一条清晰有力的消息
推送我用的是一个通用的 Webhook 模板,支持企业微信、钉钉这一类群机器人,也支持自建接口。模板里的变量只有事件标题、本次进展、来源链接、当前状态四样:
def send_webhook(webhook_url, payload): resp = requests.post(webhook_url, json=payload, timeout=10) resp.raise_for_status()发出去的正文是这样的:
【事件追踪】智能设备新机型相关动态 当前阶段:进展更新 本次变化:供应链确认新机型将提前进入量产阶段,预计发布时间较原计划提前两周。 原始链接:https://example.com/news/report我不会在推送里塞太多内容。一旦信息变长,手机上阅读率就会直线下降。我的原则是:标题必看,变化一句话必看,原文链接按需点击。
3.8 定时任务调度
有了上面的模块之后,还需要一个调度器。我的实践是用系统自带的 cron 而不是在 Python 里写 while True,因为 cron 挂了能自动恢复,Python 常驻进程挂了就真的挂了,没有任何守护。
一个典型配置是每 30 分钟拉取一次主信息源的 RSS:
*/30 * * * * cd /path/to/project && /usr/bin/python3 main.py >> logs/news_tracker.log 2>&1每天 9 点和 21 点再跑一次“合并报告”,把过去半天非紧急的进展集中发一次:
0 9,21 * * * cd /path/to/project && /usr/bin/python3 digest.py >> logs/digest.log 2>&1这样设计是为了不让用户被推送轰炸。真正的突发事件可以在 30 分钟内到达,普通资讯每天集中看两次就够了。
4. 真实使用中踩过的坑与排查经验
4.1 重复推送:指纹算法没你想的那么万能
一开始我在正式运行前做了一次模拟测试,从十八个订阅源里拉过去两天历史数据,系统推了 23 条通知,其中有 5 条是同一篇新闻的不同转载版本。原因是我当时用的指纹只包含标题,而转载方经常在标题前后加“独家”“快讯”“重磅”等前缀词,归一化之后依然不完全一致。
解决办法是给标题指纹加一个“前后缀去除”的逻辑,把“重磅|独家|快讯|突发|评论|滚动”这些都先删掉,再做归一化和哈希。加了之后重复推送率明显降了下来。但这也提醒我:没有完美指纹,只能靠持续观察日志去补规则。
4.2 事件串了线:同题异构导致误建事件
这个坑比较隐蔽。比如某天短时间内在三个不同源出现了相似主题的文章,但三篇标题讲的是同一个事件的口径不一致,我的系统给它们建了三个不同事件,后续的进展被拆散后推得到处都是,很混乱。
后来我在 AI 判断前加了一个相似事件查找:新文章指纹出现时,先在 events 表里按标题词的 Jaccard 相似度做一次粗匹配,超过阈值就把判断上下文从“当前事件”换成“最相似事件”。简单说,就是先给文章“找熟人”,找不到熟人才让它开新事件。
4.3 AI 的“幻觉进展”:看起来有逻辑,其实是瞎猜
有一次系统推了一条“厂商可能将在下月发布新固件”给我,理由是某篇文章里提到了“软件开发团队正在招聘相关岗位”。这个推断虽然有点道理,但不构成实质进展,推出来会误导人。问题出在模型把“可能”和“确定”的边界搞混了。
我在 prompt 里加了一条硬规则:除非原文明确给出时间、人物、数据、官方表态,否则一律视为 news_repost,不准创建 new_update。现在 AI 判定的标准是“原文有明确事实依据才算进展”,误报率降了不少,也牺牲了一部分信息联想能力,但追新闻场景下,确凿性比预见性重要得多。
4.4 时区问题:国际新闻源的“今天”不是我的“今天”
我最初的时间排序用的是服务器本地 UTC 时间,结果每天早上 8 点打开系统,发现“最新进展”列表里排得乱七八糟,因为当天很多新闻在 UTC 的日期还是“昨天”。这件事让我花了一下午排查,最后才意识到问题出在时区。
解决办法是在数据库连接后统一指定时区:
conn.execute("SET time_zone = '+08:00'")并且在计算“当天已推送次数”时,把窗口切割逻辑全部按本地时区处理,之后所有统计和排序都正常了。
4.5 模型调用成本上涨:学会“分级模型”
我对所有新闻都用同一个最强模型那段时间,月度 API 费用高得离谱。后来改成分级策略:
- 粗筛后的普通判断用便宜模型(如 gpt-4o-mini)跑。
- 只有当系统判定为 new_update 或 new_initial 时,才用更强模型去重新生成详细摘要。
- 平时每轮判断输入尽量压缩,把事件历史压到三百字以内,文章摘要只保留前两百个字符。
改完这个分级方案后,费用大概少了百分之六十五,判断效果几乎没有肉眼可见的下降。
5. 成本、进阶技巧与后续玩法
5.1 用数据验证这套系统是否真的有效
我对“有效”的定义是三条:被推送出来的信息量足够少、每一条都和当前事件强相关、用户收到后打开率高于 50%。
我做了一张简易效果表,每周末自己复盘一次:
| 指标 | 公式 | 我的目标 |
|---|---|---|
| 推送量 | 每周实际推送条数 | 不超过 15 条 |
| 有效进展率 | 推送中确实有实质新信息 | 超过 80% |
| 打开率 | 收到的推送被点进去的比例 | 超过 50% |
如果某一周有效进展率偏低,我就去翻它误报的原因,重点看 prompt 里那条“硬规则”是不是被模型绕过了,或者粗筛的排除词是不是漏了某些高频噪音词。
5.2 提示词的三个进阶经验
第一个经验:把“进展判断标准”写成列表,而不是一段描述。模型对列表的遵循度比对散文高出很多。第二个经验:给模型一个“委婉拒绝”的出口,明确告诉它可以返回 not_relevant,这样它不会为了完成对话而硬给一个进展判断。第三个经验:定期更新示例样本,每次发现一个典型误报案例,就把它的标题和正确判断写成 few-shot 示例加进 prompt,相当于一个持续进化的“案底库”。
5.3 扩展玩法:从“追新闻”到“做沉淀”
系统跑顺以后,我又加了一个轻量的每日盘点,用的还是数据库里的数据。每天晚上十点,系统会把所有跟踪事件的最新状态汇总成一份简报,分成“新发现”“有进展”“无变化”三栏,自动发送到邮箱。这样即使白天错过即时推送,第二天早上也能在五分钟内恢复全局认知。
再往后,还可以把事件打上行业标签,用嵌入模型把文章向量化,做语义聚类和热度趋势分析。不过这些都是锦上添花,追新闻这个核心循环只要把状态判断和推送克制做到位,就已经解决大部分问题了。
我个人的体会是,这种“AI 帮我追新闻”的工具,真正的价值从来不是把新闻喂给我,而是帮我把注意力集中在真正变化的节点上。追新闻这件事本身不难,难的是做到“该打扰的时候打扰,不该打扰的时候安静”。只要这个分寸拿捏好了,这套系统就能长期安稳地跑下去。