Page Watch — Website Change Monitor & Content Tracker
Pricing
from $1.00 / 1,000 page checkeds
Page Watch — Website Change Monitor & Content Tracker
Monitor any URL for content changes with hash-based change detection. Pay per check + per detected change. Ideal for watching competitor pages, docs, pricing, or any public web page.
Pricing
from $1.00 / 1,000 page checkeds
Rating
0.0
(0)
Developer
Mariano Ferreras
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 hours ago
Last modified
Categories
Share
Page Watch — Website Change Monitor
Pay-per-event pricing. Fetches URLs, hashes their content, and compares against the previous run's hash stored in Apify's KeyValueStore. Per-URL CSS selectors scope the check to specific page sections. A noise threshold (
minChangeRatio) suppresses small changes from rotating widgets or ad slots. Several competitors offer similar features — this is a commodity market; pick based on price and reliability. $0.001/check + $0.01/change.
The most important limitation: ultimate page-watch is trivial to DIY — a cron job + curl | sha256sum. You're paying for the hosted scheduler, KVS persistence across runs, and the CSS-selector noise-threshold UX.
What it does
This actor fetches one or more URLs, extracts the page text (optionally scoped to specific sections via CSS selectors), computes a content hash, and compares it against the previous check's hash stored in the actor's persistent KeyValueStore. An optional noise threshold (minChangeRatio, using Dice coefficient over word multisets) ignores small content churn so you only get charged for changes that matter. Each result includes the hashes and a diff excerpt around the first divergence. Several competitors offer similar features (e.g. foo121/website-change-monitor has CSS selectors + noise-filtered diffing, ryanclinton/website-change-monitor has 31 users).
Run it once to establish a baseline, then schedule it (e.g. every hour, daily) to monitor web pages continuously. Because it uses Apify's built-in KeyValueStore, the hash state persists across scheduled runs — no external storage needed. If you're doing competitor page tracking at scale, batch multiple URLs into a single run to work around Apify's schedule limit (100 per account).
Use cases
- Competitor page tracking — watch competitor landing pages, product pages, or docs for changes. Get notified when they update pricing, features, or copy.
- Price tracking — watch pricing pages on e-commerce or SaaS sites. Detects when prices go up or down.
- Documentation change alerts — monitor API docs, terms of service, or compliance pages. Know the moment something changes.
- Regulatory / legal monitoring — watch government pages, procurement portals, or regulatory sites for new announcements.
- Agent-powered watchlists — wire this into an AI agent workflow: have your agent set up monitors, check diff excerpts, and summarize what changed.
Input example
Watch only a price div on one page, the full body on another, and ignore under-2% churn everywhere:
{"urls": [{ "url": "https://example.com/pricing", "selector": ".pricing-table" },"https://example.com/docs"],"minChangeRatio": 0.02,"maxItems": 50,"useResidentialProxy": false}
| Field | Type | Default | Description |
|---|---|---|---|
urls | array | required | One or more URLs to check. Each entry can be a plain URL string, { "url": "..." }, or { "url": "...", "selector": "..." } to scope just that URL to a CSS selector |
defaultSelector | string | none | CSS selector applied to any URL that doesn't set its own selector. Example: .pricing-table |
minChangeRatio | number | 0 | Noise threshold in [0, 1]. 0 (default) reports every content hash difference, same as before. 0.02 ignores under-2% content churn — the hash still notices the diff, but it won't be reported as changed or charged unless the changed fraction meets the threshold |
selectors | array | [] | Legacy, global multi-selector mode — applied only to URLs with no per-URL selector and no defaultSelector. Kept for backward compatibility |
maxItems | integer | 50 | Max URLs to check in this run. 0 = unlimited |
useResidentialProxy | boolean | false | Upgrade to residential proxies if the target blocks datacenter IPs |
Per-URL selectors — behavior
- Selector resolution order per URL: its own
selector→defaultSelector→ the legacy globalselectorsarray → full page body. - A bad selector never produces a silent phantom "change". If a per-URL/default selector is invalid CSS or matches no content on the page, the actor logs a warning, sets
selectorWarningon that URL's result row, and falls back to hashing the full page instead of a stable empty string. - Switching a URL's selector (adding, removing, or changing it) changes what's being tracked, so the very next check may legitimately report one change as the new baseline is established — this is expected, not a bug.
Output example
[{"url": "https://example.com/pricing","checkedAt": "2026-07-23T23:00:00.000Z","changed": true,"hash": "a1b2c3d4e5f6g7h8","previousHash": "9i8j7k6l5m4n3o2p","title": "Example — Pricing","contentLength": 4523,"selector": ".pricing-table","selectorWarning": null,"diffSnippetPrevious": "...our starter plan is $10 per month with...","diffSnippetCurrent": "...our starter plan is $15 per month with...","changeRatio": 0.18,"belowChangeThreshold": false}]
| Field | Type | Notes |
|---|---|---|
url | string | The URL that was checked |
checkedAt | string | ISO 8601 timestamp of the check |
changed | boolean | true only when the content hash differs from the previous check AND (if minChangeRatio is set) the changed fraction meets the threshold |
hash | string | SHA-256 hex digest (first 16 chars) of the current extracted content |
previousHash | string|null | Hash from the previous check. null on the first run |
title | string|null | Page <title> content |
contentLength | integer | Character count of the extracted text |
selector | string|null | CSS selector actually used to extract content (per-URL, defaultSelector, or the legacy selectors list joined). null for full-page checks |
selectorWarning | string|null | Set when a per-URL/default selector was invalid or matched nothing — the check fell back to full-page text. null when no issue occurred |
diffSnippetPrevious | string|null | Excerpt of the previous content around the first difference. null when unchanged or first check |
diffSnippetCurrent | string|null | Excerpt of the current content around the first difference. null when unchanged or first check |
changeRatio | number|null | Fraction of content changed vs. the previous check (0-1), computed only when the hash differs and a previous check exists. null otherwise |
belowChangeThreshold | boolean | true when the hash differed but changeRatio was below minChangeRatio — a real change was detected but suppressed as noise (not reported, not charged) |
Pricing
This actor uses pay-per-event pricing. You are never charged for failed requests or quarantined results.
| Event | Price | Description |
|---|---|---|
page-checked | $1.00 / 1,000 checks | Charged once per URL checked, regardless of whether it changed |
page-changed | $10.00 / 1,000 changes | Charged once per detected change (in addition to the check fee) |
The page-changed event is only triggered when content actually differs from the previous check. Use a small maxItems for test runs. At $0.001/check, monitoring 10 URLs daily costs ~$0.30/month.
Scheduling
Page Watch is designed to be run on a schedule. Create a schedule in Apify Console (e.g. hourly, daily) and the actor will automatically compare against the previous run's hashes stored in its KeyValueStore.
To watch multiple URLs, batch them into a single run using the urls input array. This works around Apify's schedule limit (100 per account) and is more efficient than one schedule per URL.
Use with AI agents (MCP)
This actor is available as a tool for AI assistants via the Apify MCP server. Agents can call it to monitor web pages for content changes directly — no Apify console needed, just a single function call.
MCP setup
{"mcpServers": {"apify": {"command": "npx","args": ["mcp-remote","https://mcp.apify.com?tools=mfapitools/page-watch","--header","Authorization: Bearer <YOUR_API_TOKEN>"]}}}
Example agent prompts
- "Monitor these five competitor pricing pages and tell me if any changed"
- "Set up change detection for this government procurement portal URL"
- "Check all my watched URLs and summarize the diffs"
The actor returns structured JSON with the same fields shown above. Agentic workflows (Claude Code, Cursor, any MCP-compatible AI) can monitor pages and analyze change excerpts in the same run.
Limitations
- Text-based comparison — the actor extracts normalized body text (with
<script>,<style>,<noscript>stripped to avoid false positives from inline nonces/timestamps) and computes a hash. On change, you get a small excerpt around the first divergence — not a full structural or visual diff, and not an LLM "what changed" summary (planned for a future version). - First run is always a baseline — the first check of any URL will show
changed: falsebecause there is no previous hash. The second run detects changes. minChangeRatiocompares only the first ~10,000 characters of extracted text from each side (the same window used for diff-snippet storage) — a size-bounded, cheap comparison. For most pages this is representative of the whole page; for very large pages, changes past that window won't move the ratio.- Public data only — this actor fetches public URLs. No login-gated content.
- No notification delivery in v1.0 — changes are recorded in the dataset. You can wire up webhooks or Apify's email/slack integrations in Console to get notified when new results arrive.
- Hash collisions are theoretically possible — using SHA-256 truncated to 16 hex chars (64 bits). The collision probability is negligible for this use case (~2^-32 for 100k checks).
Changelog
See ./CHANGELOG.md.