Website Change Monitor & Page Diff Tracker
Pricing
Pay per usage
Website Change Monitor & Page Diff Tracker
Monitor website, SEO, pricing, landing page, documentation, and AI/RAG content changes. Track persistent snapshots, detect significant changes, and export structured page diffs for Apify Schedules, APIs, webhooks, and automations.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
João Victor
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
a day ago
Last modified
Categories
Share
Monitor website, SEO, pricing, landing page, documentation, changelog, and AI/RAG source changes — then export clean structured diffs for automations.
This Actor turns any public URL list into a scheduled website change monitoring API with persistent snapshots, noise filtering, and machine-readable diffs. It is built for teams that need automation-ready data — not just visual screenshots.
⚡ At a glance
| Monitor | Detect | Automate |
|---|---|---|
| Pricing pages | Text, HTML, metadata, headings, links | Apify Schedules |
| SEO pages | Significant changes and diff text | Webhooks |
| Landing pages | Added/removed content | Make, n8n, Zapier |
| Documentation | AI/RAG source drift | Slack, email, dashboards |
🎯 Best for
| Audience | Why they use it |
|---|---|
| SEO agencies | Monitor titles, descriptions, headings, landing pages, client sites, and competitor pages. |
| SaaS founders & growth teams | Track competitor pricing, homepage positioning, feature pages, docs, and changelogs. |
| AI/RAG teams | Know when source docs, help centers, FAQs, or knowledge-base pages changed. |
| Marketing teams | Watch CTAs, offers, testimonials, campaign pages, and product messaging. |
| Automation builders | Feed clean JSON diffs into alerts, dashboards, BI pipelines, and AI agents. |
What this Actor does
- Takes a list of public URLs.
- Fetches each page with fast HTTP mode by default.
- Optionally renders JavaScript-heavy pages with Playwright browser mode.
- Extracts visible text, HTML, or metadata.
- Removes noisy page parts such as scripts, navbars, footers, cookie banners, dates, or custom regex patterns.
- Stores a persistent baseline snapshot in a named key-value store.
- Compares future runs against the previous snapshot.
- Outputs structured dataset rows with
FIRST_SEEN,UNCHANGED,CHANGED, orERRORstatus. - Reports diff text, severity, change ratio, headings, link count, hashes, timestamps, and snapshot keys.
- Works with Apify Schedules, Tasks, API, webhooks, and integrations.
🧭 Typical workflow
| Step | Action | Output |
|---|---|---|
| 1 | Run with your URL list | FIRST_SEEN baseline snapshots |
| 2 | Schedule the same input | recurring checks |
| 3 | Detect changed pages | CHANGED, severity, changeRatio |
| 4 | Send results to your workflow | webhook, Slack, Make, n8n, Zapier, API |
Why this Actor is different
Generic website monitors often create noisy alerts from cookie banners, timestamps, layout shifts, nav menus, ads, analytics widgets, and formatting changes. This Actor is designed for commercial automation workflows:
- Persistent snapshots across scheduled runs.
- Structured JSON output for API users, BI, automations, and AI agents.
- CSS selector targeting to monitor only
main,article,.pricing,#content, or any page region. - Noise filtering with excluded selectors, date/number handling, whitespace normalization, and regex ignores.
- SEO-friendly modes for visible text, stricter HTML comparison, or metadata-only monitoring.
- Cost-aware defaults: HTTP mode, proxy disabled, browser disabled, bounded concurrency, response-size caps.
- Marketplace-safe security: private/internal URLs are blocked by default to reduce SSRF risk.
Use cases
| Use case | What to monitor | Recommended settings |
|---|---|---|
| Competitor pricing monitor | Pricing pages, plan tables, discount text | contentSelector: "main", ignoreNumbers: false, emitMode: "CHANGES_ONLY" |
| SEO content monitor | Landing pages, service pages, blog pages | extractionMode: "VISIBLE_TEXT", ignoreDates: true |
| SEO metadata tracker | Titles, descriptions, headings, links | extractionMode: "METADATA" |
| Landing page CTA monitor | Headlines, buttons, offers, hero text | contentSelector: "main", low threshold such as 0.03 |
| Documentation monitor | Docs, developer portals, changelogs | contentSelector: "main", emitMode: "SIGNIFICANT_ONLY" |
| AI/RAG freshness monitor | Knowledge-base pages, FAQs, help centers | scheduled task + webhook when pages change |
| Agency alert workflow | Client/competitor URL sets | separate snapshotStoreName per client |
| Compliance/evidence workflow | Public policy pages, terms, docs | enable saveHtmlSnapshot if raw HTML evidence is needed |
Quick start
- Open the Actor on Apify.
- Add one or more URLs in Pages to monitor.
- Keep
snapshotStoreNamestable across runs. - Run once to create the baseline. Results will be
FIRST_SEEN. - Run again later, or create a Schedule. Results will be
UNCHANGEDorCHANGED. - Use
emitMode = CHANGES_ONLYorSIGNIFICANT_ONLYfor alerts.
Minimal input:
{"startUrls": [{ "url": "https://example.com" }],"snapshotStoreName": "my-website-monitor","emitMode": "ALL"}
Tutorial: create a daily competitor pricing monitor
Step 1 — Add competitor URLs
{"startUrls": [{ "url": "https://competitor-one.com/pricing" },{ "url": "https://competitor-two.com/pricing" },{ "url": "https://competitor-three.com/pricing" }],"snapshotStoreName": "competitor-pricing-daily-monitor","contentSelector": "main","extractionMode": "VISIBLE_TEXT","ignoreDates": true,"ignoreNumbers": false,"significantChangeThreshold": 0.03,"emitMode": "CHANGES_ONLY","maxConcurrency": 3,"useBrowser": false,"proxy": { "useApifyProxy": false }}
Step 2 — Run once to create the baseline
The first run returns FIRST_SEEN because no previous snapshot exists yet.
Step 3 — Save as an Apify Task
Create a Task after verifying the input. Use a different snapshotStoreName for each client, competitor group, or project.
Step 4 — Schedule the Task
Create an Apify Schedule, for example once per day. For high-priority pricing pages, daily monitoring is usually enough. For docs and SEO content, daily or weekly monitoring usually works well.
Step 5 — Add notifications or workflow automation
Connect the Actor or Task to:
- Apify webhooks;
- Slack or Discord alerts;
- Make, Zapier, or n8n workflows;
- Google Sheets or Airtable dashboards;
- internal BI pipelines;
- AI agents that summarize changes.
Example inputs
Example 1 — SEO metadata monitor
{"startUrls": [{ "url": "https://example.com" },{ "url": "https://example.com/pricing" },{ "url": "https://example.com/blog" }],"snapshotStoreName": "seo-metadata-monitor","extractionMode": "METADATA","emitMode": "ALL","significantChangeThreshold": 0.01}
Example 2 — Documentation and changelog monitor
{"startUrls": [{ "url": "https://docs.example.com/introduction" },{ "url": "https://docs.example.com/api-reference" },{ "url": "https://example.com/changelog" }],"snapshotStoreName": "docs-rag-source-monitor","contentSelector": "main","excludeSelectors": ["nav", "footer", ".pagination", ".last-updated"],"extractionMode": "VISIBLE_TEXT","ignoreDates": true,"significantChangeThreshold": 0.05,"emitMode": "SIGNIFICANT_ONLY"}
Example 3 — Landing page CTA monitor
{"startUrls": [{ "url": "https://competitor.com" },{ "url": "https://competitor.com/product" }],"snapshotStoreName": "landing-page-cta-monitor","contentSelector": "main","excludeSelectors": ["script", "style", "nav", "footer", ".cookie", ".announcement-bar"],"ignoreDates": true,"ignoreNumbers": false,"significantChangeThreshold": 0.03,"emitMode": "CHANGES_ONLY"}
Example 4 — Dry run without updating the stored baseline
{"startUrls": [{ "url": "https://example.com/pricing" }],"snapshotStoreName": "production-pricing-monitor","updateSnapshots": false,"emitMode": "ALL"}
Input reference
| Field | Type | Default | Description |
|---|---|---|---|
startUrls | array | https://example.com | Pages to monitor. |
snapshotStoreName | string | website-change-monitor-snapshots | Named key-value store for previous snapshots. Use a different store per project/client. |
maxItems | integer | 10 | Maximum pages processed in one run. |
maxConcurrency | integer | 3 | Maximum URLs processed in parallel. Keep low for browser mode to control memory/cost. |
extractionMode | enum | VISIBLE_TEXT | What to compare: VISIBLE_TEXT, HTML, or METADATA. |
contentSelector | string | empty | Optional CSS selector for the exact page area to monitor, e.g. main, .pricing, article. |
excludeSelectors | string[] | common noise selectors | Elements removed before comparison. |
ignoreRegexes | string[] | [] | Regex patterns removed from content before comparison. |
ignoreWhitespace | boolean | true | Normalize whitespace. |
ignoreCase | boolean | false | Normalize casing. |
ignoreNumbers | boolean | false | Normalize numbers. Keep disabled for pricing pages. |
ignoreDates | boolean | true | Normalize common date formats. |
significantChangeThreshold | number | 0.05 | Minimum changed-content ratio to mark a change significant. |
emitMode | enum | ALL | ALL, CHANGES_ONLY, or SIGNIFICANT_ONLY. |
saveHtmlSnapshot | boolean | false | Store raw HTML evidence in the snapshot store. Increases storage usage. |
updateSnapshots | boolean | true | Disable for dry-run comparisons. |
maxDiffCharacters | integer | 2000 | Maximum length of addedText / removedText. |
useBrowser | boolean | false | Use Playwright for JavaScript-rendered pages. Costs more than HTTP mode. |
maxRetries | integer | 2 | Retry attempts for transient fetch errors. Applies to HTTP and browser mode. |
requestTimeoutSecs | integer | 20 | Fetch/browser timeout per page. |
maxContentBytes | integer | 2000000 | Maximum bytes read per page before stopping to protect memory/storage cost. |
allowPrivateUrls | boolean | false | Allow localhost/private/internal IPs. Keep disabled for public runs. |
proxy | object | disabled | Optional Apify Proxy configuration. Most public pages do not need proxy. |
Output
The Actor writes results to the default dataset. Each row represents one monitored URL.
Dataset item example — first run
{"url": "https://example.com","finalUrl": "https://example.com","title": "Example Domain","statusCode": 200,"changeType": "FIRST_SEEN","severity": "HIGH","isSignificant": true,"changeRatio": 1,"previousHash": null,"currentHash": "4a415f5252c741fbf3b4e11711552f78d0a5c3c0abaeefacedae0fc0807b63db","previousFetchedAt": null,"currentFetchedAt": "2026-07-05T18:44:29.407Z","addedText": "Example Domain This domain is for use in documentation examples...","removedText": null,"headings": ["Example Domain"],"linksCount": 1,"contentLength": 125,"snapshotKey": "SNAPSHOT_...","htmlSnapshotKey": null,"source": "http","error": null}
Dataset item example — changed page
{"url": "https://competitor.com/pricing","finalUrl": "https://competitor.com/pricing","title": "Pricing | Competitor","statusCode": 200,"changeType": "CHANGED","severity": "HIGH","isSignificant": true,"changeRatio": 0.31,"previousHash": "old-hash","currentHash": "new-hash","previousFetchedAt": "2026-07-04T08:00:00.000Z","currentFetchedAt": "2026-07-05T08:00:00.000Z","addedText": "Pro plan $39 per month AI add-on included","removedText": "Pro plan $29 per month","headings": ["Pricing", "Pro"],"linksCount": 12,"contentLength": 4210,"snapshotKey": "SNAPSHOT_...","htmlSnapshotKey": "HTML_...","source": "http","error": null}
Run summary
A summary is stored in the default key-value store under OUTPUT:
{"processed": 10,"emitted": 3,"changed": 2,"significant": 1,"unchanged": 7,"firstSeen": 1,"failed": 0,"snapshotStoreName": "competitor-pricing-monitor","startedAt": "2026-07-05T08:00:00.000Z","finishedAt": "2026-07-05T08:00:12.000Z"}
Understanding results
| Field | Meaning |
|---|---|
FIRST_SEEN | No previous snapshot existed. The current page is stored as the baseline. |
UNCHANGED | Current normalized content hash equals the previous hash. |
CHANGED | Content changed. Check changeRatio, severity, addedText, and removedText. |
ERROR | The page failed after retries. Error details are in error. |
| Severity | Approximate ratio | Meaning |
|---|---|---|
NONE | 0 | No content change. |
LOW | < 5% | Small change. Often harmless. |
MEDIUM | 5–25% | Meaningful content change. |
HIGH | > 25% | Large change or first snapshot. |
API usage
Run asynchronously:
curl -X POST \"https://api.apify.com/v2/actors/joaosbp~website-change-monitor-seo-ai/runs" \-H "Authorization: Bearer YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"startUrls": [{ "url": "https://example.com" }],"snapshotStoreName": "example-monitor","emitMode": "ALL"}'
Run synchronously and get dataset items:
curl -X POST \"https://api.apify.com/v2/actors/joaosbp~website-change-monitor-seo-ai/run-sync-get-dataset-items?format=json" \-H "Authorization: Bearer YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"startUrls": [{ "url": "https://example.com" }],"snapshotStoreName": "example-monitor"}'
FAQ
Is this a visual screenshot monitor?
No. It is a structured content and metadata change monitor. It focuses on text, HTML, metadata, hashes, and diffs that are useful for APIs, alerts, automation, SEO, and AI workflows.
Does it crawl an entire website automatically?
No. You provide the URLs to monitor. This keeps runs predictable, cheaper, and easier to schedule. If you need whole-site discovery, combine it with a sitemap extractor or a future sitemap-monitor Actor.
Why is the first run marked FIRST_SEEN?
The first run creates the baseline snapshot. Run the same task again later with the same snapshotStoreName to detect UNCHANGED or CHANGED.
How do I reduce noisy alerts?
Use contentSelector to focus on the important part of the page and excludeSelectors to remove navbars, footers, cookie banners, widgets, ads, or timestamps. Keep ignoreDates = true for pages with last-updated dates.
Can I monitor pricing pages?
Yes. Keep ignoreNumbers = false, use contentSelector = "main", and set emitMode = "CHANGES_ONLY". This is one of the primary use cases.
Can I monitor SEO titles and descriptions?
Yes. Use extractionMode = "METADATA" to focus on title, description, headings, and links.
Can I monitor JavaScript-heavy pages?
Yes, enable useBrowser = true. Browser mode costs more than HTTP mode, so use it only when the page content is not available in the initial HTML.
Do I need proxies?
Usually no. Proxy is disabled by default to keep costs low. Enable Apify Proxy only for pages that block normal HTTP requests or require proxy routing.
Are private/internal URLs allowed?
No, they are blocked by default for safety. Advanced users can set allowPrivateUrls = true only for trusted private monitoring jobs.
Does the Actor store raw HTML?
Only if saveHtmlSnapshot = true. By default it stores normalized snapshots and compact diffs to control storage costs.
Can I send alerts to Slack, email, Make, Zapier, or n8n?
Yes. Use Apify webhooks or run the Actor as a scheduled Task, then send dataset items or the OUTPUT summary to your automation tool.
What happens on errors?
One bad URL does not fail the entire run. The Actor emits an ERROR dataset item for that URL and continues with the rest.
Pricing and cost guidance
Recommended Apify monetization model: Pay Per Event after enough production usage data is collected.
A real remote test on 8 public pricing/docs pages with HTTP mode and proxy disabled succeeded at roughly $0.000996 total platform cost after tuning, or about $0.000125 per successful page in that sample.
Suggested future paid events:
| Event | Suggested price | When to charge |
|---|---|---|
page-checked | $0.003–$0.006 | successful non-error page check |
change-detected | $0.03–$0.10 | page changed above hash baseline |
significant-change-detected | $0.05–$0.20 | change ratio passes threshold |
Do not charge for per-URL errors. Keep browser/proxy modes as advanced options because they increase platform cost.
Limitations
- The Actor monitors the URLs you provide; it does not discover all pages on a website by default.
- JavaScript-heavy websites may require
useBrowser = true, which costs more. - Some websites block bots or require login; this Actor is designed for public pages.
- Text diff is optimized for concise monitoring signals, not full Git-style line-by-line review.
- Private/internal URLs are blocked by default for safety.
- First run always produces
FIRST_SEENbecause there is no baseline yet.
Best practices
- Use
contentSelectorfor noisy layouts. - Use separate
snapshotStoreNamevalues per client, project, competitor set, or environment. - Keep
ignoreNumbers = falsefor pricing and metrics pages. - Use
ignoreDates = truefor blogs, docs, and changelogs with timestamps. - Use
emitMode = CHANGES_ONLYorSIGNIFICANT_ONLYfor alerting workflows. - Use
updateSnapshots = falsewhen testing thresholds against an existing baseline. - Start with HTTP mode. Enable browser/proxy only when needed.
Changelog
See CHANGELOG.md for release history.
Search terms
Website change monitor, website monitoring, page change detector, page diff tracker, SEO monitor, SEO change tracker, competitor monitoring, competitor pricing monitor, pricing page monitor, landing page monitor, content change detection, documentation monitor, changelog monitor, help center monitor, AI RAG monitor, website alerts, web page monitoring API, Apify website monitor, scheduled web monitoring, structured diff API.
Support
If a page produces noisy changes, configure contentSelector, excludeSelectors, and ignoreRegexes before increasing the threshold. For pricing pages, keep numbers enabled. For documentation pages, usually monitor the main element and exclude navigation, footer, pagination, and last-updated widgets.