用 MutationObserver 构建价格与库存监视器
你想要那件商品,但只在合适的价格 —— 或者你想在某个售罄商品重新上架的那一刻就知道。整天刷新标签页不是一种活法。本文用一条 JustZix JS 规则构建一个价格与库存监视器:一个 MutationObserver 监视相关元素,比较数值,并在你的条件满足时提醒你。
监视器如何工作
这个方案有四个部分,每一部分都对应下面的一个小节:
- 为价格或库存元素挑一个稳定的选择器。
- 挂上一个
MutationObserver,这样你无需轮询就能对变化做出反应。 - 把文本解析成一个可比较的数字或布尔值。
- 提醒自己 —— 一个页内横幅或一个系统通知 —— 并记住上一个值。
这一切都在标签页里客户端运行。让商品页面保持打开(一个固定的标签页最理想),规则负责监视。
挑一个稳定的选择器
监视器的可靠程度只取决于它的选择器。打开 DevTools 的 Elements 面板,找到承载价格的那个元素。按以下顺序优先选择:
- 一个语义锚点:
[itemprop="price"]、[data-price]、[data-testid="product-price"]。这些很少变。 - 一个稳定的 ID:
#priceblock_ourprice、#product-price。 - 一个有意义的类:
.price-now、.product__price。
避开哈希类(.css-1a2b3c)和长长的后代链 —— 它们在下次部署时就会失效。许多商店还在一个 <script type="application/ld+json"> 块里嵌入结构化数据;如果可见的 DOM 很乱,解析那段 JSON 往往是最稳定的路线。
设置 MutationObserver
每当被监视的子树发生变化时,MutationObserver 就触发一个回调 —— 无需轮询,不浪费 CPU。监视价格节点的文本和子节点变化:
const SELECTOR = '[itemprop="price"]'; // your stable selector
const priceEl = document.querySelector(SELECTOR);
if (!priceEl) {
console.warn('[watcher] price element not found - check the selector');
} else {
const observer = new MutationObserver(() => checkPrice(priceEl));
observer.observe(priceEl, {
childList: true,
subtree: true,
characterData: true
});
// Also check once on load - the price may already be a deal
checkPrice(priceEl);
}
单页应用商店有时在更新时替换整个价格节点。如果你的 observer 安静了下来,去监视一个稳定的父容器(带 subtree: true),并在回调里重新查询里面的价格。
把价格解析成数字
价格文本很乱:货币符号、千位分隔符、小数点逗号。在比较之前把它精简成一个干净的数字:
// Turn "$1,299.00" or "1 299,00 zl" into 1299
function parsePrice(text) {
if (!text) return NaN;
// Keep digits, dot and comma; drop everything else
let s = text.replace(/[^0-9.,]/g, '');
// If comma is the decimal separator, normalise it
if (/,\d{2}$/.test(s)) s = s.replace(/\./g, '').replace(',', '.');
else s = s.replace(/,/g, '');
return parseFloat(s);
}
与阈值比较
现在是决策逻辑。把解析出的价格与你的目标比较,只在它真正越过时才提醒 —— 而且每次下降只提醒一次,这样你不会在每一次微小的 DOM 变动时被刷屏:
const TARGET = 999; // alert when price drops to/below this
const STORAGE_KEY = 'jz-watch-' + location.pathname;
function checkPrice(el) {
const price = parsePrice(el.textContent);
if (Number.isNaN(price)) return;
const last = parseFloat(localStorage.getItem(STORAGE_KEY)) || Infinity;
localStorage.setItem(STORAGE_KEY, String(price));
console.log('[watcher] price now', price, '- last', last);
// Fire only when we newly cross the threshold
if (price <= TARGET && last > TARGET) {
notify('Price drop! Now ' + price + ' (target ' + TARGET + ')');
}
}
监视重新上架
同样的模式,不同的信号。你监视的不是一个数字,而是一个布尔值 —— 购买按钮是否启用了,「缺货」标签是否消失了:
function checkStock() {
const soldOut = document.querySelector('[class*="out-of-stock" i], .sold-out');
const buyBtn = document.querySelector('button[name="add-to-cart"], .add-to-cart');
const inStock = !soldOut && buyBtn && !buyBtn.disabled;
const wasInStock = localStorage.getItem('jz-stock') === 'yes';
localStorage.setItem('jz-stock', inStock ? 'yes' : 'no');
if (inStock && !wasInStock) {
notify('Back in stock! Grab it now.');
}
}
提醒自己
一个你看不到的提醒毫无用处。用两个渠道:一个不可错过的页内横幅,外加一个在标签页处于后台时用的系统通知。
function notify(message) {
// 1. On-page banner - always visible in the tab
let bar = document.getElementById('jz-watch-bar');
if (!bar) {
bar = document.createElement('div');
bar.id = 'jz-watch-bar';
bar.style.cssText =
'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
'background:#16a34a;color:#fff;font:600 15px/1.4 sans-serif;' +
'padding:12px 16px;text-align:center;';
document.body.appendChild(bar);
}
bar.textContent = 'JZ watcher: ' + message;
// 2. System notification - works when the tab is hidden
if (Notification.permission === 'granted') {
new Notification('JustZix watcher', { body: message });
} else if (Notification.permission !== 'denied') {
Notification.requestPermission();
}
}
2147483647 这个 z-index 是 32 位整数的最大值 —— 它保证横幅位于网站渲染的任何东西之上。系统通知需要权限,浏览器只会从一个用户手势或一个之前已批准的来源授予它,所以首次运行可能只是请求权限;同时横幅替你兜底。
持久化上次看到的值
你在上面看到了 localStorage 的使用,它做着实实在在的活。它能挺过刷新,所以监视器知道一个变化是不是真正的新变化。它还能给提醒去重 —— 你与存储的值比较,只在真正越过时才通知。按页面(location.pathname)来设键,这样多个商品监视器可以共存而不会互相覆盖。
在 JustZix 里把它接起来
- 创建一条规则,URL 模式正是那个商品,例如
https://shop.example.com/product/12345*。 - 把合并后的脚本粘进 JS 标签 —— 为那个商品设置
SELECTOR和TARGET。 - 固定标签页并让它保持打开;每当商店更新 DOM,observer 就做出反应。
- 想要即使没有 DOM 变化也定期重新检查?加一个
setInterval,每隔几分钟重新加载页面。
另见
- 禁用暗黑模式 —— 剥掉虚假紧迫感,让你看到真实价格。
- 模拟 API 响应 —— 用一个假价格源测试你的监视器。
- JustZix 应用场景 —— 更多自动化点子。
别再刷新那个标签页了。安装 JustZix,把监视器脚本放进一条按商品的规则,让浏览器在价格合适的那一刻告诉你。
为这篇文章评分
暂无评分 — 成为第一个。