Website Change Monitor: Diffs, Prices & Alerts avatar

Website Change Monitor: Diffs, Prices & Alerts

Pricing

from $1.00 / 1,000 page checkeds

Go to Apify Store
Website Change Monitor: Diffs, Prices & Alerts

Website Change Monitor: Diffs, Prices & Alerts

Monitor up to 500 web pages and get a row only when one changes: added and removed lines, change size, the full diff, and a rule-based summary such as "price text changed from €49 to €59". Main-content detection or a CSS selector, ignore patterns, schedules and alerts.

Pricing

from $1.00 / 1,000 page checkeds

Rating

0.0

(0)

Developer

Offera Studio

Offera Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 hours ago

Last modified

Share

What does Website Change Monitor do?

Website Change Monitor watches a list of web pages and gives you a row only when a page changes, with a clean diff and a short plain-English summary of what changed. Add up to 500 URLs, schedule it daily or hourly, and connect alerts by email, Slack or webhook.

For every change you get:

  • 🔗 the URL, when it was checked and when it was checked before
  • 📏 the change size in characters
  • ➕➖ the added and removed lines (first 50 of each) and a link to the full diff
  • 📝 a rule-based summary (no AI, no guessing), for example "Price text changed from €49 to €59 under 'Pro'", "3 lines added under 'Pricing'", "Availability text changed from 'in stock' to 'sold out'"
  • 💶 price, number and date changes as structured data (priceChanges, numberChanges)

The first run saves a baseline of every page (one "baseline saved" row each). From then on, only changes produce rows.

What can you monitor?

  • Competitor pricing pages: get "Price text changed from $19 to $24" the morning it happens.
  • Terms of service and privacy policies: see which clauses were added or removed, under which heading.
  • Job and careers pages: new openings show up as "2 lines added under 'Open positions'".
  • Tender and procurement notices: new tenders, changed deadlines and amended documents.
  • Product availability: "in stock" → "sold out", "notify me" → "add to cart", price drops.
  • Also: regulatory pages, documentation, status pages, school and council notices, real estate listings.

How to use it

  1. Paste the pages into URLs to monitor (or upload a text file with one URL per line).
  2. Optional: a CSS selector to compare only part of each page, e.g. .pricing-table or #terms. Without one, the Actor finds the main content itself and leaves out navigation, header, footer, sidebars, cookie banners, scripts and hidden elements.
  3. Optional: ignore patterns for text that changes on every visit (see below), Ignore dates and times, and a minimum change size.
  4. Turn Include unchanged pages off (it is on in the example input only so a first try shows a row for every page).
  5. Click Start: the first run saves the baselines. Then add a schedule (Console → Schedules), e.g. every day at 7:00 or every hour.

Ignore patterns: dates, counters and other noise

Patterns are regular expressions, matched case-insensitively against each line. Matching text is ignored when comparing (it still appears in the diff).

To ignorePattern
View or visitor counters\d[\d,]* (views|visitors|comments)
"Last updated" lineslast updated:? .*
Session or tracking IDssession id: \w+
Prices you don't care about, in a list\$\d+(\.\d\d)? shipping

Ignore dates and times covers dates (2026-10-01, 10/01/2026, 1 October 2026), clock times and "5 minutes ago".

Minimum change size skips changes smaller than that many characters. Smaller changes are not lost: they are compared against the saved version until, together, they reach the minimum.

Alerts: email, Slack or webhook on every change

Save your input as a task, add a schedule, then pick one of these in the task's Integrations tab or in your automation tool:

  • Slack (built in): add the Slack integration for the event Run succeeded. Each run posts one message; the default text can be replaced with a template, for example {{resource.statusMessage}} https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}. The status message reads like "Checked 50 page(s), 2 changed", so you see at a glance whether anything moved.
  • Email or Slack only when something changed (Zapier or Make): trigger Finished Task Run (Zapier) or Watch Task Runs (Make), then Get Dataset Items for the run's dataset, then continue only if there are items with status = changed, and send url, summary and diffUrl by email or to Slack.
  • Webhook: add a webhook for Run succeeded. Your endpoint receives the run in resource, reads https://api.apify.com/v2/datasets/{defaultDatasetId}/items?view=overview and alerts when it has rows.

Because the dataset only gets rows when something changed, "the run's dataset is not empty" is the alert condition. Keep Include unchanged pages off, and filter on status = changed if you don't want the one-time baseline rows or the free error rows.

Input example

{
"startUrls": [
{ "url": "https://competitor.example/pricing" },
{ "url": "https://supplier.example/terms" }
],
"cssSelector": "",
"ignorePatterns": ["\\d[\\d,]* (views|visitors)"],
"ignoreDatesAndTimes": true,
"minChangeChars": 0,
"includeUnchanged": false,
"monitorName": "competitors"
}

Output example

A real change row from a local run against the Hacker News front page, checked twice 40 seconds apart (shortened; locally diffUrl is a file:// link, on Apify it points to the record in your key-value store). A busy page like this changes all the time; an ignore pattern such as \d+ (points|comments) or a CSS selector would keep only the changes you care about:

{
"url": "https://news.ycombinator.com/",
"status": "changed",
"summary": "Number changed from 35 to 38 in \"185 points by Snowly 2 hours ago | hide | 38 comments\"; Number changed from 10 to 12 in \"28 points by trickypr 1 hour ago | hide | 12 comments\"; Number changed from 36 to 37 in \"83 points by giuliomagnifico 3 hours ago | hide | 37 commen…\"; Number changed from 154 to 156 in \"156 points by sagacity 6 hours ago | hide | 88 comments\"; Numbers changed in \"78 points by speckx 51 minutes ago | hide | 30 comments\": 73 → 78, 49 → 51; and 12 more changes.",
"monitorName": "readme demo",
"checkedAt": "2026-10-01T13:47:48.417Z",
"previousCheckedAt": "2026-10-01T13:47:08.815Z",
"previousVersionAt": "2026-10-01T13:47:08.815Z",
"changeSize": 763,
"addedLinesCount": 20,
"removedLinesCount": 20,
"addedLines": [
"185 points by Snowly 2 hours ago | hide | 38 comments",
"28 points by trickypr 1 hour ago | hide | 12 comments",
"…"
],
"removedLines": [
"185 points by Snowly 2 hours ago | hide | 35 comments",
"28 points by trickypr 1 hour ago | hide | 10 comments",
"…"
],
"changeTypes": ["number", "text-changed", "text-added", "text-removed"],
"priceChanges": [],
"numberChanges": [
{ "from": "35", "to": "38", "context": "185 points by Snowly 2 hours ago | hide | 38 comments", "section": null },
"…"
],
"diffUrl": "https://api.apify.com/v2/key-value-stores/<store id>/records/diff-0f63a2a5a5620b745938e6a2-20261001134748",
"title": "Hacker News",
"httpStatus": 200,
"contentMode": "main-content",
"contentRoot": "body",
"contentLines": 93,
"error": null
}

On a pricing page the summary reads like "Price text changed from €49 to €59 under 'Pro' ("€59 per month"); 3 lines added under 'Pro'" and priceChanges holds [{ "from": "€49", "to": "€59", "context": "€59 per month", "section": "Pro" }] (from the tests in test/).

The first check of a page (real row, example.com):

{
"url": "https://example.com/",
"status": "baseline",
"summary": "Baseline saved (first check of this page). Changes are reported from the next run.",
"checkedAt": "2026-10-01T13:40:36.887Z",
"previousCheckedAt": null,
"title": "Example Domain",
"httpStatus": 200,
"contentMode": "main-content",
"contentRoot": "body",
"contentLines": 2
}

status is one of baseline, changed, unchanged (only with Include unchanged pages) and error. The Price changes tab lists every price that changed, appeared or disappeared.

Errors are free

A page that can't be checked gets a free row with status: "error", an error code and a plain-English errorMessage, and its saved version is not touched, so the next successful check still compares against it:

errorMeaning
dns-not-found, connection-failed, timeout, tls-errorThe site could not be reached.
http-errorThe page answered 404, 500 … (429 and 5xx are retried first).
blockedThe site needs a login or refuses automated visits (401, 403, bot challenge).
not-text, too-largeNot a web page (e.g. a PDF), or larger than 5 MB.
selector-not-foundYour CSS selector matched nothing on this page.
empty-contentNo visible text: the page probably builds its content with JavaScript.
invalid-url, invalid-inputA URL or ignore pattern in the input can't be used.

How much does it cost?

This Actor uses pay per event:

EventPrice
Page checked (fetched and compared, changed or not)$0.001 per page
Pages that fail to load, error rowsfree
  • 100 pages checked daily cost $0.10 a day (about $3 a month); hourly checks of 10 pages cost $0.24 a day.
  • The first (baseline) check of a page costs the same $0.001.
  • Apify also charges a tiny standard start fee per run, and the snapshots use a little key-value storage in your account.
  • Set Maximum cost per run in the run options and the Actor stops when it is reached.

Where the history is kept

Snapshots live in a named key-value store in your Apify account, called change-monitor-<monitor name> (or change-monitor-<hash of your URLs> without a name). Named stores are kept until you delete them, so every scheduled run remembers the previous one. Each page keeps its current version and the latest 20 full diffs.

  • Same Monitor name = same history, even if you add or remove URLs (new URLs just get a baseline).
  • Without a name, changing the URL list or selector starts a fresh monitor. Set a name for monitors you edit over time.
  • Changing the CSS selector saves a new baseline for each page (rows say so) instead of reporting the whole page as changed.
  • To start over, delete the store in Storage → Key-value stores.

Limitations

  • No JavaScript rendering. Pages are fetched over plain HTTP, like a search engine sees them. Content that only appears after scripts run (many single-page apps) gives an empty-content error or misses those parts; a CSS selector on server-rendered parts often helps.
  • Text only. Images, styles and layout changes are not compared; image alt text is not either.
  • Pages behind a login, a paywall or bot protection can't be checked.
  • One request per page per run, one at a time per website, with retries; up to 500 URLs per run.
  • The summary is rule-based: it recognises prices in common formats (€49, $1,299.00, 49 EUR, 129,90 zł), numbers, dates and availability words in English. Other changes are described as lines added, removed or changed under the nearest heading.

FAQ

Why did my first run return a row for every page?

The first check of each page saves its baseline and says so. Changes are reported from the next run on.

Why do I get no rows?

Nothing changed since the last check. That is the point: rows only appear when a page changes (or turn on Include unchanged pages to see every check).

How do I watch only the price on a product page?

Use a CSS selector such as .price, .availability. Only those elements are compared.

A clock or counter on the page reports a change every time.

Turn on Ignore dates and times, add an ignore pattern for the counter, or set a minimum change size.

Is my data shared?

No. Snapshots and diffs stay in your own Apify storage.

More tools from the same developer

All pay-per-result, no proxy or login needed, built and maintained by the same developer:

Website audits

Company data and compliance

Market signals

Feedback

Missing a rule in the summary, or a page type that doesn't work well? Open an issue on the Issues tab.