Website Screenshot & Visual Change Monitor avatar

Website Screenshot & Visual Change Monitor

Pricing

$2.00 / 1,000 successful page checks

Go to Apify Store
Website Screenshot & Visual Change Monitor

Website Screenshot & Visual Change Monitor

Monitor website visual changes and capture stable PNG screenshots in bulk. Compare every successful render with the last good baseline, get pixel-change ratios, highlighted diffs and changed regions, and send webhook-ready results for QA, ecommerce, compliance and competitor tracking.

Pricing

$2.00 / 1,000 successful page checks

Rating

0.0

(0)

Developer

Vadim Bezrukov

Vadim Bezrukov

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

2 days ago

Last modified

Share

Monitor website visual changes between runs with pixel-level comparisons, highlighted diffs, changed regions and webhook-ready events. The same Actor is a website screenshot API for stable PNG captures of one public page or a batch.

Save a monitor input as an Apify Task and run it on a schedule. The first successful run creates a BASELINE; every later complete capture returns UNCHANGED or VISUAL_CHANGE against the last successful capture.

A timeout, blocked page, incomplete render or artifact-storage failure never becomes “unchanged” and never replaces a good baseline.

See what the diff means

These are controlled synthetic images of an example pricing card. The Actor's actual image-comparison code generated the diff. The price changes from $29 to $24 while a dynamic offer panel also changes.

BeforeAfterDiff (red pixels changed)
Controlled before imageControlled after imageCalculated unmasked diff

The unmasked images differ by 11.2652%. With the dynamic region masked in both images while preserving layout, the price change accounts for 0.0547%:

Masked beforeMasked afterMasked diff
Controlled masked beforeControlled masked afterCalculated masked diff

In the noise-only fixture, masking reduces changed pixels from 28,699 to zero. Reproducible pixel counts describe this image fixture; browser selectors must still be checked on your target page.

changeThreshold is a fraction: 0.01 means 1%. That default would miss the small price edit in this full-page example. For a small important change, mask known noise and lower the threshold (for these images, 0.0005 catches the 0.0547% change). Inspect the diff before choosing alert sensitivity. Use precise ignoreSelectors to mask changing content without reflow; hideSelectors removes content and can change layout.

Start with the public ecommerce test-page example, then repeat the same Task. Current, previous and diff PNGs are saved in each run's storage and are not overwritten by later runs. Export them if you need evidence beyond your Apify storage-retention period.

What you get

  • PNG viewport or full-page website screenshots;
  • stable desktop, mobile, locale, timezone and motion settings;
  • bounded lazy-load scrolling, font/image settling and animation suppression;
  • ignoreSelectors for noisy content without layout reflow;
  • hideSelectors for banners or widgets that should be removed;
  • deterministic pixel-change ratio and changed-region bounding boxes;
  • optional highlighted diff PNG on meaningful changes;
  • one explicit result for every requested URL, including invalid, failed and blocked pages;
  • one atomic baseline record per monitoring configuration - no hidden screenshot-history database.

Who uses this website visual change monitor?

  • QA and engineering teams comparing production pages after a release;
  • ecommerce teams tracking product, pricing and checkout-page changes;
  • compliance teams keeping timestamped visual evidence of public disclosures;
  • agencies monitoring client landing pages and campaign assets;
  • competitive-intelligence workflows that need a machine-readable change event, not just an image.

Ready-made example Tasks

Quick start

1. Basic screenshot

{
"urls": [{"url": "https://example.com", "externalId": "homepage"}],
"mode": "snapshot",
"fullPage": false
}

2. Full-page screenshot

{
"urls": [{"url": "https://webscraper.io/test-sites/product/toyota-2000gt-1970-c007"}],
"mode": "snapshot",
"fullPage": true,
"scrollForLazyLoad": true
}

3. Batch screenshots

{
"urls": [
{"url": "https://example.com", "externalId": "home"},
{"url": "https://webscraper.io/test-sites/product/toyota-2000gt-1970-c007", "externalId": "pricing"},
{"url": "https://example.org", "externalId": "terms"}
],
"mode": "snapshot"
}

One bad URL does not stop the others. Three inputs always produce three dataset records unless the whole Actor runtime cannot start.

4. Mobile viewport

{
"urls": [{"url": "https://example.com"}],
"mode": "snapshot",
"viewport": {"width": 390, "height": 844},
"fullPage": true
}

Viewport and capture settings are part of the monitor state identity. Mobile and desktop captures never share a baseline.

5. Monitor mode

{
"urls": [{"url": "https://webscraper.io/test-sites/product/toyota-2000gt-1970-c007", "externalId": "competitor-pricing"}],
"mode": "monitor",
"fullPage": true,
"changeThreshold": 0.01
}

Run the same saved Task again. changeThreshold is a fraction: 0.01 means one percent of the normalized common image canvas.

6. Visual diff

{
"urls": [{"url": "https://example.com"}],
"mode": "monitor",
"changeThreshold": 0.005,
"saveDiffImage": true
}

When the ratio reaches the threshold, the dataset row contains VISUAL_CHANGE, changed_regions, and a diff_image_url with changed pixels highlighted in red.

{
"urls": [{"url": "https://example.com"}],
"mode": "monitor",
"ignoreSelectors": [".live-clock", "[data-testid='ad-slot']"],
"hideSelectors": ["#cookie-banner", ".chat-widget"]
}

Ignored elements keep their layout space but their content is invisible. Hidden elements use display:none and can cause layout reflow. Prefer a precise selector over a high global threshold. The Actor intentionally has no fragile built-in database of banner selectors.

8. Daily schedule

  1. Run a monitor input successfully once.
  2. In Apify Console, choose Save as task.
  3. Open Schedules, create a daily schedule, and select that Task.
  4. Keep URL, viewport, full-page, wait, delay and selector settings stable.

Monitor mode uses the persistent named KVS website-screenshot-visual-monitor-state, so independent Task runs find the last committed successful observation. Baseline metadata and its PNG are stored together, after confirmed result delivery. Every result's current, previous and diff PNGs, plus RUN_SUMMARY, live in that run's default storage. A new capture configuration creates a separate BASELINE instead of comparing unlike screenshots.

Overlapping runs may compare with the same prior baseline; the last completed baseline write wins as a complete observation. Schedule checks without overlap when you need a strictly chronological comparison chain. externalId is a join label and does not create a separate monitor. Valid legacy baselines migrate on the first successfully delivered check; old artifacts are retained.

9. Webhook on VISUAL_CHANGE

Attach an ACTOR.RUN.SUCCEEDED webhook to the scheduled Task. In the receiver, fetch the run dataset and act only on rows satisfying both conditions:

const changed = datasetItems.filter(
(item) => item.status === 'SUCCESS' && item.change_type === 'VISUAL_CHANGE',
);

Never branch on changed alone: failure rows deliberately use changed: null. The diff URL and changed regions make each matching row usable as an alert payload without comparing two exports downstream.

Input reference

FieldDefaultMeaning
urlsrequired1–500 public HTTP(S) pages, with optional externalId
modesnapshotsnapshot does not touch monitor state; monitor compares successful captures
fullPagetrueFull scrollable page or viewport only
viewport1440 × 900Deterministic CSS-pixel viewport
waitUntilnetworkidlenetworkidle, load, or domcontentloaded
delayMs500Extra render-settling time, 0–30 seconds
navigationTimeoutMs45000Hard per-attempt navigation limit
scrollForLazyLoadtrueBounded scroll before full-page capture
ignoreSelectors[]Preserve layout while hiding selected content
hideSelectors[]Remove selected elements before capture
changeThreshold0.01Changed-pixel fraction required for VISUAL_CHANGE
saveDiffImagetrueSave one highlighted PNG for a meaningful change
proxyConfigurationdirectOptional user-controlled Apify/custom proxy

Browser pool size, concurrency, Chromium arguments and retry timing are not Store inputs. They are conservative internal runtime choices.

Output and failure semantics

Every row carries source, stable source_id, source_url, scraped_at, schema_version and fingerprint, plus screenshot/change fields. Results can arrive in completion order; input_index is their zero-based input position. Repeated normalized URLs share one capture: their alias rows retain each external_id, carry duplicate_of, and incur no extra event charge.

Statuses:

  • SUCCESS - navigation, rendering, screenshot and KVS write all completed;
  • PARTIAL - something rendered, but completeness or storage could not be verified;
  • FAILED - navigation/browser/source failed after bounded retry where sensible, or the check was rejected before capture by a too-low run spending limit;
  • BLOCKED - HTTP 401/403/407/429, CAPTCHA or a recognizable challenge page;
  • INVALID_URL - rejected before browser access, including private/local targets.

Change types exist only on successful monitor captures:

  • BASELINE - first success for this URL and capture configuration;
  • UNCHANGED - changed-pixel ratio is below changeThreshold;
  • VISUAL_CHANGE - ratio reached the threshold.

Full-page dimension changes are normalized onto a common canvas. Added or removed width/height counts as change even when the added region is blank.

See examples/sample_output.json for a complete change event.

API use

Use the standard Apify Actor API with your own Actor ID and token. Never place a token in source control.

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automa-flow/website-screenshot-visual-monitor").call(
run_input={
"urls": [{"url": "https://example.com", "externalId": "homepage"}],
"mode": "monitor",
"changeThreshold": 0.01,
}
)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())

Use with AI agents through Apify MCP

Expose the Actor as a typed MCP tool:

https://mcp.apify.com?tools=automa-flow/website-screenshot-visual-monitor

Example prompt:

Run automa-flow/website-screenshot-visual-monitor in monitor mode for these
public product-page URLs. Report only VISUAL_CHANGE rows above a 1% threshold,
include changed regions and screenshot/diff URLs, and keep failed captures
separate from unchanged pages.

The first successful monitor run creates a BASELINE. A blocked or partial capture never becomes UNCHANGED and never replaces the last good baseline.

Pricing

The publication price is $0.002 per successful page check ($2 per 1,000). A page check includes the persisted PNG, and in monitor mode it also includes comparison with the last successful baseline, changed-region detection and the optional highlighted diff image. There is no start fee.

Successful page checksActor charge
1$0.002
100$0.20
1,000$2.00

An unchanged monitor check is billable because the page was rendered, persisted and compared. Invalid URLs, blocked pages, partial captures, failed captures and retries are not charged. Platform usage is included in this pay-per-event price; the Apify Console price shown at run time is canonical. If a run's maximum total charge cannot cover every potentially successful URL in the batch, the Actor stops before opening Chromium, writes one free BILLING_LIMIT_TOO_LOW result per otherwise valid URL and fails the run. No monitor baseline is advanced without a deliverable paid result. A batch containing only invalid URLs succeeds with one explicit free INVALID_URL row per input because those are per-item outcomes, not a system failure. Repeated normalized URLs are captured and charged only once per run, with free alias rows for the remaining input positions.

A completed run is not replayed after resurrection. If output delivery was interrupted or its acknowledgement was lost, the Actor retains existing output and stops with DELIVERY_UNCERTAIN; start a new run for a new observation. Dataset, billing and baseline storage do not form one transaction. A failed baseline commit after successful delivery fails the run while retaining the delivered PNG and result. RUN_SUMMARY distinguishes confirmed charges from unknown delivery outcomes; use platform event counts for final accounting. If all captures fail, the run fails with NO_SUCCESS and retains the free error rows.

Capture settings and proxy use

The Actor reuses one Chromium process, creates an isolated context per URL and runs at internal concurrency 2. Every HTTP(S) navigation and page resource is checked against local, private and reserved networks, and service workers are disabled so they cannot bypass that guard. Direct access is the default. Enable a proxy only when you are permitted to access the page and direct anonymous access is insufficient. The Actor does not automatically escalate to residential proxies and never attempts universal anti-bot bypass.

Use the default 2 GiB memory allocation. Dynamic content can produce visual changes even without a site update: mask noisy regions and review the first diff before connecting alerts. RUN_SUMMARY records page-check counts and usage.

This Actor is for public webpages only. Do not submit private dashboards, credentials, cookies, authenticated profiles or URLs carrying secrets. It does not bypass login, paywalls, CAPTCHA, account permissions or access controls.

Website terms and robots policies differ. You are responsible for having the right to access, capture, process and retain each page. A screenshot can contain public personal data or copyrighted material; visual monitoring grants no right to republish it.

The named monitoring KVS retains one current baseline envelope per URL/configuration. It contains both the PNG and its metadata, so competing writes cannot mix them. Result PNGs use content-addressed keys in each run's default KVS; current and previous share a key when their PNG bytes are identical. Dataset observations and run PNGs follow your Apify storage-retention policy. Legacy rolling artifacts are not automatically deleted during migration.

Known limitations

  • Pixel comparison detects visible differences, not their business meaning.
  • Personalization, A/B tests, ads, live counters and geography can create genuine pixel changes; use precise selectors and an observed threshold.
  • Font/browser upgrades can change rendering. The pinned image reduces this risk; an intentional runtime/configuration change re-baselines safely.
  • Full-page captures above 50,000 CSS pixels in height or 30 million rendered pixels return PARTIAL/PAGE_TOO_LARGE instead of risking an out-of-memory run.
  • If two individually valid captures require a comparison canvas above 30 million pixels, the check returns PARTIAL/COMPARISON_TOO_LARGE and preserves the baseline.
  • Full-page scrolling is bounded to 20 viewport steps. Reaching that limit before the bottom returns PARTIAL/LAZY_SCROLL_LIMIT. Visible images throughout the capture area must finish loading; broken or timed-out images return PARTIAL/INCOMPLETE_IMAGES, without a charge or baseline update. Images masked by your selectors and images outside a viewport-only capture are excluded.
  • Closed shadow DOM, cross-origin frames and canvas animations cannot always be selectively ignored with CSS selectors.
  • Login-required and strongly protected pages are intentionally unsupported.

FAQ and troubleshooting

Can I use this as a website screenshot API?

Yes. Run it through the Apify API with one URL or a batch of up to 500 URLs. Each input produces an explicit dataset row on an ordinary run, and screenshot files are stored in the Actor's key-value store.

How does visual change detection work?

The first successful monitor run creates a baseline. Later runs normalize the two PNG canvases, compare pixels and report UNCHANGED or VISUAL_CHANGE based on changeThreshold. Failed or incomplete checks never replace the baseline.

Why does a page report changes every day?

Ads, clocks, cookie banners, rotating content, personalization and A/B tests are real pixel changes. Add precise ignoreSelectors or hideSelectors, keep the viewport stable and raise the threshold only after reviewing representative diffs.

Why is a screenshot blocked or partial?

BLOCKED means the site returned an authorization/rate-limit response or a recognizable challenge. PARTIAL means the Actor could not prove a complete, persisted capture. The Actor does not bypass login, CAPTCHA or access controls; use a permitted proxy only when direct access is insufficient.