Get Price Drop Alerts on Any Website

Set a price drop alert on any website: where trackers stop, a page-watch pipeline for the stores they skip, and the threshold you add yourself.

MMotherlode Lab10 min read
Magpie title card: Get Price Drop Alerts on Any Website

You found the thing and the price is wrong. Every tracker you try covers Amazon or a fixed list of chains, while the store you actually want is a two-person coffee roaster with no app. This guide sets up a price drop alert for any website that loads in a browser, with one catch stated up front: a raw page watch fires on any change, so the “only tell me below $75” logic is yours to add, and Step 3 builds that part in twenty lines of shell.

Where each kind of tracker quits

Three lanes, and most people are standing in the wrong one.

Amazon-only trackers. CamelCamelCamel is free and its alert form has a literal “notify me below $X” field. Keepa adds price-history charts, free with an account, with a paid tier for exports and API access. Both quit at Amazon’s border. If your product is on Amazon, stop reading and set the Camel alert now; for the scraper route on that same site, see the Amazon price tracker guide.

Big-chain apps and extensions. PayPal’s Honey has Droplist, a free extension feature that watches saved items on a select store list (Amazon, Macy’s, Target, Walmart per Honey’s own feature page at this writing). Google Shopping shows price history on indexed products, with no custom threshold, and the data comes from merchant feeds that can lag the shelf price by hours. The retailer decides what counts as a “drop”. Your indie roaster is not on any of these lists.

Generic page watchers. Visualping watches any URL; the free plan covers 5 pages and paid plans start at $10/month (annual billing, per their pricing page at this writing), and its AI alert criteria accept “below $X” phrasing. changedetection.io is the open-source, self-hosted take, with a proper UI. These work everywhere. The trade is genericness: without criteria, they alert on every change, and product pages change constantly.

That third lane is where Magpie lives. Magpie is a local CLI (AGPL-3.0-or-later or a commercial licence; free to build and run), and its watch command stores a snapshot of a page, refetches on your interval, and on change prints a word diff and POSTs a webhook. There is no price condition anywhere in that pipeline. No “below X”, no “back in stock” check, no keyword filter. Any text change fires. That is the honest limit of this article’s Magpie setup: you accept any-change alerts, or you build the threshold yourself. Step 3 builds it.

Before you start

  • A machine that stays on. An old laptop, a mini PC, or a small VPS. A watch that runs when you happen to be logged in is not a watch.
  • Magpie installed: clone the repo and go build -o magpie ./cmd/magpie (needs Go). Put the binary on your PATH.
  • The product URL, open in a tab.
  • About ten minutes.

Step 1: Find where the store exposes the price

Run a scrape against the product page and look at what comes back:

magpie scrape "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html" \
  --page-format markdown
{
  "url": "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "title": "A Light in the Attic | Books to Scrape - Sandbox",
  "markdown": "...\n\n£51.77\n\nIn stock (22 available)\n\n...",
  "structured_data": null
}

(abridged) The cleaned markdown keeps the price and the stock line and drops the nav, the footer, and the banner ads. structured_data: null means this store ships no schema.org markup, so text matching is your only route here.

Most real stores do ship markup, and markup is the better raw material. When JSON-LD exists, ask for the typed record instead of prose:

magpie scrape "https://www.deathwishcoffee.com/products/society-of-strong-coffee-initiation-kit" \
  --vertical ecommerce_product
{
  "url": "https://www.deathwishcoffee.com/products/society-of-strong-coffee-initiation-kit",
  "vertical": "ecommerce_product",
  "record": {
    "availability": "http://schema.org/InStock",
    "brand": "BAMKO",
    "currency": "USD",
    "name": "Society of Strong Coffee Initiation Kit",
    "price": 50
  }
}

(abridged) One number, one currency, no regex. The vertical route covers Shopify and WooCommerce stores too; the e-commerce product scraper guide has the full catalog-extraction setup if you need more than one product’s price.

Failure cases worth knowing before they cost you an evening:

  • Thin or JavaScript-walled pages come back as a typed error instead of silence. When I scraped a GitHub gist’s rendered page, Magpie’s cleaner extracted nothing and exited with quality blocked (empty) and code 8. Watch that exit code in any cron setup; a page that stopped cleaning looks different from a price that stopped moving.
  • Walled marketplaces refuse the fetch outright. eBay, Etsy, Best Buy, and AliExpress sit in that group as of this writing; no watch pipeline in this guide changes that.
  • Session-gated pricing. Amazon varies price by delivery ZIP, so what your server sees is not always what your door gets. Treat an Amazon alert as approximate even when it fires.

Step 2: Watch the page and act on the diff

The watch loop is one command:

magpie watch "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html" --every 15m

The first check stores the snapshot and prints nothing. Every later check diffs against it: silence means no change, a word diff means change. The minimum interval is 30 seconds, and I would not go below a few minutes on a stranger’s server anyway; Magpie respects robots.txt and throttles per host by itself, which keeps the whole thing polite (see the robots.txt guide for where the lines are).

To see what “any change” really means, here is a real diff from watching the Hacker News front page at a one-minute interval:

changed=true old=e87afd0c... new=5fa4b9ee...
- 59
+ 77
- [57 minutes
+ [1 hour
+ [Bark: The Toll-Free BBS for 90s Teens](https://blog.bityard.net/...)

(abridged) Point scores, relative timestamps, a new story. Nothing there is a price, and all of it fired. Product pages churn the same way: review counts tick up, “only 3 left” appears and vanishes, cross-sell carousels reshuffle. A raw page watch is a firehose, and Step 4 narrows it.

For actual notifications, add a webhook. On change, and only on change, Magpie POSTs one JSON body:

magpie watch "https://example.com/product" --every 15m \
  --webhook "http://127.0.0.1:8932/hook"
{"url": "https://news.ycombinator.com", "changed": true,
 "old_hash": "e87afd0c...", "new_hash": "5fa4b9ee...",
 "diff": "- 59 | + 77 | ..."}

Point the webhook at a Discord or Slack incoming hook, an n8n flow, or your own receiver. One detail I verified while testing: the fetch side refuses loopback and private addresses (that is the SSRF guard, and it refuses on purpose), but the webhook delivery side happily POSTs to 127.0.0.1, so your receiver can run on the same box as the watcher.

Step 3: Add the price drop alert threshold yourself

This is the piece the trackers have and the raw watch does not. A state file, a comparison, done:

#!/usr/bin/env bash
# price-watch.sh URL LIMIT — fires when the price crosses below LIMIT
set -u
URL="$1"; LIMIT="$2"
DIR="$HOME/.price-watch"; mkdir -p "$DIR"
STATE="$DIR/$(printf %s "$URL" | md5sum | cut -c1-12)"

PRICE=$(magpie scrape "$URL" --vertical ecommerce_product 2>/dev/null \
  | python3 -c 'import json,sys
try: print(json.load(sys.stdin)["record"]["price"])
except Exception: print("")')

if [ -z "$PRICE" ]; then
  PRICE=$(magpie scrape "$URL" --page-format markdown 2>/dev/null \
    | python3 -c 'import json,sys,re
md = (json.load(sys.stdin).get("markdown") or "")
m = re.search(r"[$£€]\s?([0-9][0-9,.]*)", md)
print(m.group(1).replace(",", "") if m else "")')
fi
[ -z "$PRICE" ] && { echo "no price found: $URL" >&2; exit 1; }

if [ -f "$STATE" ]; then
  LAST=$(cat "$STATE")
  awk -v p="$PRICE" -v l="$LIMIT" -v o="$LAST" 'BEGIN{exit !(p+0<l && o+0>=l)}' \
    && echo "PRICE DROP: $URL now $PRICE (below $LIMIT, was $LAST)"
fi
echo "$PRICE" > "$STATE"

It tries the JSON-LD record first and falls back to the first currency figure in the cleaned text. First run stores a baseline and says nothing. On books.toscrape with a 60 limit, once the baseline sat above the line, the next run printed:

PRICE DROP: http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html now 51.77 (below 60, was 89.00)

That run is real; I seeded the state file with 89.00 to stand in for last week’s price, because a static demo page never actually drops. The crossing logic is what you are copying, and it only alerts on the move from at-or-above to below, so it will not re-ping you every 30 minutes while the price stays low.

Put it on a cron and let it grind:

*/30 8-22 * * * $HOME/bin/price-watch.sh "https://www.deathwishcoffee.com/products/society-of-strong-coffee-initiation-kit" 45 >> $HOME/.price-watch/log 2>&1

Every 30 minutes during waking hours, one fetch per tick, zero LLM calls, zero marginal cost. (date -Is in the script is GNU syntax; on macOS swap it for plain date.)

One regex hazard, learned the hard way: on sale pages, the first $ number in the text is often the “was” price, not the “now” price. That is the strongest argument for insisting on the JSON-LD route whenever the store ships markup, because schema.org’s offers.price is the sell price by definition. Text matching is the fallback, not the plan.

Step 4: Cut the noise before it kills the habit

Most abandoned price watches die of spam, not bugs. Three cuts, in order of how well they work:

  1. Watch the number, not the page. The Step 3 script never diffs the page at all; it compares two integers. When markup exists, this is the whole game.
  2. Filter at the receiver. If you keep the raw watch for its webhook, make the consumer do the price check. The diff is right there in the payload: jq -r .diff | grep -E '[$£€]' fires your notification only when money changed.
  3. Slow the interval. Fifteen minutes catches any real drop. It does not reduce the causes of noise, only how often they page you, which is why it is third.

The stock-flip flavor of this problem, alert when “out of stock” becomes “in stock”, is a guide of its own, and it inherits the same limit: Magpie sees the words change, it does not know a restock from a typo.

When a page watch is the wrong tool

Some jobs only look like this one. Monitoring competitors’ catalogs and price lists at scale is extraction, not alerting, and it deserves the price scraper guide rather than a cron loop. Whole-of-site change monitoring across many pages is the competitor monitoring setup. Amazon buyers who want history charts and third-party seller tracking want the Keepa class of tool, not a diff. And if you would rather click a UI than run cron, the changedetection.io alternatives page covers the self-hosted option honestly.

FAQ

Do price drop alerts work on any website?

Trackers: no, they cover Amazon or a fixed retailer list. A page watch: yes, for any page it can load and clean. Walled marketplaces (eBay, Etsy, Best Buy at this writing) and hard JavaScript walls are the exceptions, and the Step 1 scrape tells you in one command which kind your store is.

How do I get a price drop alert below a set amount?

On Amazon, CamelCamelCamel’s “below $X” field does it for free. Anywhere else, no mainstream tool ships a universal threshold; you either use a watcher with alert criteria (Visualping supports “below $X” per its docs) or run the script from Step 3.

Why didn’t my alert fire when the price changed?

Three real causes: a CDN served your watcher a stale copy (GitHub’s raw cache served me a 15-minute-old page after an edit); session- or region-gated pricing, so watcher and buyer see different numbers; or the page stopped cleaning and errors on every check. Log stderr in cron so the third one is visible.

Reading a public product page the way a browser does is the polite end of scraping: respect robots.txt, throttle your interval, and do not hammer a small store’s server every 30 seconds. Terms of service vary by site, and login-walled or marketplace pages carry stricter rules than a public product page. The robots.txt guide covers the etiquette.

Your next move

Run the Step 1 scrape against your product URL right now. The output picks your pipeline: a JSON record means the Step 3 script as written, bare markdown means the same script on its regex fallback, and a typed error means find another seller. Set the cron, lower the limit to the number you actually want to pay, and let the machine do the waiting.

Stop renting credits.

One flat price, your machine, your keys.

Download free for Windows, macOS and Linux

Windows · macOS · Linux $49/mo flat, cancel anytime