L logiover
data · Jul 9, 2026 · 7 min read

How to Monitor Website Changes: Page Diffs & Alerts

Track any web page for content changes with line-by-line diffs and a SHA-256 baseline. Target sections with CSS selectors, ignore noise, schedule checks.

Watching a page for changes by hand is a losing game. Competitor pricing pages, regulatory notices that ship without an RSS feed, career pages with no job alerts, product pages that flip between “In Stock” and “Out of Stock” — all of it needs a person to remember to look, notice what moved, and act. Paid change-detection services solve this but return opaque screenshots or lock the diff behind their own dashboard. This guide covers the lightweight alternative: text-content monitoring that fetches each page, computes a SHA-256 baseline, and emits a machine-readable line-by-line diff you can wire straight into your own alerts.

What’s worth extracting

Each check produces one row per URL. The fields, grouped by what they tell you:

  • Change flaghasChanged (true if the content differs from the previous run), changePercent (an approximate percentage of lines that changed, 0–100).
  • Diff detailaddedLines (count of new lines), removedLines (count of lines no longer present), diffSummary (a human-readable summary of the first few added and removed lines).
  • Baseline trackingcurrentContentHash (the SHA-256 of the current content) and previousContentHash (the SHA-256 of the prior baseline).
  • Contexturl (the monitored page) and checkedAt (ISO 8601 timestamp).

The diffSummary is the field that makes this actionable. Instead of “something changed,” you get lines like + Pro plan now $49/mo and - Team plan $39/mo — the actual before-and-after, ready to drop into a Slack message.

How the source actually works

The mechanism is deliberately simple, which is why it’s cheap and easy to parse. For each URL the actor:

  1. Fetches the page over HTTP (using got-scraping, not a headless browser).
  2. Extracts the monitored content — the whole page by default, or only the sections you name with cssSelectors.
  3. Strips noise you flag via ignoreSelectors, and optionally numeric churn via stripNumbers.
  4. Hashes the resulting text with SHA-256 and compares it against the stored baseline.
  5. Diffs line-by-line when the hash differs, producing the added/removed lines and percent-change.

The first run on a URL just records the baseline — every URL returns “baseline saved.” Subsequent runs compare against it and flag changes. A realistic input for competitor pricing pages, monitoring only the price section:

{
  "urls": [
    "https://competitor-a.com/pricing",
    "https://competitor-b.com/pricing"
  ],
  "cssSelectors": ["h1", ".price-value", ".plan-name"],
  "ignoreSelectors": [".timestamp", "#dynamic-ad"]
}

Here cssSelectors narrows monitoring to the headline, price values and plan names — so a change to the footer or a rotating banner never triggers a false alert. ignoreSelectors strips a timestamp and an ad container before comparison. For a job board you might instead target #job-listings and enable stripNumbers to ignore an applicant counter:

{
  "urls": ["https://company.com/careers"],
  "cssSelectors": ["#job-listings"],
  "stripNumbers": true
}

Baselines and how state persists

The thing that makes a change monitor work is state — it has to remember what the page looked like last time. This actor stores baselines in Apify’s key-value store under the actor’s namespace. They persist across runs and, importantly, refresh automatically after each detected change, so the next run compares against the latest version rather than re-flagging the same edit forever. If you ever need to start clean, you delete the key-value store records in the Storage tab, or run once with different input fields to seed fresh baselines.

This is why the intended workflow is two-phase: run once to establish baselines, then set a recurring schedule (hourly, daily at 9 AM, weekly) so each subsequent run diffs against what came before.

Open the Website Change Monitor on Apify — one run diffs your monitored pages and returns added/removed lines plus a percent-change and human-readable summary per URL. No API key, no login. Export to JSON, CSV or Excel, or pipe to Slack/email.

The access reality

This is a lightweight HTTP tool, so the honest limitations are about what it can see, not anti-bot walls:

  • It reads server-rendered HTML. Because it uses an HTTP fetch (got-scraping) rather than a browser, it handles server-rendered content well. For JavaScript-heavy single-page apps where the content you care about is injected client-side, an HTTP fetch won’t see it — you’d need a Playwright-based monitor or a screenshot-capture actor for those.
  • Text, not pixels. This detects content changes, not visual ones. A CSS restyle that doesn’t touch the text won’t register. That’s a feature for pricing and availability monitoring — and the reason the README suggests pairing it with screenshot capture when you also care about visual regressions. Each text check is only about 1–5 KB per URL versus 1–5 MB for a screenshot, so it’s far cheaper to run at scale.
  • Noise is the real enemy, and you control it. Timestamps, session IDs, rotating ad slots and view counters will trip a naive whole-page diff constantly. cssSelectors, ignoreSelectors and stripNumbers exist specifically to cut that noise down to meaningful edits.

Example output

A diff run that caught a pricing change:

{
  "url": "https://competitor-a.com/pricing",
  "hasChanged": true,
  "changePercent": 8,
  "addedLines": 3,
  "removedLines": 1,
  "currentContentHash": "9f2b1c4e…",
  "previousContentHash": "3a7d0e51…",
  "diffSummary": "+ Pro plan now $49/mo\n+ New: unlimited seats\n- Team plan $39/mo",
  "checkedAt": "2026-07-06T09:00:00.000Z"
}

Typical use cases

  • Competitor price tracking — monitor /pricing pages and get alerted the moment tiers, features or plan names change, with the exact before-and-after in the diff.
  • Regulation and compliance — track government or regulatory pages that publish updates without an RSS feed and receive a diff of precisely what changed.
  • Job board monitoring — target the listings selector on a company careers page to catch new postings without a job-alert feature.
  • E-commerce stock and price alerts — detect “Out of Stock” to “In Stock” transitions and price moves on product pages.
  • News and blog monitoring — see when a competitor publishes a new article and what the headline is.
  • Content-integrity checks — catch unexpected edits or defacement on pages you own.

Cost and effort math

Building this yourself means writing the fetch layer, an HTML-to-text extractor with selector targeting, an ignore-list stripper, a numeric-churn filter, SHA-256 baselining with somewhere durable to store the hashes, a line-diff algorithm, and the scheduling and alerting wiring around it. The diffing and hashing are easy; the durable per-URL state and the noise-suppression tuning are where the time goes — and a self-hosted version still needs a place to persist baselines between runs.

The managed actor is pay-per-result — you pay only for the checks performed in each run, a fraction of a cent per URL check, with no monthly subscription and no minimum. Because each check is an HTTP fetch of a few kilobytes, a large monitoring list stays cheap and scales linearly, and there’s no rendering overhead or heavy proxy bandwidth to pay for. Compared to a paid change-detection SaaS with a per-monitor monthly fee, watching a big list of pages costs far less, and the diff comes back as JSON you own rather than locked in someone’s dashboard.

Common pitfalls

Specific to text-based change monitoring:

  • Whole-page monitoring is a false-alarm machine. Without cssSelectors, every timestamp, rotating banner and “you may also like” carousel counts as a change. Target the section you actually care about; it’s the single biggest lever on signal quality.
  • stripNumbers is blunt — use it deliberately. It ignores changes that only involve numbers, which is perfect for view counters and dates but will also hide a genuine price change if the surrounding text stays identical. Pair it with a selector on the price element rather than applying it page-wide when price is the thing you’re tracking.
  • The first run never flags a change. It only records the baseline. Don’t wire alerts expecting output on run one; the diffs start on the second scheduled run.
  • Baselines self-refresh after a change. After a detected change, the new content becomes the baseline. That’s intended (you won’t re-alert on the same edit), but it means the diff is always against the previous state, not some fixed original — reset the KV store if you want a fresh anchor.
  • SPA content may be invisible. If the page renders its meaningful content in the browser via JavaScript, the HTTP fetch won’t capture it. Verify the content is in the server HTML, or switch to a browser-based approach for that URL.
  • Cross-run diffs need the same input shape. Because baselines are keyed under the actor’s namespace, changing input fields can seed new baselines. Keep the monitoring config stable for a given URL so you’re comparing like with like.

Wrapping up

Change monitoring is one of those jobs that’s trivial to prototype and annoying to run reliably — the diffing is the easy 10%, and durable state plus noise suppression is the other 90%. If you need alerts on pricing, availability, regulations or job postings, the managed actor gives you selector-targeted text diffs with self-refreshing baselines and a machine-readable output you can route to Slack, email or a webhook.

Run the Website Change Monitor on Apify — one run returns a line-by-line diff (added/removed lines, percent change, summary) per monitored URL, on any schedule. No API key. Start on Apify’s free monthly credit.

Related guides