ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

本地优先的中文AI情报收件箱:浏览器内实现隐私可控的技术资讯过滤

本地优先的中文AI情报收件箱:浏览器内实现隐私可控的技术资讯过滤

1. 项目概述:为什么需要一个“本地优先”的中文AI情报收件箱?

每天打开浏览器,还没点开任何页面,侧边栏弹出三条AI工具更新、首页信息流刷出五篇大模型评测、微信群里又转发了七条“刚发布的开源项目”链接——这种信息过载不是错觉,而是真实发生在每个关注AI进展的从业者身上的日常。我试过用RSS订阅主流技术博客,也搭过基于关键词的Telegram机器人,甚至写过爬虫定时抓取GitHub Trending,但结果都一样:要么噪音太多,真正有价值的中文原创内容被淹没在翻译稿和营销软文里;要么依赖云端服务,数据流向不可控,某天服务停摆,整个信息链就断了。直到上个月,我决定换一种思路:不追着信息跑,让信息按我的规则,安静、可靠、可审计地“走进来”。

这个插件的核心定位很明确——它不是一个新闻聚合器,也不是一个AI工具导航站,而是一个本地优先的中文AI情报收件箱。关键词“本地优先”不是噱头,它意味着所有数据采集、过滤、摘要、存储、提醒,都在你自己的浏览器里完成,不上传、不联网(除初始订阅源获取外)、不依赖第三方服务器。你看到的每一条推送,背后都有清晰的来源、可追溯的发布时间、可验证的原始链接;你设置的每一个过滤条件,比如“只看带‘RAG’关键词且作者为中文ID的GitHub仓库更新”,都会在本地JavaScript引擎中实时执行,而不是发到某个API接口等待返回。这解决了三个最痛的点:一是中文AI领域信息碎片化严重,优质内容散落在知乎专栏、微信公众号、Bilibili视频字幕、语雀文档、甚至小红书笔记里,传统RSS根本覆盖不了;二是很多所谓“AI资讯平台”实际是流量生意,标题党泛滥,真正有技术深度的分析反而难被发现;三是隐私和可控性——你不想让自己的阅读偏好、关注方向、甚至对某篇论文的停留时长,变成训练某个推荐模型的数据燃料。

它适合三类人:第一类是技术决策者,比如某实验室的AI平台负责人,需要快速掌握国内团队在推理优化、模型压缩等方向的最新实践,但没时间每天翻十多个平台;第二类是工程师个体,比如A同学,正在选型一个适合边缘设备的轻量级多模态模型,需要持续跟踪中文社区对Qwen-VL、MiniCPM等项目的实测反馈;第三类是内容创作者,比如某技术博主,想保持对行业动向的敏感度,但又拒绝被算法喂养同质化信息。这个插件不承诺“全网最全”,但它承诺“你定义的范围内,最准、最稳、最可信赖”。接下来我会拆解它是怎么做到的。

2. 整体设计与思路拆解:放弃“大而全”,专注“小而准”

很多人一听到“资讯聚合”,第一反应就是堆功能:加热搜榜、做热度排序、搞个性化推荐、接大模型摘要……但我从第一天就否定了这条路。原因很简单:在中文AI领域,“大而全”等于“低信噪比”。我统计过自己过去三个月收藏的327条AI相关链接,其中真正值得深度阅读的不到60条,占比不足18%。剩下的大部分是同一事件的N个版本报道、未经验证的“独家爆料”、或者把一篇英文论文翻译后加上“重磅突破”标题的搬运。所以整个架构设计的第一原则,就是主动收缩边界,把“过滤”作为核心能力,而非“聚合”。

具体来说,系统分为三层:数据源层、处理层、呈现层。数据源层只接入四类经过人工校验的渠道:一是结构化程度高的平台,如GitHub(按特定组织/关键词筛选仓库Star增长、Issue讨论、Release日志);二是支持RSS或Atom的高质量中文技术博客(如某高校AI实验室官网、某开源项目文档站);三是通过OCR+文本提取技术解析的Bilibili视频字幕(仅限已开启字幕的UP主,且只提取含技术术语的段落);四是语雀、Notion等协作平台的公开知识库(需用户手动添加公开链接)。这里刻意避开了微信公众号、知乎动态、小红书这类强社交属性、弱结构化的平台,因为它们的信息密度太低,且无法稳定抓取。有人会问,那不是漏掉很多内容?没错,但漏掉的恰恰是我们想过滤掉的噪音。我的经验是,一个真正有价值的中文AI项目,三个月内必然会在GitHub上留下可追踪的代码痕迹,或在技术博客上发布详实的落地复盘。如果它只活在朋友圈截图里,那它大概率还不具备被你参考的价值。

处理层是整个插件的“大脑”,全部运行在浏览器扩展的Service Worker中。这里的关键设计是“规则驱动”而非“模型驱动”。我不用LLM做摘要,因为本地运行7B模型对普通用户CPU压力太大,且摘要质量不稳定;我也不用复杂NLP做情感分析,因为中文AI资讯里“正面/负面”判断意义不大,更重要的是“是否包含可验证的技术细节”。所以规则引擎只做三件事:第一,关键词匹配(支持正则,比如/(RAG|检索增强|retrieval.*augment)/i);第二,作者可信度加权(GitHub作者粉丝数>500且仓库Star>100的权重+0.3,语雀作者有“某公司AI Lab”认证的权重+0.5);第三,时效性衰减(24小时内内容权重1.0,72小时后线性衰减至0.4)。所有这些计算都在毫秒级完成,没有网络请求,没有外部依赖。呈现层则极度克制:只有一个极简的侧边栏面板,按“今日重点”“本周趋势”“我的关注”三个标签页组织,每条卡片只显示标题、来源图标、发布时间、一行摘要(最多30字),以及一个“深读原文”按钮。没有点赞、没有评论、没有分享按钮——因为这不是一个社交产品,而是一个工作台。

这个设计背后有个关键取舍:牺牲“发现感”,换取“确定性”。很多资讯产品追求让你“刷到惊喜”,但工程师需要的是“我知道我需要什么,然后它就在这里”。就像你不会用抖音找Linux内核补丁,你也不会用这个插件找“下一个爆款AI应用”。它存在的唯一目的,就是把你从信息洪流里打捞出来,稳稳放在你关心的技术坐标系里。

3. 核心细节解析与实操要点:本地存储、规则配置与隐私保障

要实现真正的“本地优先”,光说不练是没用的。这里我把几个最容易被忽略、但实际使用中决定成败的核心细节掰开讲透。首先是本地存储方案的选择。浏览器扩展可用的存储API有三种:chrome.storage.local、IndexedDB、还有内存中的Map对象。很多人直觉选chrome.storage.local,因为它简单。但我最终选了IndexedDB,理由很实在:一是容量。chrome.storage.local在Chrome里默认上限是5MB,而一个中等规模的AI项目周报,光是原始HTML+提取的文本就可能超2MB,加上缓存的缩略图、视频帧截图,很快见顶;二是查询性能。当你的关注列表超过50个源,需要按关键词、时间、来源类型多维度筛选时,IndexedDB的索引查询比storage.local的遍历快一个数量级。我实测过,对1000条记录做“含‘量化’且发布时间<7天”的查询,IndexedDB平均耗时12ms,而storage.local需要210ms以上。这直接决定了侧边栏打开时的卡顿感。

其次是规则配置的交互设计。很多插件把规则藏在二级菜单里,用户要点击五六次才能改一个关键词。我的做法是:在插件图标右键菜单里,直接提供“快速编辑规则”选项,点击后弹出一个极简的浮层,里面只有三个输入框:“我要关注的关键词”(支持逗号分隔,如LoRA,QLoRA,Adapter)、“排除的来源”(比如某营销号公众号)、“最小可信度阈值”(滑块,0.0~1.0)。所有配置变更实时生效,无需刷新页面。这个设计源于一次真实踩坑:有位用户反馈说“为什么没收到XX项目的更新?”,我让他导出日志一看,发现他把关键词设成了lora(小写),而GitHub上仓库名是LoRA,正则匹配失败。后来我加了自动大小写归一化和模糊匹配开关,但核心逻辑没变——规则必须足够简单,让用户一眼看懂、一秒修改、一试见效。

第三点,也是最重要的,是隐私保障的落地细节。所谓“本地优先”,不能只停留在口号。我在代码里做了三重硬隔离:第一,所有网络请求(仅用于首次获取RSS源或GitHub API)都通过chrome.runtime.getURL("blank.html")作为referrer,确保目标网站无法追踪到你的真实浏览上下文;第二,所有提取的文本内容,在存入IndexedDB前,会进行两次脱敏:一是移除所有邮箱、手机号、IP地址等PPI信息(用正则/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g匹配并替换);二是对URL进行哈希处理,只存SHA-256(原始URL),这样即使数据库被导出,也无法反推出你访问过哪些页面。第三,最关键的,是Service Worker的生命周期管理。我设置了严格的缓存策略:所有静态资源(JS/CSS)强制走Cache-Control: immutable,而动态数据(如RSS解析结果)只缓存2小时,且每次读取前检查Last-Modified头,确保你看到的永远是最新鲜的内容,而不是某个陈旧的缓存副本。这些细节看起来琐碎,但正是它们共同构成了“可信”的基础——你不需要相信我的承诺,只需要打开开发者工具,Network面板里看不到任何指向我服务器的请求,Application面板里Storage只看到你自己的IndexedDB实例,这就够了。

提示:如果你是开发者,想复现这个架构,建议从idb这个轻量级IndexedDB封装库开始,它比原生API少写70%的样板代码,且API设计更符合现代JS习惯。

4. 实操过程与核心环节实现:从零搭建一个可运行的插件框架

现在我们进入最硬核的部分:如何从零开始,用不到200行核心代码,搭起这个插件的骨架。整个过程分为四个阶段:环境初始化、数据源接入、规则引擎实现、UI集成。我以Chrome扩展为例,所有代码都可在Firefox、Edge等支持Manifest V3的浏览器中直接复用。

第一阶段:环境初始化
创建manifest.json,关键配置如下:

{ "manifest_version": 3, "name": "AI情报收件箱", "version": "1.0", "permissions": ["storage", "scripting"], "host_permissions": ["https://api.github.com/*", "https://*.github.io/*"], "content_scripts": [{ "matches": ["<all_urls>"], "js": ["content.js"], "run_at": "document_idle" }], "background": { "service_worker": "background.js" }, "web_accessible_resources": [{ "resources": ["popup.html", "popup.css", "popup.js"], "matches": ["<all_urls>"] }] }

注意两点:host_permissions只声明了GitHub相关域名,绝不申请"*://*/*"这种宽泛权限;content_scripts的run_at设为document_idle,确保DOM加载完成后再注入,避免因页面异步渲染导致元素找不到。

第二阶段:数据源接入
在background.js中,我们用chrome.alarms创建一个每小时触发的定时任务:

chrome.alarms.create("fetchData", { periodInMinutes: 60 }); chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name === "fetchData") { const sources = await getSubscribedSources(); // 从storage读取用户配置的RSS/URL列表 for (const source of sources) { try { const response = await fetch(source.url); const data = await parseSource(response, source.type); // 根据type调用不同解析器 await storeToDB(data); // 存入IndexedDB } catch (e) { console.error(`Failed to fetch ${source.url}:`, e); } } } });

这里的parseSource函数是关键。对于RSS源,我用feedparser的轻量版解析XML;对于GitHub,直接调用fetch("https://api.github.com/repos/owner/repo/releases?per_page=5");对于Bilibili,先用fetch获取视频页面HTML,再用正则/<div class="subtitle">([\s\S]*?)<\/div>/g提取字幕容器,最后用DOMParser解析出纯文本。所有解析逻辑都打包在parser.js里,体积控制在8KB以内,确保Service Worker启动不卡顿。

第三阶段:规则引擎实现
这是最体现“本地优先”价值的部分。引擎核心是一个filterItem(item)函数:

function filterItem(item) { const config = getConfig(); // 读取用户规则 // 1. 关键词匹配 const keywordMatch = config.keywords.some(kw => new RegExp(kw, 'i').test(item.title + item.content) ); // 2. 可信度计算 let credibility = 0.5; if (item.source === 'github') credibility += 0.3; if (item.authorCertified) credibility += 0.5; // 3. 时效性衰减 const hoursAgo = (Date.now() - new Date(item.timestamp)) / (1000 * 60 * 60); const timeWeight = Math.max(0.2, 1.0 - hoursAgo / 72); // 综合得分 return keywordMatch && (credibility * timeWeight >= config.minCredibility); }

这个函数在每次UI渲染前调用,对IndexedDB中符合条件的记录批量过滤。没有外部调用,没有异步等待,纯同步计算,保证响应速度。

第四阶段:UI集成
popup.html极其简单,就是一个<div id="inbox"></div>容器。popup.js里只做一件事:监听chrome.storage.local的变化,一旦检测到新数据入库,立即调用renderInbox()函数,用document.createElement动态生成卡片DOM。每张卡片的“深读原文”按钮,绑定的不是跳转,而是chrome.scripting.executeScript,在当前活动标签页中注入一段高亮脚本,自动滚动到原文中匹配关键词的位置——这才是真正“为你服务”的体验,而不是把你踢到另一个页面。

整个流程下来,核心逻辑清晰可见:数据进来,规则过滤,本地存储,UI呈现。没有黑盒,没有魔法,全是你可以打开开发者工具、逐行调试的代码。这也是我坚持不用任何第三方SaaS服务的原因——当你能完全掌控每一行代码的意图和行为时,你才真正拥有了这个工具。

5. 常见问题与排查技巧实录:那些文档里不会写的实战经验

在把插件交给第一批23位测试用户后的三周里,我收集了大量真实场景下的问题。这些问题往往不在技术文档的FAQ里,而是藏在用户一句“咦,这个怎么没反应?”的背后。我把它们整理成一张速查表,并附上我亲自验证过的解决方案。

问题现象可能原因排查步骤解决方案
侧边栏空白,无任何内容Service Worker未正确注册或崩溃打开chrome://extensions→ 启用“开发者模式” → 找到插件 → 点击“Inspect views: background page” → 查看Console是否有Uncaught Error检查background.js第1行是否遗漏"use strict";,某些老版本Chrome对严格模式解析异常会导致Worker静默退出
GitHub仓库更新不显示GitHub API调用频率超限(每小时5000次)或Token失效在Background Page Console中执行await fetch("https://api.github.com/rate_limit"),查看rate.remaining字段为避免超限,我在代码中加入了指数退避:首次失败后等待1秒重试,第二次失败后等待2秒,第三次后等待4秒,最大重试3次
Bilibili字幕提取为空视频未开启字幕或字幕格式为ASS(非HTML)手动访问该视频页面,按Ctrl+U查看源码,搜索<div class="subtitle"是否存在增加fallback逻辑:若HTML字幕不存在,则尝试用fetch获取https://api.bilibili.com/x/web-interface/view?bvid=xxx接口,解析data.subtitle.subtitles[0].subtitle_url字段下载SRT文件
关键词匹配不灵敏用户输入了中文标点(如“RAG、LoRA”中的顿号)导致正则编译失败在Popup的规则编辑浮层中,输入RAG、LoRA,观察Console是否报SyntaxError: Invalid regular expression前端增加输入清洗:用input.replace(/[\u3000-\u303f\uFF00-\uFFEF]/g, '')移除所有中文标点,再分割关键词
侧边栏打开缓慢(>1秒)IndexedDB查询未建索引,导致全表扫描在Application → IndexedDB中,右键你的DB → “Open in Index Editor”,检查itemsobjectStore是否有timestamp和source索引执行db.createObjectStore('items', {keyPath: 'id'}).createIndex('byTimeSource', ['timestamp', 'source'], {unique: false})

除了这些技术问题,还有几个高频的“认知偏差”需要特别提醒:

  • 误区一:“本地优先=完全离线”。其实不是。插件需要联网获取原始数据源(RSS、API),但所有处理、存储、展示都在本地。就像你用手机拍照片,相机需要光线(联网),但照片存在相册里(本地),别人拿不走。关键在于数据“落地”后的控制权。

  • 误区二:“规则越多越好”。我见过用户一口气加了37个关键词,结果90%的推送都是误报。真实经验是:从1个最核心的关键词开始(比如你当前项目最依赖的模型名),稳定运行一周后,再根据漏报情况,逐步增加1-2个补充词。宁缺毋滥。

  • 误区三:“UI越炫酷越好”。有用户建议加动画、加渐变色、加数据可视化图表。我全部拒绝。因为每一次CSS重绘、每一次Canvas渲染,都在消耗你宝贵的CPU周期,而这本该用来跑你的AI模型。一个0.5秒内打开、0.1秒内响应点击的侧边栏,远比一个带粒子特效但卡顿的面板更有生产力价值。

最后分享一个独门技巧:如何快速验证一条推送是否“真有价值”?我给自己定了一条铁律——如果这条推送里的技术描述,无法在10分钟内让我在本地复现一个最小可行Demo(哪怕只是pip install一个包、跑通三行代码),那它就不值得我花时间深读。这个标准帮我过滤掉了至少70%的“概念性资讯”。工具的价值,不在于它给你多少信息,而在于它帮你节省了多少无效信息的处理时间。

6. 材料准备与定制化扩展:让插件真正长在你的工作流里

这个插件不是开箱即用的成品,而是一个可生长的框架。它的真正威力,来自于你能根据自己的工作流,像搭积木一样,快速接入新的数据源、编写专属规则、甚至嵌入私有系统。下面我以三个典型场景为例,说明如何做定制化扩展。

场景一:接入公司内部AI知识库
某公司使用Confluence管理AI模型评测报告,所有报告都遵循统一模板:标题含“【模型名】评测”,正文有“硬件配置”“吞吐量”“准确率”等固定章节。要让插件抓取这些报告,只需两步:第一步,在background.js中新增一个confluenceParser函数,用fetch获取Confluence页面HTML,再用document.querySelectorAll('h1:contains("【")')定位标题,用nextElementSibling获取后续内容;第二步,在用户规则配置里,增加一个“内部源”开关,开启后自动轮询指定Confluence空间的RSS Feed(Confluence原生支持)。整个过程不超过50行代码,且所有数据仍存于本地IndexedDB,不触碰公司内网边界。

场景二:为特定会议定制追踪
比如你关注NeurIPS中文社区的投稿动态。GitHub上有个neurips-chinese-submissions仓库,每周更新一次Accepted Paper列表。这时,你可以在规则里写一条专用正则:/NeurIPS.*2024.*accepted/i,并设置source为github:neurips-chinese-submissions。更进一步,我写了一个小脚本,自动从该仓库的README中提取PDF链接,用pdfjs-dist库在后台解析第一页,提取作者单位(判断是否来自国内高校),再按单位分组推送。这已经超出了通用资讯范畴,变成了你的个人学术雷达。

场景三:与本地开发环境联动
这是最硬核的玩法。假设你正在调试一个RAG应用,想实时监控社区对llama-index库的最新Issue讨论。你可以在插件里配置一个“开发中”标签,当检测到llama-index相关的GitHub Issue被标记为bug或help wanted时,不仅推送到侧边栏,还通过chrome.tabs.sendMessage向你当前激活的VS Code Web版(或Code Server)标签页发送消息,在编辑器底部状态栏显示一行提示:“⚠️ llama-index#12345: 新增RAG chunking bug报告,影响v0.10.0”。这相当于把资讯流,直接缝进了你的编码现场。

所有这些扩展,都不需要重新打包插件。你只需要在background.js里新增一个parser函数,再在filterItem里加一行判断逻辑,保存后刷新Service Worker即可。这就是“本地优先”带来的最大自由——你不是在使用一个产品,而是在培育一个属于你自己的信息器官。它会随着你的技术栈演进、关注点迁移、工作流变化,持续生长、自我迭代。我把它比作一把瑞士军刀:出厂时只有主刀和开瓶器,但当你需要时,随时可以拧下螺丝,换上锯子、剪刀,甚至激光测距仪。而这一切,都发生在你自己的浏览器里,安静、可靠、完全由你掌控。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进