Website Change Watcher: Page, Sitemap & Feed Monitor avatar

Website Change Watcher: Page, Sitemap & Feed Monitor

Pricing

from $2.00 / 1,000 target checkeds

Go to Apify Store
Website Change Watcher: Page, Sitemap & Feed Monitor

Website Change Watcher: Page, Sitemap & Feed Monitor

Website change monitor and price tracker: watch any page, sitemap or feed and get only what changed. Track price and stock with CSS selectors (old, new, difference), quiet hours, no false alarms. Never charges for a failed or empty result.

Pricing

from $2.00 / 1,000 target checkeds

Rating

0.0

(0)

Developer

Pavel Tashev

Pavel Tashev

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 hours ago

Last modified

Share

What does Website Change Watcher do?

Website Change Watcher checks pages, sitemaps and feeds on a schedule and reports only what changed since the last run — the added and removed text, the new and removed URLs, the new feed items, and the before and after value of a price, stock status or any number you name.

It is built for monitoring that runs unattended: competitor prices, product availability, pricing and terms pages, documentation, a competitor's new blog posts or job ads, and news feeds. Because it runs on the Apify platform you get scheduling, webhooks, integrations (Make, Zapier, n8n, Slack) and an API, and every run keeps its own history.

Why use Website Change Watcher?

  • Values, not just text. Point it at a price with a CSS selector and get 199.00 → 179.00, with the difference and the percentage, classified as a price drop.
  • No false alarms. Clocks, timestamps, "3 minutes ago" and request tokens are filtered out. A blocked page, a server error or a half-loaded sitemap keeps the saved snapshot instead of reporting an invented change.
  • Noise control. Watch part of a page with a selector, ignore text with your own patterns, require a minimum percentage change, or hold a change back until it repeats.
  • Honest results. Every row says exactly what happened: ok, unchanged, baseline, empty, blocked, not_found, error or skipped. Only a real comparison is charged.
  • Three kinds of target in one Actor. A page's text, a sitemap's URLs, or a feed's items.

How to monitor a website for changes

  1. Click Try for free.
  2. Put one or more addresses in Targets: a page, a sitemap, a feed, or a bare domain.
  3. Optional: add Values to extract to track a price or stock status, and a selector to watch only part of the page.
  4. Click Start. The first run saves a baseline and charges nothing.
  5. Open Schedules and run the Actor every hour or every day. From then on each run returns only what changed.

Price and stock monitoring

Add entries to Values to extract. Each one has a name, a CSS selector and a type (text, number, price, date or boolean):

{
"targets": ["https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"],
"extract": [
{ "name": "price", "selector": ".price_color", "type": "price" },
{ "name": "inStock", "selector": ".instock.availability", "type": "boolean" },
{ "name": "title", "selector": "h1", "type": "text" }
]
}

The first run saves {"price": 51.77, "inStock": true, "title": "A Light in the Attic"}. When the price later drops to 46.99, the next run returns:

{
"status": "ok",
"changed": true,
"changeKind": "price-down",
"values": { "price": 46.99, "inStock": true, "title": "A Light in the Attic" },
"previousValues": { "price": 51.77, "inStock": true, "title": "A Light in the Attic" },
"deltas": {
"price": { "from": 51.77, "to": 46.99, "changed": true, "absolute": -4.78, "percent": -9.23 }
}
}

changeKind tells you what moved, so a workflow can react differently to each case:

changeKindWhat it means
price-down / price-upA value of type price moved down or up
availabilityA value of type boolean flipped, for example in stock → sold out
valueAnother extracted value changed
contentThe page text changed, with no watched value moving
structuralA sitemap gained or lost URLs, or a feed gained items

Availability wins over a price change, and a price drop wins over a price rise, so the most useful label is the one you get.

Selector syntax

Tags, #id, .class, [attribute], [attribute="value"], descendant paths (.product .price) and direct children (.product > .price). End a selector with ::text (the default) or ::attr(name) to read an attribute:

  • .price_color — the text of the first matching element
  • meta[itemprop="price"]::attr(content) — a price hidden in a meta tag, which is usually the most stable source
  • .product > .price::text — only a direct child, ignoring nested matches
  • [data-testid="stock-status"] — attribute-only selectors work too

Prices and numbers are read in any common format: 1,234.56, 1.234,56, 1 234,56, €19.99, 19.99 EUR. The currency is reported separately. Dates accept ISO, 17/09/2026 (day first), 17 September 2026 and similar.

When a selector finds nothing

The value is returned with status: "empty" and a reason such as Nothing on the page matched ".price". It is never a silent null, and the saved snapshot is kept, so a redesign of the page does not look like a price change. If every watched value comes back empty, the whole target is reported as empty and is not charged.

Scheduling: quiet hours, weekdays and a minimum gap

Schedule the Actor as often as you like and let these settings decide when a target is actually fetched. A skipped target is not requested and not charged.

  • Quiet hours — for example 22:00-07:00, including ranges that cross midnight.
  • Check only on these days — for example mon, tue, wed, thu, fri.
  • Minimum hours between checks — run the Actor hourly for urgent targets while a slower target is checked once a day.
  • Time zone — a name such as Europe/Sofia or an offset such as +02:00. Defaults to UTC.

Ignoring rotating content (debounce)

Some sites rotate banners or run A/B tests, so the page differs on every visit. Set Report a change only after this many runs in a row to 2 or 3: a change is held back until the same content is seen that many times, and the baseline is kept until then. The trade-off is that a genuine change is reported one run later.

Input

FieldDescriptionDefault
TargetsPages, sitemap URLs, feed URLs or domains.
What to watchDetect automatically, or force page / sitemap / feed.auto
First check of a targetSave the baseline silently, or return a baseline row.baseline only
Watch only this part of the pageCSS selector: tags, #id, .class, [attribute], paths with spaces or >.whole page
Values to extractNamed values to track: {name, selector, type} with type text, number, price, date or boolean.
Report a change only after this many runs in a rowHold a change back until it repeats, to ignore A/B tests.1
Quiet hoursWindow when targets are skipped, e.g. 22:00-07:00.
Check only on these daysWeekdays when checks may run, e.g. mon, tue.every day
Minimum hours between checksSkip a target checked more recently than this.0
Time zoneEurope/Sofia or +02:00, for quiet hours and weekdays.UTC
Compare visible text onlyOff also compares HTML, links and attributes.on
Ignore clocks, timestamps and "x minutes ago"Removes the usual per-visit noise.on
Ignore all numbersFor view counters and stock levels. Turn off when watching prices.off
Ignore text matchingYour own regular expressions, e.g. Cart \(\d+\).
Minimum change to report (%)Ignore changes smaller than this.0
Maximum URLs or items per targetFor sitemaps and feeds.50,000
Maximum characters of changed textCap per target.5,000
Shrink safety threshold (%)Treat a suddenly tiny response as a block, not a change.30
Respect robots.txtSkip URLs the site disallows.on

Example input:

{
"targets": ["https://www.python.org/downloads/", "https://blog.cloudflare.com/rss/"],
"selector": "main",
"ignoreNumbers": false,
"minChangePercent": 1,
"quietHours": "22:00-07:00",
"timezone": "Europe/Sofia",
"requireConsecutiveChanges": 2
}

Output

One row per target, whatever happened to it. A real example from a run on 17 September 2026:

{
"target": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
"type": "page",
"status": "ok",
"reason": null,
"changed": true,
"changeKind": "price-down",
"values": { "price": 46.99, "inStock": true, "title": "A Light in the Attic" },
"previousValues": { "price": 51.77, "inStock": true, "title": "A Light in the Attic" },
"deltas": {
"price": { "from": 51.77, "to": 46.99, "changed": true, "absolute": -4.78, "percent": -9.23 }
},
"valueDetails": {
"price": { "raw": "£46.99", "status": "ok", "selectorMatches": 1, "currency": "GBP" }
},
"similarity": 0.9861,
"changePercent": 1.39,
"title": "A Light in the Attic",
"addedText": "£46.99",
"removedText": "£51.77",
"statusCode": 200,
"previousCheckedAt": "2026-09-17T08:10:11+00:00",
"checkedAt": "2026-09-17T09:10:09+00:00",
"pendingChange": false,
"error": null
}

For a sitemap you get addedUrls, removedUrls and modifiedUrls; for a feed you get newItems with title, URL and publication date. You can download the dataset as JSON, CSV, Excel or HTML, or fetch it through the API. A per-target summary of the run is saved in the key-value store as WATCH_REPORT.

Output fields

FieldMeaning
target, url, site, typeWhat you entered, what was read, and whether it was a page, sitemap or feed
status, reasonWhat happened to this target, and why when it is not ok or unchanged
changed, changeKindWhether it changed, and what kind of change it was
values, previousValues, deltas, valueDetailsExtracted values now and before, the difference, and how each one was read
pendingChange, pendingSeenA change is being held back until it repeats, and how many times it has been seen
changeTypeKept from version 1: baseline, unchanged, modified or error
similarity, changePercentHow much of the target changed
added, removed, addedText, removedText, diffThe changed text
addedUrls, removedUrls, modifiedUrlsSitemap changes
newItemsNew feed items (title, URL, date)
title, selectorMatches, contentLength, previousContentLength, entries, previousEntriesContext for the comparison
statusCode, previousCheckedAt, checkedAtWhen and how the target answered
error, noteWhy a target could not be compared, or what the run decided

What each status means

statusMeaningCharged?
okChecked, and something changedYes (plus the change fee)
unchangedChecked, nothing changedYes
baselineFirst check of this target; the snapshot was savedNo
emptyFetched, but there was nothing to compare (selector or values matched nothing)No
blockedrobots.txt, 401/403, rate limiting, or a page far smaller than last timeNo
not_found404 or 410No
errorAnything else, with the reason in reasonNo
skippedQuiet hours, an excluded weekday, or the minimum gap between checksNo

The run's status message lists the counts, for example 12 targets: 2 changed, 8 unchanged, 1 blocked, 1 skipped (2× price-down). Charged: 10 checks + 2 changes.

How much does it cost to monitor a website?

This Actor uses pay-per-event pricing: a small fee per target checked, plus a fee when a change is found. Only a real comparison is charged, which means ok and unchanged. A first check (baseline), a target that could not be read (blocked, not_found, error), a target with nothing to compare (empty) and a target skipped by your schedule (skipped) are all returned in the dataset free of charge. See the Pricing tab for current prices. Platform compute is included.

Each target is saved and charged the moment it finishes, not at the end of the run. If a run reaches your maximum cost, or stops shortly before its timeout, the targets already checked keep their results and the rest are returned as skipped free of charge. Nothing you paid for is thrown away.

Limits per run

LimitValue
Targets per run500
Items per target (sitemap URLs or feed items)up to 50,000
Targets checked in parallelup to 10
Page size fetched per targetup to 10 MB

These are the sizes the Actor is tested at. Larger watch lists work well as several scheduled runs.

Watching 20 pages every hour is 480 checks a day; watching them once a day is 20. Start with a daily schedule and increase it only for pages that really move.

Where the comparison is stored

Snapshots live in a key-value store named website-change-watcher-state in your own Apify account, one compressed record per target. Nothing is stored outside your account. Deleting that store resets the monitoring, and the next run saves a fresh baseline. A snapshot is tied to the target plus the settings that decide what is compared (selector, text-only, ignore rules), so changing one of those starts a new baseline; changing reporting-only settings such as the minimum change does not.

Tips

  • Use a selector for noisy pages. main, #content or .product removes menus, cookie banners and "related posts".
  • Watching a price? Add it to Values to extract as type price instead of relying on the text diff. Prefer a meta[itemprop="price"]::attr(content) selector when the page has one: it survives redesigns better than a visible element.
  • Alerted by rotating banners? Set Report a change only after this many runs in a row to 2.
  • Checking many targets on one schedule? Use Minimum hours between checks so urgent and slow targets can share a single hourly schedule.
  • Watching wording or availability? Turn Ignore all numbers on so counters do not trigger alerts.
  • Getting an alert every run? Add the changing phrase to Ignore text matching, or raise Minimum change to report.
  • JavaScript-rendered pages are not supported: this Actor reads the HTML the server returns, with no browser. If the content only appears after scripts run, watch the underlying API endpoint or feed instead.

For AI agents

The Actor runs with limited permissions, charges per event and needs no login, so an agent can call it through the Apify MCP server or the API and pay for it directly. A run is one call: pass targets (and extract for prices or stock), then read back one row per target with status, changed, changeKind, deltas and the text or URLs that changed. Because every row states its own status, an agent never has to guess whether an empty result means "no change" or "the page was blocked". Keep firstRunOutput at none for silent baselines, and schedule the Actor rather than polling it from the agent.

FAQ and support

Does it store the pages it reads? Only a normalized, compressed copy of the compared text or URL list, in your own account, so the next run can diff against it. Email addresses are removed from the stored text and from the reported changes.

Why does the first run report nothing? The first check of a target saves a baseline. Set First check of a target to "Return a baseline row" if you want to confirm what is being watched.

What happens when a site won't let us read its robots.txt? Some sites put their /robots.txt behind bot protection and answer 403 or time out. We then can't tell what the site allows, so we follow one rule everywhere: the address you entered is still read, and addresses we would have worked out ourselves are not. A site with no robots.txt at all (404) is treated as allowing everything. For this Actor that means the URL you are watching is still checked, while sitemap and feed addresses we would look for on a bare domain are not.

Why did a change not get reported? Check note and error: the page may have been below the shrink safety threshold, below your minimum change percent, or blocked by robots.txt.

Something missing? Open an issue in the Issues tab. Only monitor pages you have the right to access.