本地优先的AI资讯浏览器插件:用轻量级模型实现智能筛选与摘要
1. 信息过载时代的自救为什么我要做这个插件每天早上睁眼第一件事打开手机十几个科技媒体的推送已经堆满了通知栏。某某模型发布了新版本某某公司开源了新的框架某某研究团队发了篇重磅论文某某工具又更新了功能。你一条条点开看看完发现真正跟自己相关的可能只有两三条剩下的全是噪音。更让人头疼的是这些信息里大部分是英文的或者翻译腔浓重的中文读起来费劲不说还经常看完才发现是半个月前的旧闻。我做这个浏览器插件的出发点特别简单我不想再被信息牵着鼻子走我要让信息来找我而且只找我关心的那部分。这个插件叫「中文AI情报站」名字听着挺大其实核心逻辑就一句话——在浏览器里安静地待着自动从你常看的几个信息源里抓取内容用本地跑的轻量级模型做筛选和摘要最后把真正值得看的中文AI资讯推到你面前。整个过程数据不出本地不需要注册账号不需要联网调用第三方API装完就能用。你可能会问市面上不是已经有很多RSS阅读器、资讯聚合工具了吗没错但那些工具的问题在于第一它们大多需要你把内容源手动配置进去配置过程本身就够折腾半小时第二它们没有智能筛选能力你订阅了十个源它就给你推十个源的全部内容信息过载的问题一点没解决第三很多工具是云端处理的你的阅读偏好、浏览记录全在别人的服务器上。我这个插件就是冲着这三个痛点去的——自动发现内容源、本地智能筛选、数据完全留在自己电脑里。适合谁来用呢如果你是AI领域的产品经理、开发者、研究者或者只是对AI动态保持关注的普通从业者每天需要花半小时以上浏览各种资讯但总觉得效率不高那这个插件就是给你准备的。它不追求大而全不做什么社交功能就是一个纯粹的、安静的、本地的信息过滤器。接下来我会从设计思路、核心实现、踩坑记录、优化技巧几个方面把这个插件从零到一的过程完整拆开讲一遍。2. 插件架构设计本地优先到底意味着什么2.1 为什么坚持本地优先而不是云端处理做这个插件之前我试过用现成的云端资讯聚合服务。把几个常看的网站加到收藏夹每天手动刷一遍后来用IFTTT之类的工具做自动化再后来尝试了一些带AI摘要功能的在线服务。用下来的感受是云端服务确实方便但代价是隐私和可控性。你的阅读习惯、点击行为、停留时长这些数据一旦上传你就不知道它们会被用来做什么。而且很多服务免费额度用完之后要么收费要么降级到几乎不可用的状态。本地优先的设计意味着所有数据处理都在你的浏览器里完成。抓取的内容存在IndexedDB里筛选和摘要用的模型是量化后的小模型通过WebAssembly在浏览器里跑。这样做的好处很明显没有网络请求发往第三方服务器没有账号体系没有使用时长限制。代价是模型能力比云端大模型弱一些但对于“筛选摘要”这个任务来说一个几百MB的量化模型已经足够用了。具体来说我选的是ONNX Runtime Web作为推理引擎模型用的是经过蒸馏和量化的中文文本分类模型参数量控制在100M以内量化到INT8之后模型文件大概40MB左右。这个体积对于浏览器插件来说是可以接受的首次加载会花几秒钟之后缓存在本地后续使用几乎无感。2.2 插件的三大核心模块拆解整个插件我分成了三个相对独立的模块每个模块各司其职通过消息传递机制通信。第一个模块是内容采集器。它负责从用户配置的信息源里抓取内容。这里我没有用传统的爬虫思路而是利用了浏览器插件的内容脚本能力。当用户打开一个支持的网站时内容脚本会自动注入提取页面上的文章标题、链接、发布时间、正文摘要等结构化信息。这样做的好处是不需要维护复杂的爬虫规则网站改版了也不容易失效因为提取逻辑是基于DOM结构的通用启发式规则而不是写死的CSS选择器。第二个模块是本地推理引擎。它接收采集器传来的文本先做一轮粗筛把明显不相关的内容过滤掉比如标题里带“招聘”“广告”“活动报名”这类关键词的直接丢弃。然后对剩下的内容做细粒度的相关性打分这个打分是基于用户的历史点击行为和显式反馈点赞/忽略来动态调整的。最后对高分内容生成一段简短的中文摘要方便用户快速判断要不要点开看全文。第三个模块是展示层。我把它做成了浏览器侧边栏的形式点击插件图标就能滑出来。侧边栏里按时间倒序排列筛选后的资讯每条显示标题、来源、摘要和相关性评分。用户可以直接在侧边栏里标记“已读”“忽略”或者“稍后读”这些反馈会实时更新到本地模型里让筛选越来越准。2.3 数据流与存储方案的选择数据流的设计上我遵循一个原则能本地算的绝不发网络请求能增量更新的绝不重新计算。采集器抓到的原始内容先存到IndexedDB的raw_items表里推理引擎处理后的结果存到processed_items表用户的反馈记录存到feedback表。三个表通过item_id关联查询的时候用索引加速。存储方案上我对比过几种选择。localStorage容量太小只有5MB左右存几百条资讯就满了。IndexedDB容量大得多而且支持索引和事务适合这种结构化数据的存储。唯一需要注意的是IndexedDB的异步API用起来比较繁琐我封装了一层Promise化的工具函数用起来跟localStorage差不多方便。这里有个细节值得提一下IndexedDB在不同浏览器里的实现有差异尤其是Safari的隐私模式下IndexedDB的写入可能会被限制。我的处理方式是检测到写入失败时自动降级到内存存储并提示用户当前处于隐私模式数据不会持久化。3. 内容采集器的实现细节与避坑3.1 内容脚本注入时机的选择内容脚本的注入时机看起来是个小问题实际做的时候踩了不少坑。最开始我用的是document_idle也就是页面加载完成后注入。这个时机对于大多数静态页面没问题但对于那些用前端框架渲染的网站document_idle触发的时候页面内容可能还没渲染出来提取到的就是空数据。后来我改成了document_end配合MutationObserver监听DOM变化。具体做法是页面DOM结构稳定后先提取一次然后启动一个观察器如果检测到主要内容区域有新增节点延迟500毫秒再提取一次。这样既能覆盖静态页面也能覆盖动态渲染的页面。延迟500毫秒是为了避免频繁触发提取给页面渲染留出缓冲时间。还有一个特殊情况是无限滚动的页面。用户往下滚新内容不断加载。我的处理是在侧边栏里加了一个“手动刷新”按钮用户滚动到一定程度后可以点一下触发一次全量提取。自动监听滚动事件的话性能开销太大而且容易误判。3.2 结构化信息提取的通用策略提取文章信息的时候我没有针对每个网站写专门的规则而是用了一套通用的启发式策略。核心思路是找到页面上最像“文章列表”的那个容器然后从里面提取条目。具体步骤是这样的先遍历页面上所有的article、section、div元素计算每个元素的“文章密度”——也就是它包含的链接数量、文本长度、标题类元素h1-h6的数量。密度最高的那个元素大概率就是文章列表容器。然后在容器内部把每个直接子元素当作一个候选条目从中提取标题找最长的文本节点或者h标签、链接找第一个a标签的href、时间找包含日期格式文本的节点。这套策略在大多数新闻站和博客上都能工作准确率大概在八成左右。剩下的两成主要是那些结构特别奇葩的网站或者把文章列表放在iframe里的网站。对于iframe的情况我暂时没有处理因为跨域限制太多投入产出比不高。3.3 去重与增量更新的工程实现去重是个看似简单实则麻烦的问题。最简单的去重是用URL做唯一标识但实际中同一个内容可能有多个URL带不同的追踪参数或者URL变了但内容没变。我的方案是URL规范化内容指纹双重去重。URL规范化就是把查询参数里的追踪参数utm_*、ref、source之类的去掉只保留路径和必要的参数。内容指纹是对标题正文前200字做SimHash得到一个64位的指纹。存储的时候两个都存查询的时候先按规范化URL查查不到再按SimHash的汉明距离查距离小于3认为是重复。增量更新方面每次提取的时候只处理发布时间在最近72小时内的条目更早的默认忽略。这个时间窗口可以在设置里调整但我不建议设得太长因为本地模型的推理速度有限一次处理太多内容会卡顿。去重策略优点缺点适用场景URL精确匹配实现简单速度快无法处理URL变化内容源稳定的场景URL规范化匹配能过滤追踪参数规则需要维护大多数新闻网站SimHash指纹能识别内容重复计算开销稍大多来源聚合场景混合策略准确率高实现复杂度高本插件的选择4. 本地推理引擎小模型也能干好筛选的活4.1 模型选型为什么不用大模型一开始我想过用云端的大模型API来做摘要和筛选效果肯定好但问题也很明显一是要花钱二是要联网三是隐私没保障。后来转向本地模型又面临选择是用规则引擎还是用机器学习模型纯规则引擎我试过写了几十条规则比如标题包含“发布”“开源”“突破”就加分包含“招聘”“活动”就减分。效果怎么说呢刚开始还行但规则越加越多维护成本越来越高而且规则之间经常打架。比如“某公司发布招聘广告”这条按“发布”加分按“招聘”减分最后得分取决于哪个规则权重高调起来很痛苦。后来决定用机器学习模型。选型的时候考虑了几个因素模型大小要控制在50MB以内推理速度要在100毫秒以内中文支持要好。最终选了一个基于BERT架构蒸馏出来的小模型6层Transformer隐藏层维度256参数量大概30M。量化到INT8之后模型文件35MB左右在浏览器里用ONNX Runtime Web跑单条推理时间大概50到80毫秒完全可以接受。4.2 训练数据的构造与标注策略模型要能筛选出用户关心的内容就得有训练数据。我没有现成的标注数据只能自己造。方法是用规则引擎先跑一遍把结果分成“高相关”“中相关”“低相关”三档然后人工抽查修正。修正后的数据用来训练模型模型跑出来的结果再人工抽查再修正迭代了几轮之后模型效果就上来了。标注的时候我定义了四个维度主题相关性是否跟AI相关、时效性是否近期内容、信息密度是否有实质内容而非标题党、来源可信度来源是否权威。每个维度打1到5分加权求和得到最终的相关性分数。权重是主题0.4、时效0.2、信息密度0.3、来源0.1这个权重是根据我自己的阅读偏好调的用户可以在设置里改。训练数据量大概5000条左右覆盖了新闻、博客、论文摘要、产品发布等不同类型的内容。训练是在本地用PyTorch跑的跑完之后导出ONNX格式再用ONNX Runtime的量化工具转成INT8。整个流程跑下来大概半天时间不算太折腾。4.3 推理性能优化的几个关键手段在浏览器里跑模型性能是绕不开的问题。我做了几件事来优化第一是模型量化。FP32转INT8之后模型体积缩小到四分之一推理速度提升大概两倍精度损失在可接受范围内准确率下降不到2%。第二是批处理。单条推理的时候每次都要做一遍预处理和后处理开销不小。改成批处理之后一次处理16条预处理和后处理的开销被摊薄了整体吞吐量提升明显。第三是缓存。同样的文本内容如果之前已经推理过直接把结果从缓存里取出来不用重新跑模型。缓存用LRU策略最多存1000条超出之后淘汰最久未使用的。第四是Web Worker。推理放在Web Worker里跑不阻塞主线程。这样即使用户在浏览网页侧边栏的更新也不会导致页面卡顿。实测数据在2020款的中端笔记本上批处理16条内容的推理时间大概1.2秒平均每条75毫秒。如果开启缓存重复内容的处理时间几乎为零。5. 侧边栏交互设计与用户反馈闭环5.1 侧边栏的布局与信息层级侧边栏的宽度我设的是360像素这个宽度在大多数屏幕上不会遮挡主要内容同时又能显示足够的信息。布局上分三层顶部是筛选栏中间是资讯列表底部是状态栏。筛选栏放了几个快捷按钮“全部”“未读”“稍后读”“已忽略”点击切换列表的过滤条件。还有一个搜索框支持按关键词过滤。搜索是在本地做的对标题和摘要做全文匹配速度很快。资讯列表是核心区域。每条资讯显示标题最多两行超出省略、来源域名、发布时间相对时间比如“3小时前”、摘要最多三行、相关性评分用颜色条表示绿色高分、黄色中等、灰色低分。点击标题在新标签页打开原文点击评分条可以手动调整评分点击右侧的菜单图标可以标记已读、稍后读或忽略。底部状态栏显示当前列表的统计信息比如“共128条未读23条今日新增15条”。还有一个刷新按钮手动触发一次内容采集。5.2 用户反馈如何驱动模型迭代用户反馈是这个插件能越用越准的关键。我设计了三种反馈方式显式反馈用户点击“有用”或“没用”按钮直接告诉插件这条内容是否符合预期。这个信号最强权重最高。隐式反馈用户点击标题打开原文说明对这条内容感兴趣算正向信号。用户快速划过在列表里停留时间少于1秒就滚走算负向信号。修正反馈用户手动调整相关性评分比如把系统判为低分的内容手动调高或者反过来。这个信号用来纠正模型的偏差。这些反馈数据存在本地的feedback表里每次模型推理之前会先用最近100条反馈数据对模型做一次轻量级的微调其实就是调整输出层的偏置项让模型适应用户的最新偏好。这个微调过程很快大概几十毫秒用户无感知。5.3 避免信息茧房的几个设计考量做推荐系统很容易陷入信息茧房——用户越喜欢看什么系统就越推什么最后视野越来越窄。我在设计的时候特意做了几个反茧房的机制一是多样性约束。每次展示的列表里同一个来源的内容不超过3条同一个主题标签的内容不超过5条。这样即使用户对某个话题特别感兴趣也不会被单一话题淹没。二是随机探索。列表里会随机插入1到2条相关性评分中等但主题跟用户历史偏好差异较大的内容给用户一个“尝鲜”的机会。如果用户点击了说明对这个新方向有兴趣后续会逐步增加这类内容的权重。三是来源轮换。如果用户配置了多个信息源系统会尽量均匀地从各个来源抽取内容而不是只推某个高产出的来源。这些机制在代码里就是几行过滤逻辑但效果很明显。我自己的使用体验是用了两周之后列表里的内容既有我关心的老话题也会时不时出现一些我没想到但确实有意思的新方向。6. 开发过程中踩过的坑与解决方案6.1 跨域请求被拦截的绕行方案内容脚本注入到目标页面之后如果要抓取页面上的内容直接读DOM是没问题的。但如果要发网络请求去获取更多数据比如调用某个API获取评论数就会遇到跨域限制。浏览器的同源策略不允许内容脚本向非当前域名的地址发请求。我的解决方案是把网络请求的逻辑放到background脚本里。Background脚本运行在插件的上下文中不受页面同源策略的限制可以自由发起跨域请求。内容脚本通过chrome.runtime.sendMessage把请求参数发给backgroundbackground发请求拿到数据后再传回来。这个绕行方案是浏览器插件开发的标准做法但第一次做的时候确实卡了我半天。还有一个细节Manifest V3之后background脚本从持久化后台页变成了Service Worker生命周期由浏览器管理不保证一直存活。所以不能把状态存在background的全局变量里得存到chrome.storage里。这个变化导致我重构了一部分代码把状态管理从内存迁移到了storage。6.2 模型加载失败的降级处理本地模型加载失败的情况比我想象中要多。有的是因为浏览器不支持WebAssembly的某些特性有的是因为模型文件下载不完整有的是因为内存不足导致加载中断。最开始我没做错误处理模型加载失败之后整个插件就卡在加载界面用户完全不知道发生了什么。后来我加了一套降级机制模型加载失败时自动切换到规则引擎模式。规则引擎虽然不如模型准但至少能用。同时在侧边栏顶部显示一个提示条告诉用户当前处于“基础模式”并提供一个“重试加载模型”的按钮。如果用户点击重试就重新走一遍加载流程。规则引擎的规则我精简到了20条左右覆盖最常见的加分项和减分项。虽然粗糙但作为降级方案够用了。实测下来模型加载成功率在95%以上降级模式很少被触发。6.3 内存占用过高的排查过程开发到中期的时候我发现插件用久了之后浏览器会变得很卡。打开任务管理器一看插件的内存占用飙到了800MB以上。这个肯定不正常一个资讯聚合插件不应该吃这么多内存。排查过程是这样的先用Chrome的开发者工具抓了一份内存快照发现IndexedDB的缓存对象占了大头。原来是我在查询数据的时候习惯性地把整个表的数据都加载到内存里再过滤而不是用IndexedDB的索引做条件查询。数据量小的时候没问题数据量大了之后内存就爆了。修复方案是把所有查询都改成基于索引的条件查询只取需要的数据。另外加了一个定时清理任务每天凌晨把30天前的旧数据删掉。改完之后内存占用稳定在80MB左右降了一个数量级。这个坑给我的教训是IndexedDB虽然叫“数据库”但它的查询能力比SQL数据库弱很多用的时候要像对待API一样谨慎不能想当然地全量加载。7. 实际使用效果与几个实用技巧7.1 我的日常使用流程用了几个月之后我的日常流程已经固定下来了。早上到工位打开浏览器侧边栏自动刷新大概有十几条新内容。我先扫一遍标题和摘要把明显不相关的点“忽略”把感兴趣的标“稍后读”。整个过程大概两分钟比之前刷各种网站快了十倍不止。中午休息的时候打开“稍后读”列表挑几篇深度文章仔细看。看完之后回到侧边栏把已经读过的标记为“已读”顺便给几篇质量高的点个“有用”。这些反馈数据会在后台默默更新模型让明天的筛选更准。晚上下班前再刷一次看看有没有下午新出的内容。如果有特别重要的直接点开看如果没有就关掉浏览器走人。整个过程很轻松不再有那种“信息永远看不完”的焦虑感。7.2 配置信息源的经验分享信息源的质量直接决定了插件的使用体验。我试过几十个不同的来源总结下来有几个经验优先选更新频率稳定的源。有些网站一个月才更新一两篇加进去之后列表里全是旧内容反而干扰视线。我一般只加日更或周更的源。中英文源搭配。纯中文源覆盖面有限纯英文源读起来累。我的配置是三分之二中文源加三分之一英文源英文源的内容让模型生成中文摘要读起来轻松很多。定期清理低质源。用了一段时间之后如果某个源的内容我几乎从来不点开就把它删掉。信息源不是越多越好精简到十个左右最舒服。利用分组功能。我把信息源分成了“必看”“常看”“偶尔看”三组侧边栏里可以按组过滤。必看组的内容会优先展示偶尔看组的内容只在没有新内容的时候才出现。7.3 让筛选更准的几个调参建议模型的相关性评分阈值是可以调的。默认阈值是0.6高于这个分数的内容才会出现在列表里。如果你觉得列表里内容太少可以把阈值降到0.5如果觉得噪音太多可以升到0.7。我自己的经验是0.55到0.65之间比较合适具体看当天的信息量。反馈的权重也是可以调的。默认显式反馈权重是1.0隐式反馈是0.3。如果你希望模型更快地适应你的偏好可以把显式反馈权重调到1.5甚至2.0。但调太高也有风险模型可能会过度拟合你最近几次的点击行为导致推荐结果波动太大。还有一个隐藏参数是“探索率”控制随机探索内容的比例。默认是0.1也就是每十条内容里有一条是随机探索的。如果你觉得列表太“跳”可以降到0.05如果你希望看到更多新方向可以升到0.2。这些参数都在设置页面里用滑块调整调完立即生效不需要重启插件。我建议刚开始用的时候保持默认值用了一周之后根据自己的感受再微调。8. 后续可以继续折腾的方向这个插件目前的功能已经能满足我的日常需求了但还有几个方向我觉得值得继续做。一个是多设备同步现在数据存在本地换电脑就得重新配置。如果做一个端到端的加密同步方案把配置和反馈数据加密后同步到用户自己的云存储里就能解决这个问题。另一个是移动端适配现在只能在桌面浏览器上用如果做成PWA手机上也能用就方便多了。还有一个方向是社区共享规则用户可以把调好的筛选规则导出分享给别人新人不用从零开始调参。不过这些都是后话了眼下这个版本已经足够好用。如果你也在被信息过载困扰不妨试试这个思路——把筛选的活交给本地模型把选择权留给自己。工具不需要多复杂能解决实际问题就行。