我平时有囤点黄金的习惯京东金融上的浙商银行积存金产品一直有在关注但是那个价格页不会自己刷新行情一波动就得手动切过去看赶上工作忙或者盯盘盯久了特别容易错过自己想入手的点位。后来我干脆花了一个周末写了一个“京东金融浙商金价实时监控 Chrome 扩展插件”把抓价格、判断涨跌、弹通知这些事全自动跑起来实测用下来比手动刷新省心太多。这篇文章就把这个项目的完整思路、技术选型、核心代码和踩坑记录整理出来适合想自己动手做数据监控工具的开发者也适合懂一点前端但对 Chrome 扩展不太熟的朋友参考。1. 项目背景与需求拆解1.1 盯盘痛点与项目目标黄金积存这类产品有个特点它不像股票那样每秒钟都在跳动但也并不是一成不变。通常一天之内会有几次调整尤其是国际金价出现异动的时候国内银行的积存金价格也会跟着变。问题是京东金融的页面不会主动提示你“价格更新了”如果你不刷新页面看到的永远是打开那一刻的旧数据。我一开始的做法是开一个定时器每隔几分钟切到浏览器标签页按一下刷新但时间一长就发现这事特别反人性一忙起来就会忘忘了之后又拍大腿。所以这个项目的核心目标很简单让浏览器替我去盯盘。只要检测到价格达到我设置的目标价或者价格出现了明显波动就自动弹通知提醒我而我不需要一直停留在页面上。为了实现这个目标我需要一个小工具能定时访问京东金融的浙商银行金价页面从页面上提取当前价格再做一次本地存储和判断最后调用浏览器通知接口把结果推送到桌面。这个工具还应该有一个简单的配置界面让我能修改监控间隔、目标价和通知开关而不是每次改需求都要重新写代码。1.2 功能需求清单在动工之前我把需求拆成了下面几条避免写着写着就跑偏。第一价格采集能力。扩展需要能够定时读取京东金融浙商银行黄金产品页面上展示的当前金价格式一般是类似“508.20元/克”这样的数字需要从页面 DOM 里准确提取出来。第二价格变化检测。连续两次采集到的价格要能做差值计算默认价格变化超过 0.1 元/克就算一次有效波动这个阈值用户可以在弹窗里自己调。第三浏览器通知提醒。当检测到达到目标价或者波动幅度超过阈值时通过 Chrome 的通知接口弹出提醒并且要避免同一轮监控重复弹出。第四扩展图标实时显示价格。不想打开弹窗也能看到最新价格最好把价格直接显示在工具栏图标上。第五完整的设置功能。监控的开关状态、轮询间隔、目标价上下限、通知开关全部做成可视化的配置。2. 技术选型与整体架构2.1 为什么选择 Chrome 扩展而不是独立脚本有人会问监控一个网页价格用 Python 写个脚本跑 cron 不也一样吗确实能实现但体验差很多。Python 脚本要依赖本机环境还得考虑 ChromeDriver 或者 requests 模拟登录的问题而且监控结果展示和通知非常麻烦要么发邮件要么接入第三方推送。Chrome 扩展就不一样了它天然跑在浏览器里面用户只要点了图标就能看到状态权限申请通过之后通知、图标角标、本地存储全部都是现成的 API不需要额外搭服务器也不需要在系统层面搞定时任务。另外Chrome 扩展对前端开发者几乎没有学习成本核心就是 JavaScript、HTML、CSS 这三件套。就算你不是专门做前端的只要会一点 JS也能很快把整个项目跑起来。我最后选择的方案是 Manifest V3这是目前 Chrome 的主流扩展规范相比旧的 V2 更安全Service Worker 代替了原来的后台页面官方也推荐新项目直接使用 V3。2.2 Manifest V3 与扩展组件划分一个标准的 MV3 扩展通常由几个部分组成manifest.json 是清单文件声明扩展的元信息、权限和入口background service worker 是后台服务负责定时任务、消息中转和通知content script 是内容脚本可以读取当前页面的 DOM 数据popup 页面是点击工具栏图标后弹出的设置界面。这四个部分的分工必须清晰否则很容易把代码搞成一团乱麻。我习惯把“数据从哪来”和“数据怎么处理”分开。content script 负责跟页面打交道专门干抓取 DOM 的活background 负责调度、存储、通知和更新图标popup 只负责读写配置。这样做的好处是以后如果京东金融改版了我大概率只需要更新 content script 里的提取逻辑不用动后台也不用动界面。2.3 数据来源方案页面解析还是接口请求开发前还有一个关键决策就是怎么拿到浙商金价。我调研了两种方案。第一种是通过 content script 直接解析京东金融页面上的 DOM 节点找到包含价格的元素把文字提取出来转成数字。优点是实现简单不需要考虑跨域逻辑完全透明页面显示什么我拿到的就是什么缺点是页面结构一变选择器可能就要跟着改。第二种是让后台直接请求价格接口扩展通过 host_permissions 绕过跨域限制。优点是即便没有打开监控页面扩展也能在后台静默请求数据缺点是接口地址可能会变参数也可能加密维护成本更高。我最终选择了“页面解析为主接口请求兜底”的组合方案。平时用户打开京东金融的浙商银行金价页面时扩展通过 content script 在页面里提取价格如果后台定时器运行时发现没有找到对应标签页就尝试请求一次兜底接口实在拿不到数据就跳过这一轮等下一轮。这个方案既保证了稳定性又不会把复杂度拉得太高。3. 从零实现核心代码与配置3.1 项目结构与 manifest 配置我先规划了一个简单的项目目录所有代码都放在一个文件夹下方便 Chrome 直接以“加载已解压的扩展程序”方式导入。项目结构大致如下jd-gold-monitor/ ├── manifest.json ├── background.js ├── content.js ├── popup.html ├── popup.js └── icons/ ├── icon16.png ├── icon48.png └── icon128.pngmanifest.json 是整个扩展的地基权限如果声明少了后面调用 API 就会报错所以我把权限一次性列清楚。下面这是我用的清单文件{ manifest_version: 3, name: 京东金融浙商金价实时监控, version: 1.0.0, description: 监控京东金融浙商银行金价达到目标价或价格波动时提醒, permissions: [storage, notifications, alarms], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: 浙商金价监控 }, content_scripts: [ { matches: [https://m.jd.com/*, https://finance.jd.com/*], js: [content.js], run_at: document_idle } ], icons: { 16: icons/icon16.png, 48: icons/icon48.png, 128: icons/icon128.png } }这里要注意content_scripts 的 matches 匹配规则要尽量贴合京东金融实际使用的域名如果你是在移动端页面监控还需要把移动端域名加进去。run_at 我选择 document_idle表示等 DOM 基本加载完再执行脚本避免页面还没渲染好就拿不到价格节点。权限里的 storage、notifications、alarms 三个是全程都要用到的缺一个都不行。3.2 价格采集逻辑content script 怎么拿到金价页面解析最麻烦的地方在于你无法提前确定价格到底挂在哪个元素上。京东金融的页面结构经过多轮改版有时候价格在 span 里有时候在 div 里但有一个规律是不变的只要页面上出现“金价”或“积存金”这类关键词附近的数字文本基本就是我们要找的价格。我写了一个比较“暴力”但实用的提取函数遍历页面里常见的文本节点同时用正则匹配三位小数的价格格式再结合关键词过滤把最像金价的那个数字找出来。代码如下function extractGoldPrice() { const candidates document.querySelectorAll(span, div, p, em, b); for (const el of candidates) { const text el.textContent.trim(); if (!/金价|买入|积存金|浙商/.test(text)) { continue; } const match text.match(/(\d{2,3}\.\d{2,3})/); if (match) { return parseFloat(match[1]); } } return null; } chrome.runtime.onMessage.addListener((msg, sender, sendResponse) { if (msg.type GET_PRICE) { sendResponse({ price: extractGoldPrice() }); } return true; });这个函数没有写死某个 CSS class而是采用“关键词加正则”的宽松匹配。这样做的好处是页面微调样式时不易失效坏处是如果页面同时出现多个相近数字可能会取到第一个匹配项。所以我后来加了一条优化如果页面里存在多个候选值就优先选择包含“浙商”或“积存金”关键词的元素里的数字进一步降低错抓的概率。3.3 定时调度与 Service Worker 的配合后台脚本最大的坑就是 Service Worker 可能会被浏览器回收。如果你在代码里直接用 setInterval一旦 Service Worker 进入休眠定时器就停了。正确做法是使用 chrome.alarms API由浏览器内核来负责定时触发即使当前 Service Worker 没有活动到点也会被唤醒来执行回调。我用的是每分钟触发一次轮询具体间隔可以在弹窗配置里调整。核心代码是这样的chrome.alarms.create(monitor, { periodInMinutes: 1 }); chrome.alarms.onAlarm.addListener((alarm) { if (alarm.name monitor) { checkPrice(); } });设置 periodInMinutes 的时候要注意虽然 Chrome 允许设置为小于 1 的小数但最低限制和系统策略有关不建议设置低于 0.5。对金价监控这种场景1 分钟一次已经完全够用。你用这个 API 之后就不需要担心 Service Worker 休眠导致定时器丢失的问题了这是 MV3 项目里非常关键的一步。3.4 数据请求与缓存逻辑定时任务触发后后台需要找到正在运行的京东金融页面然后给 content script 发消息要价格。如果找到了页面就发消息没找到就尝试请求备用接口。我这里把主流程写成了一个异步函数async function checkPrice() { const tabs await chrome.tabs.query({ url: [*://finance.jd.com/*, *://m.jd.com/*] }); let price null; if (tabs.length 0) { const responses await Promise.all( tabs.map((tab) chrome.tabs.sendMessage(tab.id, { type: GET_PRICE }).catch(() null) ) ); price responses.find((r) r r.price)?.price || null; } if (price null) { price await fetchPriceFromApi(); } if (price ! null) { await processPrice(price); } }这段代码里我用了 chrome.tabs.query 的 url 匹配参数可以精确过滤出符合条件的标签页避免在几十个标签里瞎找。sendMessage 可能因为 content script 还没注入而报错所以统一没做 catch 的话就会让整个流程崩掉。实测下来这这个 catch 可以让扩展在页面未就绪时直接跳到备用接口稳定性提高不少。备用接口我封装在另一个函数里这里不写死具体地址只是告诉你思路用 fetch 请求页面渲染时使用的数据接口请求头里带上必要的 referer 和 user-agent。扩展的 host_permissions 需要提前声明接口域名但实际测试中会发现很多接口仍然有额外的校验所以我的兜底策略是“能拿到就更新拿不到就跳过”绝不让一次失败影响整体监控。3.5 价格变化检测与通知触发拿到价格以后不能每次都弹通知否则几分钟弹一次谁也受不了。我做了两重判断第一重是价格变化阈值默认超过 0.1 元/克才认为有波动第二重是目标价判断如果当前价格低于我设置的心理买入价或者高于卖出价也触发提醒。价格变化检测需要依赖历史价格我用 chrome.storage.local 做持久化存储。每次拿到新价格就和上一次的价格对比如果差值超过阈值或者价格突破了目标价区间就发送通知。具体代码如下const THRESHOLD 0.1; async function processPrice(price) { const data await chrome.storage.local.get([lastPrice, noticeEnabled]); const lastPrice data.lastPrice; const shouldNotify data.noticeEnabled ! false (lastPrice undefined || Math.abs(price - lastPrice) THRESHOLD || (data.targetLow price data.targetLow) || (data.targetHigh price data.targetHigh)); await chrome.storage.local.set({ lastPrice: price }); if (shouldNotify) { chrome.notifications.create({ type: basic, iconUrl: icons/icon128.png, title: 浙商金价提醒, message: 当前价格 ${price.toFixed(2)} 元/克较上次变化 ${(price - lastPrice).toFixed(2)} 元/克 }); updateBadge(price); } }这里要注意 lastPrice 为 undefined 的情况也就是第一次运行不能直接拿 undefined 去减否则算出来是 NaN。所以通知条件里用了 lastPrice undefined 作为首轮静默条件首轮只记录价格、不弹通知从第二轮开始才做比较。这样处理很符合实际使用场景不然装上扩展第一次运行就弹个通知很容易让人觉得是 bug。3.6 扩展图标 Badge 显示当前价格Chrome 工具栏上的扩展图标可以显示一小段文本这个文本就叫做 badge。它的长度有限一般只能显示四五个字符所以我直接取价格的小数点前三位加一位小数比如“508.2”超过这个长度就会被截断。我封装了一个 updateBadge 函数专门用来刷新图标角标function updateBadge(price) { const text price ? price.toFixed(1) : --; chrome.action.setBadgeText({ text }); chrome.action.setBadgeBackgroundColor({ color: #B71C1C }); }Badge 的好处是不用点开扩展也不用打开页面只要眼睛余光扫到工具栏就能知道当前价格大概在什么位置。我平时就是把京东金融页面放在后台标签页扩展每分钟自动刷新一次工具栏上的数字也跟着变化看盘效率高了很多。如果你不喜欢红色角标也可以改成绿色或者根据涨跌动态切换颜色这都属于很小的扩展点。3.7 设置页面与参数调整为了让工具能被家人朋友也用起来我加了一个 popup 设置页用 HTML 写几个输入框和开关再配上 popup.js 把配置读到扩展的 storage 里。popup 页面不需要太复杂逻辑就是页面加载时读配置点击保存时写配置。关键代码如下const config await chrome.storage.local.get([interval, targetLow, targetHigh, noticeEnabled]); document.getElementById(interval).value config.interval ?? 1; document.getElementById(targetLow).value config.targetLow ?? ; document.getElementById(targetHigh).value config.targetHigh ?? ; document.getElementById(noticeEnabled).checked config.noticeEnabled ! false; saveBtn.addEventListener(click, async () { await chrome.storage.local.set({ interval: parseInt(document.getElementById(interval).value, 10), targetLow: parseFloat(document.getElementById(targetLow).value) || null, targetHigh: parseFloat(document.getElementById(targetHigh).value) || null, noticeEnabled: document.getElementById(noticeEnabled).checked }); chrome.alarms.clear(monitor); chrome.alarms.create(monitor, { periodInMinutes: interval }); });这里有一个细节修改间隔之后要先把旧 alarm 清掉再重新创建否则新配置不会生效。我一开始没注意这个问题改成 5 分钟后发现还是按原来的 1 分钟跑后来才排查到是 alarm 名称相同、无法覆盖导致的。这个坑很小但非常典型。4. 打包安装与本地测试4.1 加载未打包扩展开发阶段不需要把扩展打包成 crx 文件直接用 Chrome 的“加载已解压的扩展程序”功能就行。操作步骤很简单打开 Chrome 地址栏输入 chrome://extensions/把右上角的“开发者模式”开关打开然后点击左上角的“加载已解压的扩展程序”选择我们之前创建的 jd-gold-monitor 文件夹。加载成功后工具栏上就会出现这个扩展的图标。这里要提醒一下Chrome 对 MV3 扩展的错误提示会直接显示在卡片上比如 manifest.json 里权限写错、图标尺寸不对都会在扩展列表里以红字标出。你调整代码后记得回到 chrome://extensions/ 页面点击刷新按钮重新加载有时候还需要把相关页面也刷新一遍让 content script 重新注入。4.2 模拟数据测试我在正式跑真实页面之前先做了模拟数据测试。方法是在任意一个页面打开开发者工具手动插入一段包含金价文本的 DOM然后调用 content script 的提取函数看能不能取到正确值。这个办法能快速验证正则和关键词过滤逻辑避免反复刷新真实页面造成不必要的压力。还有一种测试方式是在 background.js 里临时加一段 console.log把每个环节的中间变量打印出来。你可以在 chrome://extensions/ 页面点击“Service Worker”链接打开后台脚本的调试控制台查看日志和错误。我调试时发现很多问题其实都出在 sendMessage 返回的 Promise 没有处理异常上控制台里会提示“Could not establish connection. Receiving end does not exist.”解决办法就是像前面那样统一用 catch 兜住。4.3 监控频率与请求压力平衡扩展做出来之后最忌讳的就是把轮询间隔设得太短比如每秒钟请求一次。一方面对目标服务器压力很大有被封的风险另一方面 Chrome 的 alarm 最短触发间隔也有限制。金价本来就不是高频交易我最终把默认间隔定在 1 分钟如果你觉得频繁可以改成 5 分钟甚至 10 分钟。我还在实现里加了一个小技巧每次请求前随机延迟 2 到 5 秒再真正去查询价格。这样做既不影响用户体验又能避免多台设备同时整点发起请求。对于这种个人自用的监控器“克制”比“激进”重要得多监控的本质是减少打扰而不是制造焦虑。5. 常见问题与排查实录5.1 页面选择器失效怎么办这是所有页面爬取类工具都绕不过的坎。京东金融的页面偶尔会调整类名和 DOM 结构如果用固定的 CSS 选择器去定位价格很容易在某次改版后突然失效。我的应对策略是不要只依赖结构更多依赖关键词和正则在文本层面做匹配这样即使包裹节点换了只要页面上还显示“金价”字样大概率还是能抓到数字。如果页面改版导致关键词匹配失败我会先手动打开页面看一下当前价格文本的实际内容和所在标签再调整 content.js 里的正则范围和关键词列表。建议把这个提取逻辑单独开放成一个函数方便替换。5.2 跨域与接口请求限制后台脚本直接 fetch 第三方接口时经常遇到 CORS 或 referer 校验问题。MV3 扩展可以通过 host_permissions 声明接口域名来规避部分跨域限制但并不是万能的。我在实际测试中遇到的典型错误是“blocked by CORS policy”这时候只能换思路优先依赖 content script 在页面内抓取数据因为页面自身发起的请求不受跨域限制。5.3 Service Worker 被回收导致状态丢失MV3 的 Service Worker 和普通浏览器后台页面不一样它会在一段时间没有活动后自动终止内存里的全局变量全部清空。所以如果你在全局变量里保存 lastPrice可能没跑多久就变成 undefined导致误报。解决办法是把状态全部放在 chrome.storage 里不要依赖内存变量。这个是我踩过最深的一个坑写代码时一定要养成习惯。5.4 通知不弹或延迟扩展通知不弹有几个常见原因。第一扩展没有获得系统的通知权限Chrome 在桌面端会询问用户是否允许通知如果之前点了拒绝需要到系统设置或 Chrome 站点设置里重新开启。第二通知 ID 重复chrome.notifications.create 如果使用相同 ID会覆盖上一条而不是新增一条需要让每条通知的 ID 唯一。第三Windows 开启了专注助手导致所有应用通知被静默这种时候需要调整系统设置。5.5 避免重复通知的实用技巧即使做了阈值判断依然可能出现连续几轮价格都在阈值附近来回波动导致反复弹通知。我后面加了一条去重逻辑如果当前价格距离上一次通知时间在 10 分钟内即使达到目标价也不重复弹只更新 Badge。这个逻辑就像游戏里的“内置冷却”非常实用。下面是我整理的问题速查表现象可能原因解决办法扩展图标不显示价格Badge 文本过长或未调用文本只保留一位小数不足时显示 --价格一直是旧值content script 未注入刷新页面确认 matches 域名覆盖正确修改间隔后没生效alarm 未清掉重建先 clear 再 create 同名 alarm通知不弹权限被拒或通知 ID 固定检查系统权限通知 ID 使用时间戳console 报后端接口错误CORS 或 referer 校验改用 content script 页面内抓取首轮运行直接弹通知lastPrice 未初始化判断 undefined 时只记录不提醒开发这个“京东金融浙商金价实时监控 Chrome 扩展插件”的过程中我最深的体会是工具不怕小只要能把一个痛点解决到位就有价值。最见鬼的不是代码复杂而是你永远猜不到页面什么时候改版、Service Worker 什么时候休眠、Chrome 通知权限什么时候被浏览器更新重置。这些坑你踩过一次之后就会明白“状态持久化”和“异常兜底”在扩展开发里有多重要。后面我打算把价格历史数据也存下来在 popup 里加一个小趋势图这样就能看到当天每个时间点的金价变化不需要再自行手动记录。如果你也想做类似的监控工具建议先像我一样跑通最小闭环再逐步丰富功能别一上来就想着做完美产品。毕竟工具是给自己用的实用、稳定、少打扰才是真正的核心。