老实说,当我第一次看到“AI原生RSS阅读器”这个概念的时候,第一反应是:这不就是把大模型塞进一个 RSS 工具里吗?但真正动手把一个叫Oksskolten的阅读器从零搭出来之后,我发现自己之前的理解确实太片面了。RSS 不算新鲜,信息过载在今天反而比十年前更严重;大模型也不算新鲜,人人都能调接口。把这两者放在一起,真正难的点不是“接入模型”,而是怎么接得顺手、怎么接得不像一个「带 AI 按钮的普通阅读器」,而是从底层逻辑上就为模型能力重新设计了一遍工具。
这篇文章是我对这个项目的一次完整复盘。我会从为什么做、架构怎么选、核心功能怎么实现、踩过哪些坑、以及最后给想抄作业的同学什么建议,一条条讲清楚。它不是那种“跑个 demo 就发推”的项目总结,里面有不少我在实际开发中反复调整才想明白的东西。如果你也在做 RSS 相关工具,或者想把大模型塞进一个具体产品里,这篇应该能让你少走一点弯路。
1. 为什么我会做一款AI原生RSS阅读器
1.1 信息过载与RSS的尴尬处境
我一直是 RSS 的重度用户,订阅了大概 200 多个源,从技术博客到智库报告,从行业新闻到个人 newsletter。按理说 RSS 应该帮我高效阅读,但实际上每天打开阅读器,面对的是一屏一屏的“原始文章堆”。标题、摘要、全文混杂在一起,不同源反复报道同一件事,大量水文和广告软文混在中间。我只能靠自己的判断力去筛,一天两小时刷完,真正有价值的信息不到十分之一。
问题出在哪?RSS 这套协议本身提供的是“原始订阅能力”,它把内容分发给你,但并没有帮你做信息整理。传统阅读器做的更多是排版优化、未读管理、多端同步,本质上是把凌乱的全文变成更好看的列表。可是信息过载的核心矛盾,不是“列表不好看”,而是“需要人花时间判断哪些值得读、哪些不需要读”。
这种情况下,AI 似乎理所当然应该出场。但大部分现有工具的 AI 功能,是在阅读列表里加一个“AI 摘要”按钮,点一下才调模型生成一段总结。这种用法我试过,几十篇文章挨个点按钮,体验非常割裂。它更像是在一个旧工具外面贴了一层人工智能,并没有改变阅读流程本身。
1.2 “AI增强”与“AI原生”到底差在哪
我后来想明白一个概念上的区别:AI增强和AI原生是两件事。AI增强,指的是“原来的功能不变,AI 在旁边辅助”;AI原生,指的是“整个产品的工作流以 AI 为核心重新设计”。
我用一个生活化的类比。传统手机拍照加个美颜滤镜,那是增强;但今天手机拍照默认就是靠多帧合成、计算摄影来成像,算法在拍照的每一步都起作用,这叫原生。RSS 阅读器也一样——不是“抓完文章,用户在看之前可以点一个摘要按钮”,而是“抓完文章,系统自动完成摘要、分类、去重、聚类,用户打开看到的是已经整理好的结果”。这就是我在 Oksskolten 里想实现的东西。
实现这种体验,意味着系统在抓取和展示之间,插入了一个“AI 处理层”。每一篇文章进来之后,先被模型阅读和理解,生成摘要、提取关键词、判断类型、输出结构化信息,然后才进入用户的阅读队列。用户面对的不再是 200 个源丢过来的原始内容,而是一个已经被过滤、去重、归类过的信息流。这个流程里,模型不是辅助你阅读的工具,而是阅读管线上不可分割的一部分。
1.3 项目目标与功能边界
当然,“AI 原生”也不能无限膨胀。刚开始我把需求想得很大:想做全文自动重写、想做个性化推荐、想做智能生成周刊、想做社区分享。后来冷静下来,划定了最小可用范围。
最终我确定的核心功能只有四个:智能摘要、自动聚类、划词问答、质量过滤。这四个功能覆盖了“减少阅读量、看清事件全貌、快速获取解释、过滤低质内容”这四个最痛的场景。我不做全文重写,因为会改变作者原意,伦理和法律风险都高;我也不做社交推荐,因为那跟 RSS 的“用户自主订阅”精神是冲突的。
这个边界很重要。很多个人项目死掉,不是因为做得太少,而是因为野心太大,最后核心功能都没打磨好。AI 原生产品尤其容易掉进“什么都能做但什么都不精”的坑。
2. 核心架构与技术选型
2.1 总体数据流解析
Oksskolten 的数据流是我画的最多的一张图。整条链路可以概括成一条单向管道:
订阅源 → 抓取调度器 → 正文提取器 → 内容净化器 → AI 处理引擎 → 存储与索引 → 阅读界面这个顺序看着简单,但每一步都有学问。
抓取调度器负责按周期获取源内容,它不关心文章内容是什么,只负责拿到原始数据。紧接着正文提取器把 HTML 或 RSS 摘要转成干净的全文。这一步筛掉导航菜单、页脚、广告脚本。内容净化器做的是清理:去掉无意义字符、修正乱码、统一编码格式。
然后才轮到 AI 处理引擎。这里我做了一个经验总结:AI 不要直接吃原始网页抓下来的文本,否则模型会把导航文字、版权信息、无关推荐一起当成正文去总结,效果非常差。必须经过前面的净化步骤,喂给模型的才是高质量正文。
处理完的文章会带着摘要、标签、分类等元信息进入存储层,同时生成向量索引。用户在界面看到的内容,已经是这条流水线的成品,不是原始半成品。
2.2 文章解析与正文提取方案
正文提取是整个项目里最容易被低估的部分。RSS 源有两种常见情况:一种是源已经提供了全文内容,那直接取<content>字段就行;另一种是只提供摘要,这时候必须去抓取原始页面,再从 HTML 里提取正文。
针对第二种情况,我一开始想过用比较重的方式,比如训练一个专门模型来识别正文区域。后来发现成本太高,而且训练数据难以覆盖各种网页结构。实际采用的方案是“文本密度打分法”——解析 HTML 成 DOM 树,遍历所有块级元素,根据文本长度、链接密度、标点符号比例来打分。一个段落如果文字很多、链接很少,很可能就是正文;反过来,如果一段内容满屏都是<a>标签,那基本可以判定是导航或者文章列表。
这个方案看起来很土,但实测下来稳定度很高。配合几个兜底规则,比如优先选择<article>标签内部的内容,实在找不到就取整页去掉<nav>、<footer>、<script>、<style>之后的文本,能覆盖绝大多数站点。真正需要为每个站单独写解析规则的,不到 5%。
2.3 AI能力如何接入:本地优先还是调用云端大模型
这是整个项目里我纠结最多的一个决策。云端大模型的摘要质量确实更高,表达更自然,理解能力更强,但需要把用户订阅的文章原文发送到远端服务,这里牵扯隐私、费用、网络稳定性三个问题。本地小模型则完全相反:离线可用、隐私可控、免费,但质量上限确实低一些。
Oksskolten 最终采用的是“本地优先 + 云端可选”的双轨策略。默认情况下,摘要和关键词生成跑在本地模型上;划词问答因为需要更强的推理能力,默认走云端通用模型接口,但如果用户愿意,也可以手动切到本地模型,代价是要等更久。
从产品角度讲,这种设计让用户自己掌握隐私和质量的平衡点,而不是替用户做决定。很多开源项目把这做成二选一,但我认为应该允许“摘要用本地、问答用云端”这种混合模式。实际运行的体验是:本地模型生成的摘要虽然偶尔语句生硬,但信息密度足够;云端模型更适合回答“这篇文章的核心论点是什么”这类开放问题。
2.4 存储与索引设计
存储层我用的是嵌入式数据库,好处是单文件、零配置、备份方便,非常适合个人自托管场景。表结构上分成几个核心部分:订阅源表、文章表、AI 元信息表、阅读状态表、向量索引表。
文章表保存标题、原文、净化后正文、抓取时间、原文链接等基础信息。AI 元信息表保存摘要、关键词、内容类型、质量评分。向量索引表负责给文章标题和摘要分别生成向量,这是做聚类和语义检索的基础。
我踩过的一个坑是:向量索引在文章量超过一定数量之后,查询速度会肉眼可见地下降。之后我做了两个优化,一是只对标题和摘要做向量,不对全文做向量,因为全文向量太占空间;二是给向量索引表单独做了分区,按月份划分,查询时只搜索最近 90 天的索引。这样在几万篇文章的规模下,检索依然能保持在毫秒级响应。
3. 关键功能背后的实现思路
3.1 智能摘要为什么不能直接截取正文
在实现摘要之前,我犯过一个直觉错误:把文章前 N 个字符截出来,或者把每个段落第一句拼起来,以为这就是摘要。这种抽取式摘要虽然速度快,但很难抓住文章的核心脉络。比如一篇技术教程,开头通常在讲背景和安装,最精华的用法和坑点在中后段;直接截前 500 字,读者根本看不到重点。
后来我改用生成式摘要,但这里有个新问题:大模型输入有 token 限制,长文章必须分段处理或截断。我的做法是:先把正文压缩到模型可接受的长度,通过“提取每段的核心句”做一个粗筛,把最关键的 20% 文本保留下来,再把这些内容连同标题一起喂给模型生成摘要。
这个两步法的好处是兼顾精度和效率。模型读到的不是随机截断的开头,而是经过统计筛选后代表全文重点的内容。生成的摘要控制在 3 到 5 句话,第一句交代事件,第二句给核心信息,后面补充关键细节。实测下来,用户判断“这篇要不要点进去看”所需的信息量,这个长度刚好。
3.2 自动聚类与话题分组
同一个新闻事件,第二天必然出现在至少五个订阅源里,标题措辞不同,角度不同,但本质上是同一件事。传统阅读器把这几篇分散开,逼着用户反复读到重复内容。我希望能做到“同一事件只出现一次,点进去能看到不同来源的报道”。
实现思路是这样的:每篇文章进来后,用标题和摘要分别生成向量。定时任务每隔一段时间跑一次聚类:两篇文章的向量余弦相似度超过阈值,就判定为同一事件。聚类算法我没有选特别复杂的,而是用并查集把相似文章连接成组,每组选一个“代表文章”展示在主列表里,其他文章收进折叠区。
阈值的选择很关键。设太高,同一事件的文章无法聚合;设太低,会把两个无关话题强行合到一起。我在测试集上调试过,最终取的是 0.82,主要用来判断标题加摘要整体语义相似度,而不是只看标题。实际体验中,同一事件聚合的准确率能在八成以上,偶尔误判,我会让用户手动“拆开”这个分组,并把修正结果记录到系统里帮助后续优化。
3.3 划词问答与对话式检索
划词问答是 Oksskolten 里最“炫技”但最实用功能。用户阅读文章时,可以选中一段不理解的内容,直接问“这句是什么意思”,系统会结合上下文给出解释。这个能力本质上是 RAG(检索增强生成),但实现上有一些细节值得说。
首先要把文章切分成语义完整的切片,不是按固定字符数硬切,那样会把一句话从中间切断。我的做法是按段落标记切分,段落过长时再按句号断句,保证每个切片是完整的语义单元。提问时,把选中片段以及它前后各一个切片作为上下文,拼进提示词,让模型解释选中的内容。
配合全文检索,这个功能还能做更大范围扩展:用户可以不选中文本,直接问一个和整篇文章相关的问题,系统会检索全文切片,找到最相关的几块内容,再生成回答。这里我一定会附上引用来源的位置,让用户能点回去核对原文,避免无脑信任模型输出。
3.4 内容质量过滤与去重
质量过滤我使用了规则和模型结合的方式。规则层负责识别明显的广告、推广软文、或只有几百字没营养的内容;模型层负责给文章打上内容类型标签,例如“资讯”“教程”“观点”“讨论”“转载”,并给出一个 0 到 1 的质量分。
用户可以根据自己的偏好设置过滤器,比如“教程类和质量分高于 0.6 的保留,资讯类只看高分源前 20 条”。这个设置直接作用在阅读列表生成环节,让每个人看到的信息流都不一样。
去重和聚类稍有不同。聚类解决的是“同一事件的多个报道”,去重解决的是“完全一样的文章在不同源重复发布”。去重我用的是内容哈希加向量相似度双重判断:完全一致的内容直接丢弃,相似度超过 0.95 且来源不同的,保留发布时间更早、来源权重更高的那篇。这件事必须放在 AI 处理之前做,否则模型会对重复内容白白消耗算力。
4. 从零到可用的完整构建过程
4.1 工程骨架与模块划分
Oksskolten 的工程结构按照数据流的阶段划分清楚,避免后期改不动。核心模块包括:fetcher(抓取)、parser(正文解析)、cleaner(内容净化)、engine(AI 处理)、storage(存储访问)、api(接口层)、web(前端界面)。
模块之间通过简单的事件机制通信:抓取完成触发解析完成事件,解析完成触发 AI 处理事件。好处是每个模块可以独立开发和测试,坏处是如果抓取量大,事件链可能变得难以追溯。我做了统一的日志记录,每条文章都有处理轨迹,出了问题可以直接看到卡在哪个阶段。
构建顺序上,我不建议先把 UI 做得漂亮。我的经验是先把数据管道跑通:能抓到一个源、能提取正文、能调用模型生成摘要、能存进数据库,这时候核心价值已经成立,UI 只是外皮。我在前期花了一周把管道打通,UI 反而是在后面两天内快速搭出来的。
4.2 订阅管理与抓取调度的实现细节
抓取调度是很容易被低估的模块。如果只是写一个定时任务,每半小时把所有源扫一遍,实际跑起来会遇到大量问题:有的源更新慢,频繁抓取浪费资源;有的源更新快,半小时内可能有新内容错过了;有的源对抓取频率敏感,容易被临时封禁。
我最后实现了“按源的更新频率动态调整抓取间隔”的逻辑。系统记录每个源的更新时间历史,根据平均更新间隔计算下一个抓取时间点。如果一个源已经连续 7 天没有任何更新,抓取频率自动降低;一旦发现新文章,立刻恢复高频状态。
另外,抓取请求要带上合理的 User-Agent,遵守目标的robots规则;对返回 304 的源使用条件请求跳过下载全文。这些细节不做好,整个阅读器就会被各种抓取异常搞崩。我的开发机上曾有一次因为循环抓取同一个坏源,把日志文件刷到几个 GB,排查到半夜才发现是重试逻辑没加次数上限。
4.3 大模型在阅读流程中的接入示例
下面给出一段简化后的接入逻辑,展示模型调用是怎么跟文章处理流程结合的。这段代码不依赖特定模型厂牌,抽象成接口后可以灵活替换后端实现。
class SummaryGenerator: def __init__(self, client): self.client = client def summarize(self, title, cleaned_content): prompt = f""" 你是一个信息助理。请阅读以下文章内容,输出一段中文摘要。 要求: 1. 不超过 5 句话; 2. 第一句概括核心事件; 3. 后续句子补充关键细节、数据、结论; 4. 只输出摘要本身,不要输出任何额外解释。 标题:{title} 正文片段: {cleaned_content[:4000]} """ result = self.client.chat(prompt) return self._clean_output(result) def _clean_output(self, text): # 去掉模型偶尔多出来的引号、前缀等 return text.strip().strip('"“”').strip()实际生产里,我会把这段摘要生成函数放进处理管线中,每篇净化后的文章都会并行调用,处理完再入库。调用大模型时一个重要经验是:给模型的提示词,指令部分固定,文章内容部分动态拼接,格式越统一,模型输出越稳定。如果想进一步压缩成本,还可以给摘要增加定时批处理,每 10 篇文章打包一次性请求,但对单篇摘要质量会有轻微影响,我最终没有采用。
4.4 前端与交互设计考量
前端阅读体验上,我坚持三个原则:默认视图要为“信息整理后”做优化、键盘操作要快到能脱离鼠标、AI 的痕迹不能喧宾夺主。
主界面有三种视图。时间线视图是按时间排序的折叠卡片,每个话题组只显示代表文章标题和摘要,展开才看到组内其他源。专题视图是按自动聚类结果组织的,适合看“这两天发生了什么”。搜索视图是问答入口,可以直接自然语言提问,例如“最近关于某技术的讨论”,系统会返回相关文章和一段汇总。
AI 处理结果在界面上是以“标签”和“摘要块”呈现的,我刻意弱化“这是 AI 生成的”这一视觉信号。因为对用户来说,他们只需要看到“这个信息被整理过”,而不是时刻提醒他们在和一个模型互动。任何产品如果在界面上到处标注 AI 徽章,反而会让人觉得是堆功能、秀技术。
5. 实测中踩过的坑与排查方案
5.1 正文提取不准导致AI瞎总结
第一个让我头疼的问题是:当正文提取器错判时,模型基于错误输入输出的摘要会非常荒谬。有一次我在测试某科技媒体的一篇文章,摘要居然总结成了“本文介绍了网站底部导航栏的各个链接”——因为正文提取器把整个侧边栏和页脚当成正文了。
排查过程让我意识到:正文提取的准确率直接决定了 AI 功能质量的上限。针对这个问题,我加强了两道防线。第一道:提取正文时加入“链接密度阈值”,一个区块如果链接占比超过 30%,直接判定为导航类内容,不参与评分。第二道:模型调用前加入“正文质量自检”——如果净化后的正文长度小于 300 字,或者正文中大量出现 “首页”“关于我们”“联系”这类导航词,就把这篇文章标记为“解析可疑”,不执行 AI 摘要,等待人工检查或下次抓取时再重试。
这个坑给测试方法带来的教训是:不能只拿几个热门站点调试正文提取器,要拿至少 50 个不同类型的站点做回归测试,尤其是那些结构奇怪的个人博客和新闻聚合站。
5.2 重复文章与滚动更新的处理
重复文章比我想象得要复杂。最开始我只用标题去重,结果发现同样一篇报道,有的源标题写成“某公司发布新品”,有的写成“新品正式发布,这几点值得关注”,标题看似完全不同,但内容几乎一样。后来改成标题向量相似度加正文哈希双重判断,才把重复率降下来。
还有一个更难处理的情况:原文作者更新了文章。同一篇文章 URL 不变,但标题改了一个词,正文里加了一段更新说明。这时候直接当新文章入库,用户会看到两条几乎一样的;完全去重,又会漏掉作者的更新内容。我的方案是:检测到 URL 已存在但内容哈希变化,就做“版本更新”,保留原文,更新正文、摘要和抓取时间,并在阅读列表中标记“有更新”。这样既不产生重复,也不会漏更新。
5.3 请求超时、限流与失败重试
抓取线上的源,网络问题永远比机器问题多。有的站点响应特别慢,有的站点会主动限流,有的站点偶尔返回 500。我一开始遇到超时就直接标记失败,结果很多正常源被误杀。后来重写了重试策略:连接超时 10 秒,读取超时 15 秒;超时后先等待 30 秒重试一次,如果仍失败,把重试间隔指数递增,最多重试 3 次。重试之间加入随机抖动,避免多个源同时被限流后的“惊群”重试。
对于返回状态码,我做了一个白名单与黑名单的组合判断:404、410 表示永久失效,直接停用订阅源并通知用户;500、502、503 是临时故障,按限流策略重试;401、403 则可能是需要登录或封禁,停止高频抓取,等待人工确认。这套规则上线之后,抓取成功率从最初不到 85% 提到 97% 以上。
5.4 本地模型硬件占用过高怎么办
本地模型是这台机器上的资源大户。最初我在一台没有独立显卡的笔记本上跑,每次生成摘要 CPU 直接拉满,风扇狂转,浏览器都跟着卡。后来做了几个调整,这一节和踩坑的朋友们分享一下。
首先是把模型选型改成量化版本,精度有小幅下降但对摘要任务影响极小,内存占用能降 60% 左右。其次是限制并发:AI 处理的所有请求进入一个队列,同一时间最多跑 1 个生成任务,避免多个模型实例同时加载。还有个办法是缓存:相同文章重复处理时直接读取上次的结果,不再调用模型;聚类任务和摘要任务分开跑,摘要优先级更高,聚类可以等系统空闲时再做。
有人会问“本地模型摘要质量不如云端怎么办”。我的答案是可以分层:固定源里的重要源,可以用云端模型;其余源用本地模型;用户手动标记为收藏的源再触发一次高质量摘要。这种分层策略是我测试下来兼顾成本和效果的最好方式。
6. 给想抄作业的同学几点实打实建议
6.1 功能优先级排序
如果现在让你从零开始做同样的项目,我建议按这个顺序来:
| 优先级 | 功能模块 | 原因 |
|---|---|---|
| P0 | RSS 抓取与正文提取 | 没有稳定数据来源,一切 AI 功能都是空谈 |
| P0 | 存储与重复过滤 | 数据管道的入库环节不干净,后期很痛苦 |
| P1 | 摘要生成 | 最核心的 AI 价值点,快速验证产品方向 |
| P1 | 话题聚类 | 解决“同一事件多篇报道”的高频痛点 |
| P2 | 划词问答 | 工程量和 AI 成本都高,适合做差异化亮点 |
| P2 | 质量过滤 | 需要积累一定数据量后效果才明显,不适合初期做 |
很多同学一上来就想做问答、做智能推荐,实际体验是:AI 摘要都没做稳定,问答更是错漏百出。先把一个点做到 90 分,再扩展,是这类项目最稳的路线。
6.2 数据隐私与自托管边界
这个项目从一开始就定位为“本地优先、自托管”。用户的订阅数据、阅读记录、文章全文都保存在自己的存储里,默认不发送到任何远程服务。只有用户主动开启“云端模型增强”后,文章片段才会发送给模型服务商处理,而且发送的是净化后的正文片段,不是原始 HTML。
自托管工具的边界问题在于“用户要为运维买单”。如果你要做成开源项目,就得把部署流程做得足够简单,一键启动,配置文件清晰,日志可读。我后来把整个项目封装成一个容器镜像,用户只需要准备好订阅源列表,所有依赖自动拉取。这样既保留了自托管的数据所有权,又不至于把用户挡在“会 Docker”这个门槛之外。
6.3 后续扩展空间
这个项目后续的扩展方向很多,我会优先考虑三个:第一个是移动端适配,现在很多人的阅读场景在通勤路上;第二个是“智能日报”,每天定时把昨天最重要的几件事生成一份简报推给用户;第三个是更多源的适配,特别是不再支持 RSS 的平台,可以通过 HTML 扫描来弥补订阅源缺失的问题。
但我个人不会做的一件事,是引入社交媒体式的“推荐流”。RSS 的价值就在于用户自己掌握订阅权,如果最终还是变成算法投喂,那项目就失去灵魂了。AI 在这个工具里的角色应该是“帮用户更好、更快地处理自己选择的内容”,而不是“替用户决定看什么”。
这大概是整个项目里我最坚持的一个原则,也是我觉得“AI 原生”这个词最值得敬畏的地方——原生不是什么都替用户做了,而是在用户划定的范围内,把该做的事做到极致。