本地优先的AI情报雷达:用浏览器插件打造主动式资讯过滤系统

发布时间:2026/10/14 0:55:38

本地优先的AI情报雷达:用浏览器插件打造主动式资讯过滤系统 每天早上一睁眼聊天群里的未读数已经顶到 99一半是“重磅”“首发”另一半是“别错过”。我连续刷了半小时看了一堆标题合上手机反问自己今天到底有什么 AI 事件值得我记住答不上来。这恐怕是所有持续关注中文 AI 情报的人都经历过的一种疲劳——不是信息太少而是太多、太散、太吵。后来我试着用收藏夹、稍后读、每日汇总邮件来消化但它们都有一个共同问题依然需要我自己去“主动打开”某个地方。真正需要的是一个本地优先的浏览器插件让中文 AI 情报自己来找我。于是我用一个周末的空闲时间做了个非常小的浏览器插件定时抓取我指定的中文技术源在本地做关键词过滤然后把命中的条目以未读角标和系统通知的形式推到眼前。它不依赖云端推荐不上传我的阅读记录不跑什么神秘算法所有的规则和缓存都留在本地。这个项目适合两类人一类是和我一样被 AI 资讯淹没、想主动掌握情报节奏的人另一类是打算入门浏览器扩展开发想看看一个完整小工具该怎么落地的新手。下面的内容既讲设计思路也讲实现细节还有我踩过的坑。1. 为什么做这个插件被资讯流支配的日常先说说问题到底出在哪。中文 AI 领域的信息源不是没有而是太分裂。头部技术媒体、垂直社区、开源项目发布页、个人博客、视频平台的更新分布在很多不同的入口里。想只用一个地方看完基本不可能。于是大家普遍的做法是把相关的号、群、专栏全部关注一遍然后靠“刷”来获取。信息流会不断往下滚昨天看过的内容很快被新内容挤下去真正重要的反而被冲走。这中间还有大量重复。同一个模型发布上午是“抢先评测”下午是“深度解读”晚上变成“一图看懂”。标题写法都差不多点进去信息量却很低。刷的时候觉得“我知道了个大事”但几天后你再问自己那个模型的核心改进是什么跟上一版相比有哪些变化大概率说不清。因为你在被动接受没有做筛选和留存。还有个容易被忽略的问题中文 AI 情报的时效性非常强。某个技术方案刚出现时讨论最有价值等大家都转完了你看到的只是二手感慨。可常规的算法推荐往往把热度最高的内容推给你而不是把最早、最原始的信息推给你。这导致你花最多时间看的往往是别人反复咀嚼过的东西。我一开始的解决办法很原始建了一个收藏夹看到有用的就扔进去。结果它变成了垃圾堆三千多条链接真正回看的不到 3%。后来试过每日邮件摘要但大多数摘要服务都在云端它们会记录你订阅了什么、点了什么而且推广内容不少。当时我正好在看浏览器扩展的文档发现它的能力组合很有意思有本地存储、有后台定时任务、有系统通知接口还有浏览器级的数据持久化。这些能力拼起来不就是一个天然的“情报订阅器”吗于是我开始动了手。为什么强调“本地优先”因为我要的是一个只服务于我自己的工具。我的阅读习惯、我关心的关键词、我看过哪些内容这些都是私人数据。把它们交给某个云端服务等于我又给自己制造了一个数据黑洞。而且云服务的过滤规则是黑盒它判定“重要”的未必是你真正需要的。把抓取、解析、过滤全部放在本地我才能随时改规则、随时查数据也知道每条情报是怎么被选出来的。2. 整体设计思路先定框架再写代码动手写代码之前我先花了小半天想清楚方案。很多业余项目的问题是上来就写写到一半发现脚手架不对又推翻重来。我的原则是先把形态、原则、功能框定否则后面全是返工。2.1 为什么是浏览器插件而不是 App 或网页服务我认真想过做一款手机 App但很快排除了。App 的分发和更新太麻烦而且我需要在电脑前读技术文章绝大多数时间就在浏览器里。浏览器插件是天然的信息触达点你不用额外打开任何软件新标签页一开插件角标上的数字就告诉你“今天有值得看的东西”。另一个关键点是后台定时能力。浏览器扩展允许注册周期任务哪怕用户不在插件面板里它也能按计划执行抓取逻辑。这在普通网页应用里很难实现——你不可能让用户一直挂着一个页面在后台跑定时器。浏览器扩展的 Alarms API 就是为这种场景设计的。我不做网页服务还有一个很实际的原因我不想运维服务器。RSS 和网页源众多抓取逻辑容易出问题如果做成云服务每次源站改版、编码异常、反爬升级我都要爬起来维护。放在本地扩展里服务只对我一个人运行出错影响有限改起来也很快。省了服务器钱也省了隐私顾虑。2.2 三个设计原则本地优先、主动投递、可裁剪这个项目的核心原则可以概括成三句话。第一本地优先。所有数据都放在浏览器扩展的本地存储里包括抓取到的原始条目、过滤规则、已读状态。解析逻辑也全部在本地完成不经过任何中转服务器。这样做的好处是离线时历史记录依然可查规则修改即时生效别人不知道我在关注什么话题。第二主动投递。传统 RSS 阅读器需要我自己打开去刷插件则反过来后台按固定周期抓取新条目经过本地过滤后如果有命中就通过通知和角标数字主动提醒我。我不需要看着页面等消息消息会自己来找我。第三可裁剪。我不希望二次掉进“推荐算法”的坑里。所以插件的功能都是可控的数据源由我增减关键词由我维护抓取频率由我设定。哪怕是临时关注某个突发话题我也能直接改规则不用等任何平台更新策略。2.3 最后定下的功能范围经过一版裁剪我确定的最小可用功能是功能模块说明源管理维护一份可编辑的中文 AI 资讯源列表支持 RSS 和普通网页链接定时抓取按设定的时间间隔到各源抓取最新条目解析标题、链接、发布时间本地过滤用关键词对标题和摘要打分白名单提高权重黑名单直接过滤去重与留存以条目标识去重只保留最近若干天的数据避免无限膨胀主动提醒命中关键词后更新扩展图标角标并通过系统通知推送摘要阅读面板点击插件图标弹出未读列表点击条目打开原文并标记已读这套功能组合基本覆盖了“信息获取、筛选、提醒、阅读回访”的完整闭环。对一个周末的业余项目来说规模刚好合适。3. 核心模块解析从抓取到呈现一步步拆开定完功能范围接下来就是把每个模块落实到浏览器扩展的各个 API 里。我拆成五个部分来讲。3.1 扩展的入口与权限设计浏览器扩展的基础是一个清单文件。我用的现代扩展规范是 Manifest V3写成这样{ manifest_version: 3, name: 中文 AI 情报雷达, version: 0.1.0, description: 本地优先的 AI 资讯聚合与主动提醒插件, permissions: [ storage, alarms, notifications ], host_permissions: [ https://example-ai-news.com/*, https://rss.example.com/* ], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: 查看 AI 情报 } }权限方面需要注意两点。第一storage用来保存抓取条目和配置alarms用来做周期抓取notifications用来发送系统通知。这三个是核心权限缺一不可。第二host_permissions需要按实际数据源来填。很多新手以为有了扩展的权限就能随便抓任意网站其实不是。扩展在发起跨域请求时目标站点必须出现在host_permissions里否则请求会被浏览器拦截。如果源是公开 RSS 服务可以单独声明域名如果以后要动态增加源更省事的做法是使用all_urls但这会扩大权限范围装载时也会有安全提示我一开始只用几个固定的白名单域名等跑顺了再放宽。3.2 定时抓取后台脚本怎么“自己去找”后台脚本是插件的心脏它负责周期性地唤醒自己去各个源把新的情报取回来。关键调度代码很简短chrome.alarms.create(fetchAI, { periodInMinutes: 30 }); chrome.alarms.onAlarm.addListener((alarm) { if (alarm.name fetchAI) { fetchAllSources(); } });这里有几个细节。periodInMinutes官方建议不要小于 1 分钟实际使用中 30 分钟是比较折中的方案。太短的话容易给源站造成压力也容易被反爬限制太长的话突发新闻不能及时看到。对于技术资讯30 分钟足够。fetchAllSources是核心抓取函数逻辑大致是async function fetchAllSources() { const config await getConfig(); for (const source of config.sources) { try { const items await fetchAndParse(source); await saveItems(items); } catch (err) { console.error(抓取失败: ${source.name}, err); } } await updateBadge(); }这里要注意一个重要问题MV3 的后台 Service Worker 不是常驻的它会休眠。chrome.alarms可以在休眠时唤醒它但网络请求不能无限挂起。我在每个请求上都设置了超时用AbortController控制避免某个慢源把整个循环拖死。3.3 解析与去重怎么把信息变成条目抓回的数据通常是 XML 或 HTML 文本第一步是解析成统一结构。我用的数据模型很简单{ id: hash, title: 标题, url: 原文链接, source: 来源名称, summary: 摘要或描述, publishedAt: 发布时间, read: false, score: 0 }对于 RSS 源核心是提取item和entry节点。如果不方便用 DOMParser可以先用字符串方式快速提取条目块再逐条抽取字段。为了演示我给一个最小解析逻辑function parseRSS(text) { const items []; const blocks text.split(item).slice(1); for (const block of blocks) { const title matchTag(block, title); const link matchTag(block, link); const pubDate matchTag(block, pubDate); if (title link) { items.push({ title, link: link.trim(), publishedAt: pubDate }); } } return items; }字符串解析虽然简单但对一些格式不规范的源会出错。所以我加了另一条路如果DOMParser可用就优先用正规 XML 解析。这是为了在“可靠”和“轻量”之间做个权衡。去重是另一个关键点。同一篇稿子可能被多个源转载我不能让它在列表里重复出现。我的做法是对标题和链接做一个归一化处理去掉 URL 里的追踪参数然后计算哈希作为条目的id。每次抓取完成后用这个id去查本地存储已存在的直接跳过。3.4 本地过滤如何让“中文 AI 情报”更精准抓回来的所有条目并不都应该提醒我否则就是从一个噪音源换到另一个噪音源。本地过滤规则要扮演“编辑”的角色。我维护了一个简单的关键词配置const ruleConfig { whiteList: [ 大模型, 智能体, 多模态, RAG, 模型微调, 推理优化, 评测, 开源模型, AI Infra ], blackList: [ 抽奖, 免费领取, 限时优惠, 标题党关键词 ], scorePerTitle: 3, scorePerSummary: 1, blackScore: -5 };过滤打分逻辑如function scoreItem(item) { let score 0; const title item.title.toLowerCase(); const summary (item.summary || ).toLowerCase(); for (const w of ruleConfig.whiteList) { if (title.includes(w.toLowerCase())) score ruleConfig.scorePerTitle; else if (summary.includes(w.toLowerCase())) score ruleConfig.scorePerSummary; } for (const b of ruleConfig.blackList) { if (title.includes(b.toLowerCase())) score ruleConfig.blackScore; } item.score score; return score 0; }这个阈值规则看起来简单实际已经能过滤掉大多数无关内容。我遇到过一种情况某个标题里没有关键词但正文里有大量相关语义简单打分就漏掉了。后来我在摘要里做了更细的匹配只要摘要里命中两个以上关键词也认为相关。对于个人工具足够用了。如果以后想更聪明可以接本地语义模型但这个项目优先保持轻量和透明。3.5 弹窗与通知真正“自己来找你”“自己来找你”的最后一步是触达。我做了两层。第一层是图标角标。每次抓取完成后统计未读条数写到扩展图标的badge上。这样用户哪怕不看弹窗瞄一眼浏览器工具栏就知道有没有新东西。async function updateBadge() { const items await getItems(); const unread items.filter(item !item.read item.score 0).length; chrome.action.setBadgeText({ text: String(unread) }); chrome.action.setBadgeBackgroundColor({ color: #b91c1c }); }第二层是系统通知。只有score足够高的条目才会触发通知比如标题里连续命中两个核心关键词。通知内容用“标题 来源 一句话摘要”点击后直接打开原文。我建议不要每条都通知。刚开始测试时我把阈值设得很低结果半小时弹了二十多条通知比广告还烦。后来我改成“每天只在整点通知一次”把最近一小时最值得看的三条合并成一条体验立刻好很多。通知是提醒不是轰炸。4. 实操过程从空目录到可用插件讲完模块说一下实际搭建的过程。如果你也想做一个类似的浏览器插件照着这套流程走大概率能顺利跑起来。4.1 先把架子搭起来我建议在本地建一个干净的目录结构如下ai-news-radar/ ├── manifest.json ├── background.js ├── popup.html ├── popup.js ├── style.css └── config.js然后打开浏览器的扩展管理页面开启“开发者模式”点击“加载已解压的扩展”选择这个目录插件就装上了。第一次装完一般不会立刻看到效果因为后台还在等第一个定时任务。我在代码里加了一个首次安装的立即抓取事件chrome.runtime.onInstalled.addListener(() { chrome.alarms.create(fetchAI, { periodInMinutes: 30 }); fetchAllSources(); });这样装完不到半分钟就能看到第一批数据不用干等半小时。4.2 后台抓取与解析的实现我把后台脚本分成三块定时调度、抓取解析、存储过滤。调度在前面已经写过重点看抓取和存储的衔接。抓取源的数据结构我定义在config.jsconst DEFAULT_CONFIG { sources: [ { name: AI媒体A, type: rss, url: https://rss.example-ai.com/feed }, { name: 技术博客B, type: html, url: https://blog.example-ai.com/ } ], fetchInterval: 30, keepDays: 7 };抓取函数里我用统一的fetchText方法读取内容后判断类型async function fetchAndParse(source) { const response await fetch(source.url, { signal: AbortSignal.timeout(15000) }); const text await response.text(); if (source.type rss) { return parseRSS(text).map(item ({ ...item, source: source.name })); } // 普通 HTML 页面简单提取标题和正文第一段 const titleMatch text.match(/title(.*?)\/title/i); const descMatch text.match(/meta[^]namedescription[^]content(.*?)/i); ... }存储我使用chrome.storage.local保存两个键items是条目数组config是配置。为了避免频繁写入我在每次抓取后批量写入一次而不是每条单独写。一个容易踩的坑是chrome.storage.local的 API 是异步回调式如果循环里并发写很容易出现数据覆盖。我封装成 Promise 形式并且在所有抓取任务完成后再统一set基本能避免。4.3 弹窗界面的实现弹窗 HTML 不需要复杂一个列表就够了!DOCTYPE html html head meta charsetutf-8 link relstylesheet hrefstyle.css /head body div idheader spanAI 情报雷达/span button idrefresh刷新/button /div div idlist/div script srcpopup.js/script /body /htmlpopup.js的核心逻辑是读取items过滤出未读或今天的内容渲染到列表里。我用了非常简单的 DOM 操作没有引入框架因为一个弹窗不需要。点击条目时用chrome.tabs.create打开原文同时把对应id的read字段置为true。这里有个体验细节弹窗每次打开都重新读取存储如果条目很多会有约一两百毫秒的白屏。我做了两个优化第一只渲染最近 50 条第二先展示上次缓存的列表再异步刷新。这个小改动让弹窗看起来快很多。4.4 踩坑记录最重要的三个问题第一个大坑是后台 Service Worker 休眠。MV3 为了省资源会自动回收后台脚本。我一开始设了每 30 分钟抓一次但因为 Service Worker 被回收onAlarm偶尔不触发。排查了半天发现需要在相关事件里注册监听而不是在全局顶层异步代码里等待。解决方案是把监听器写在顶层并且避免在服务worker里长时间存储大对象。第二个坑是跨域抓取。很多 RSS 源允许普通浏览器直接访问但扩展的fetch请求依然受 CORS 限制。表面上看起来像“源站拒绝了请求”实际上问题在权限配置。我在host_permissions里补齐所有源域名后请求就通了。第三个坑是中文乱码。一些老站点的内容是用 GBK/GB2312 编码直接用response.text()会解出乱码。解决方法是读取ArrayBuffer再用TextDecoder按源站编码解码const buffer await response.arrayBuffer(); const decoder new TextDecoder(source.encoding || utf-8); const text decoder.decode(buffer);这个技巧在抓取中文技术资讯时特别实用至少拯救了我一半以上的源。5. 常见问题与排查技巧速查用了几个星期之后我把朋友测试时反馈的问题和自己踩过的坑整理成了一张表方便你对照排查。问题可能原因解决办法插件没有自动抓取后台 Service Worker 休眠或 alarms 未注册检查 alarm 是否创建在 onStartup 里补一次抓取抓取时报跨域错误host_permissions 缺少源域名把源站的完整域名添加到 host_permissions标题和正文乱码源站不是 UTF-8 编码用 arrayBuffer 结合 TextDecoder 按源站编码解码同一篇文章重复出现链接带追踪参数哈希不同标准化 URL去掉 utm_* 等跟踪参数后再计算 id通知太多/收不到通知阈值过低或系统权限关闭提高 score 阈值检查系统通知设置和 notifications API 权限弹窗打开白屏异步数据未返回或 JS 报错打开弹窗控制台看报错给渲染函数加 loading 处理加载扩展报清单错误Manifest 字段格式不对确认 manifest_version 是 3字段名和 JSON 格式完全正确某些动态网站抓不到正文内容由 JS 渲染HTML 里没有优先使用该站提供的 RSS或换用静态页面接口后台服务不少内存占用高存储条目过多只保留最近 7 天控制单次抓取条数手动点击展开却看不到角标未调用更新角标函数每次抓取完成后主动调用 updateBadge调试时有个很实用的技巧右键点击插件图标选择“检查弹出内容”可以看弹窗的 Console在扩展管理页面点击“Service Worker”链接可以看后台脚本的日志。绝大多数抓取失败都能在里面找到具体报错不用瞎猜。6. 后续可以怎么玩我的扩展路线图做第一个可用版本之后我明显感觉到信息节奏变了。但插件还有很多可以改进的地方我列一下准备做的功能供你参考。首先是本地摘要。现在只展示抓取的摘要信息量有限。如果能接一个本地运行的小模型把长文压缩成 200 字以内的情报摘要整个工具的价值会高很多。关键是继续保持本地推理不上传原文。其次是自动标签和聚类。同一个热点事件在不同源里会有不同表述我希望插件能把它们聚类成“事件组”而不是简单按时间排列。在本地做轻量聚类并不难用关键词重叠或标题相似度就能实现。然后是每日早报。固定每天早上九点生成一份“昨日 AI 必读”清单通过通知或一个内置页面展示形成固定仪式感。我试用了一个粗暴版本发现比刷一小时信息流高效得多。最后是规则配置的可视化。现在改关键词要用代码不够友好。后面可以做成弹窗里的编辑表单甚至支持导入导出规则集方便在不同设备间同步同步过程中仍然只经过浏览器自己的同步通道不上传到自定义服务器。这个项目给我的最大启示是要解决信息过载不一定要建一个巨大的平台也不一定要依赖更智能的推荐。有时候只要一个本地优先的小插件把“被动刷”变成“主动收”就足以夺回注意力的控制权。从第一次看到角标数字出现、通知弹出、点开那篇真正值得读的文章时我知道这个方向走对了。如果你也在信息流里挣扎不妨用自己的方式造一个“情报雷达”。
延伸阅读

更多相关文章

2026/10/14 0:55:38

3ds Max 2009 导出 cgr 文件:Quest3D 4.0 插件安装与避坑指南

简介:这份资源是面向3D建模与实时三维开发者的Quest3D Extensions插件包,专为3ds Max 2009打造,用于将场景、模型、材质与动画导出为Quest3D 4.0所需的cgr专有格式,解决两个工具之间数据互通与版本兼容问题。压缩包共10个文件&…

2026/10/14 0:55:38

ueditor模板实战:从目录结构到图片上传的完整集成方案

简介:ueditor模版是一套基于百度UEditor的富文本编辑器快速集成资源,面向需要为网站、博客或后台系统添加在线编辑功能的Web开发者。压缩包共278个文件,约3.21MB,以js、css、png为主,同时包含html、gif、swf等类型&…

2026/10/14 0:50:38

Redis Linux源码编译安装与生产环境配置全攻略

1. 动手之前,先弄清楚Redis到底解决了什么问题我在日常工作中见过太多人一上来就敲yum install redis,装完跑起来就算完事,结果过两星期线上出问题,连日志都不知道去哪看。安装Redis本身不复杂,但如果你不清楚自己为什…

2026/10/14 5:30:52

Flutter HTML模板库鸿蒙适配实战与性能优化

1. 项目背景与核心价值作为一名长期深耕跨平台开发领域的技术老兵,我见证过无数次技术栈迁移带来的阵痛。当团队决定将Flutter应用迁移到鸿蒙平台时,html_template这个原本在Flutter生态中默默无闻的三方库突然成了关键路径上的绊脚石。这个专门处理HTML…

2026/10/14 5:30:52

游戏活动模板系统设计:从配置化到复用,告别每次写新代码

一次活动紧急上线,深夜两点,运营在群里喊:“明天上午十点要开一个节日活动,跟三个月前那个差不多,但奖励换一批,关卡规则改一下。”如果你所在的项目组还在用“活动开发一批新代码”的旧思路,那…

2026/10/14 5:30:52

C++编译期类型生成:从TypeList到std::variant的实战指南

编译期生成类型这种玩法,在C圈子里算是一道分水岭。读代码时看到别人用模板拼出一个完整的类型系统,总觉得很玄;真到自己上手写,又觉得无从下手。我最早接触这个概念是在做消息分发框架时遇到的一个需求:有一组结构体&…

2026/10/14 5:30:52

Python构建高性能笔记应用后端实践

1. 项目概述"笔记web-后端Python"这个项目名称已经清晰地表明了核心内容:一个基于Python技术栈的Web版笔记应用后端实现。作为现代生产力工具的重要组成部分,笔记类应用需要处理的核心问题包括数据持久化、多端同步、富文本编辑和权限管理等。…

2026/10/14 5:30:52

单级式储能单元模型:提升电网稳定性的关键技术

1. 项目背景与核心价值在新能源发电与智能电网快速发展的今天,储能系统作为能量缓冲的关键环节,其动态性能直接影响整个电力系统的稳定性。这个单级式储能单元模型项目,正是针对储能系统在充放电切换、电网故障等极端工况下的控制难题提出的解…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑