前阵子在 openclaw 的 skill 仓库里翻到一个叫 rss-ai-reader 的小工具,大致逻辑很简单:定时抓 RSS 订阅 → 丢给 LLM 生成中文摘要 → 推到飞书群里。开箱即用,看起来很美好,我直接部署。
但用了两周,问题就冒出来了。
原版的问题
原版作者塞了 91 个 RSS 源在 config.yaml 里,都是技术博客,质量不低——Simon Willison、Krebs on Security、antirez、Gwern,每一个单独拎出来都值得订。但问题在于:91 个全量抓,谁的文章多谁就占满推送队列。
某天 Daring Fireball 一口气发了 5 篇,我那天的推送列表小一半都是它。第二天 Pluralistic 又灌进来好几篇。我那几个真正想看的源——比如 antirez 偶尔冒一篇、Dwarkesh Patel 一周一篇——被稀释得几乎看不见。
你以为自己订了很多源,其实算法(或者说 RSS 的生产频次)替你做了选择。高产源霸占了你的注意力,低产但高质量的源永远轮不到。
另一个问题是内容旱涝不均。有的时候一天更新了十几篇,有的时候三天推送不了一篇。原版一概而论,多的丢了不可惜,少的空转也不补充。
还有一个细节:原版虽然 prompt 里写了「把标题翻译成中文」「严格按以下格式输出」,但 LLM 经常不听话。有时候它输出英文标题,有时候标志符号漏了,有时候它直接说「我无法总结这篇文章」。没有任何校验,LLM 做出什么就推什么。
我改了什么
我一口气改了一大堆,下面是所有改动的完整对比。
订阅模型:固定 + 随机的双轨制
原版的 91 个源全部硬编码在配置里。我拆成了两层:
- 固定订阅(5 个):OpenAI Blog、Anthropic News、TechCrunch、Ars Technica、GitHub Blog。这些是核心关注,每轮必刷。
- 随机池(94 个):单独一个
all_feeds.txt文件,涵盖 Simon Willison、Krebs on Security、Dwarkesh Patel、construction physics、experimental history 等等。每次运行时从中随机抽 20 个源来抓。
这样一来,每次推送既有必读的基本盘,又有随机刷新出来的「意外之喜」。第一次运行推送给我 Sean Goedecke ——说不的工程师,给到全新的视野,这要是在原版我估计永远不会收到它。
配额分配:轮询制,不让一个源刷屏
每轮最多推 12 篇,分配逻辑是这样的:
- 固定订阅先上,每个 feed 保底 1 篇,然后按 feed 轮询填充,上限 8 篇
- 固定没用完的配额溢出给随机
- 随机同样按 feed 轮询,上限 4 篇 + 溢出部分
核心思路是每个源不论产量高低,都有公平的曝光机会。TechCrunch 一天出 10 篇,在我这也就占 1-2 个位置;GitHub Blog 一个月写 3 篇,每篇都不会漏。
旱涝调节:pending 队列
原版的做法是:新文章 > max_articles,多的就丢掉不要了;新文章为 0,就空转一轮。
我加了一个 pending 队列。多出来的文章不进数据库标记为已处理,而是存下来等下一轮。如果下一轮新内容不足,自动从 pending 队列里补上来。把多的匀给少的。
这招在周末特别管用——周末很多博客不更新,新文章常常只有个位数,pending 队列能补到满配。
翻译与审查机制:三层把关
这是比较核心的改动。原版 prompt 里写了翻译要求,但写归写,没人检查。
我加了三个关卡:
第一关:内容审查 _is_content_valid
过滤原文垃圾。很多 RSS feed 摘取的内容是空的、只有链接、或者只有「Please enable JavaScript to view this page」。这些文章不配浪费我的 token,直接标记为已处理不进入队列。
第二关:摘要格式校验 _is_summary_valid
LLM 输出摘要后,检查:
- 长度是否 ≥60 字
- 是否以 🏷️ 开头
- 五个标记(🏷️ 📌 💡 📋 ⭐)是否出现了至少 3 个
- 有没有出现「我无法总结」「I cannot provide」等拒绝回复
第三关:Reflection Retry
如果哪条不合格,不是直接丢弃,而是把具体原因反馈给 LLM,告诉它:「你上次的问题是摘要过短/格式不完整/漏了标记,请按原始格式重写。」模型会根据反馈自我修正,最多重试 2 轮。
另外,我用的过 DeepSeek 模型,它有个习惯——输出前先来一段 <think>...</think> 思维链。我写了个正则剥离器,把这些思考块从最终输出里清掉,保证推到飞书的只有最终摘要,没有 LLM 的内心独白。
LLM:从 SDK 到 HTTP 直连
原版用 Anthropic SDK 和 OpenAI SDK,每个都依赖一整个包。我换成了 HTTP 直连,支持 DeepSeek 和 MiniMax。配置里填一个 base_url + api_key 就行了,任何 OpenAI 兼容的 API 都能用。更轻量,也方便国内网络环境。
并发与速度
原版是串行处理:N 篇文章就调 N 次 LLM,一次等一次。我加了 ThreadPoolExecutor,默认 3 个 worker 并发跑摘要。12 篇文章从 1 多分钟压缩到 20 秒左右。
飞书推送:主动节流
原版飞书推送没做任何限速。自定义机器人约 5 msg/sec 的限频,连续推快了就返回 code 11232。我加了 1.2 秒的最小间隔,遇到 11232 自动等 2s→3s→5s 指数退避重试。
日志:print() → logging
原版全程 print(),跑完了控制台一关,什么痕迹都没有。我换成了标准的 logging 模块:控制台输出 INFO 级别,文件输出 DEBUG 级别,每次运行自动生成日志文件。还挂了全局异常钩子,万一哪个地方崩了,栈信息能写进日志文件,不会悄无声息。
数据库扩展
原版数据库只有去重功能。我加了 category、pushed、content 三个字段,新增了 store_pending、get_pending_articles、mark_pushed 三个方法,支撑 pending 队列。
其他
- Telegram 支持被我移除了——我没在用
- Python 版本降到了 3.6.8 兼容——服务器上的 Python 比较老
- Dockerfile 和 Railway 部署配置去掉了——用不到
效果
改动落地后跑了一段时间,飞书群里的画风变了:
- 信息源不再被高产源绑架,固定关注必读,随机池拓宽视野
- 新内容少了也不会空转,pending 队列自动补位
- 推送到手机上的每一条都过了质检,没有「请启用 JS」的垃圾,没有格式乱七八糟的半成品
- 每天总有一两篇是「意外刷到的惊喜」,比如 Hillel Wayne 讲数学对程序员的重要,以及 Brutecat 讲安全漏洞
用一句话总结这次改造:
工具不应该只是替你看,而是帮你看到你本来会错过的东西。
代码
完整代码在 GitHub 上,欢迎按需改造。
评论
游客无需注册即可评论。
你提交的昵称、邮箱、网址和评论内容会保存在服务端,用于展示评论身份、接收回复及必要的安全审计。
浏览器会本地保存已填游客信息和评论草稿,方便下次免填。
回复提醒会通过站内消息和邮件通知。