← 全部文章

API 与辅助工具

用 MutationObserver 构建价格与库存监视器

你想要那件商品,但只在合适的价格 —— 或者你想在某个售罄商品重新上架的那一刻就知道。整天刷新标签页不是一种活法。本文用一条 JustZix JS 规则构建一个价格与库存监视器:一个 MutationObserver 监视相关元素,比较数值,并在你的条件满足时提醒你。

监视器如何工作

这个方案有四个部分,每一部分都对应下面的一个小节:

  1. 为价格或库存元素挑一个稳定的选择器。
  2. 挂上一个 MutationObserver,这样你无需轮询就能对变化做出反应。
  3. 把文本解析成一个可比较的数字或布尔值。
  4. 提醒自己 —— 一个页内横幅或一个系统通知 —— 并记住上一个值。

这一切都在标签页里客户端运行。让商品页面保持打开(一个固定的标签页最理想),规则负责监视。

挑一个稳定的选择器

监视器的可靠程度只取决于它的选择器。打开 DevTools 的 Elements 面板,找到承载价格的那个元素。按以下顺序优先选择:

避开哈希类(.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 里把它接起来

  1. 创建一条规则,URL 模式正是那个商品,例如 https://shop.example.com/product/12345*
  2. 把合并后的脚本粘进 JS 标签 —— 为那个商品设置 SELECTORTARGET
  3. 固定标签页并让它保持打开;每当商店更新 DOM,observer 就做出反应。
  4. 想要即使没有 DOM 变化也定期重新检查?加一个 setInterval,每隔几分钟重新加载页面。

另见

别再刷新那个标签页了。安装 JustZix,把监视器脚本放进一条按商品的规则,让浏览器在价格合适的那一刻告诉你。

为这篇文章评分

暂无评分 — 成为第一个。

自己动手试试

安装 JustZix,粘贴本文中的任意代码片段。两分钟,从零到一条在你所有设备上生效的规则。

获取 JustZix

功能 · 工作原理 · 示例 · 应用场景