# Website Screenshot & Visual Change Monitor (`jakob_jerich/website-screenshot-monitor`) Actor

Screenshot many URLs in desktop, laptop, tablet and mobile viewports in one run: full page, viewport or one element, as PNG, JPEG or PDF. Built-in visual change detection shows the % changed and a highlighted diff image. Pay only for successful screenshots.

- **URL**: https://apify.com/jakob_jerich/website-screenshot-monitor.md
- **Developed by:** [Jakob Jerič](https://apify.com/jakob_jerich) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Website Screenshot & Visual Change Monitor

Take **screenshots of any number of websites** in **desktop, laptop, tablet and mobile** viewports in one run: full page, visible area or a single element, as **PNG, JPEG or PDF**. Turn on **visual change detection** and every run compares each page pixel by pixel with the previous run. You get the **% of the page that changed** and a **diff image with the changes highlighted**.

Use it for responsive design checks, website change monitoring, competitor tracking, visual regression testing, archiving pages, and thumbnails for link previews. It works with the Apify scheduler, API, webhooks, Zapier, Make, n8n and AI agents (MCP).

### What you get

- **Several devices per URL in one run:** desktop, laptop, tablet, mobile (touch and mobile user agent). Optional retina (2×) rendering.
- **Full page, visible viewport or a single element** (CSS selector). Long pages are cut at a safety height.
- **Visual change detection built in:** % of pixels changed, `changed` true/false against your threshold, and a diff image with the changes highlighted in red.
- **Clean captures:** cookie banners hidden with CSS, your own elements to hide, dark mode, lazy-load scrolling.
- **One result row per capture, including failures**, with a readable error message. One broken URL never fails the whole batch.
- **Robust loading:** if a page never fires its final load event (heavy ads, long polling), it's captured as rendered so far instead of being dropped. Failed loads are retried once.
- **Fair billing:** you pay only for successful screenshots. Failures and HTTP error pages (4xx/5xx) are free.

### How to use

1. Add your **URLs** (one per line, `https://` optional).
2. Pick the **viewports** (for example Desktop + Mobile).
3. Optionally choose full page or element capture, format, delays, and elements to hide.
4. Click **Start**. Screenshots appear in the **Output** tab with a preview, and the files are in the run's key-value store.

**To monitor pages for changes:** turn on **Detect visual changes**, keep the format as PNG, and create a **Schedule** (for example daily). The first run stores a baseline. Every later run reports `changePercent` and `changed`, and links a diff image. To get alerts, add a webhook or integration (Slack, email, Zapier) and filter on `changed = true`.

### Input example

```json
{
  "urls": ["https://apify.com", "https://en.wikipedia.org/wiki/Web_scraping"],
  "viewports": ["desktop", "mobile"],
  "fullPage": true,
  "format": "png",
  "hideCookieBanners": true,
  "hideSelectors": ["#chat-widget"],
  "delayMs": 1000,
  "compareWithPrevious": true,
  "changeThresholdPercent": 0.1
}
```

All input fields have defaults. Only `urls` is required.

| Field | What it does |
|---|---|
| `urls` | Pages to capture. Duplicates and invalid URLs are skipped with a warning. |
| `viewports` | `desktop` 1920×1080, `laptop` 1366×768, `tablet` 768×1024 (touch, tablet user agent), `mobile` 390×844 (touch, mobile user agent). |
| `highDpi` | Render tablet and mobile at 2× pixel density, like real devices: sharper, but 4× the pixels. |
| `fullPage`, `maxFullPageHeight` | Whole scrollable page, cut at a safety height (default 10,000 px). |
| `selector` | Capture only one element, e.g. `#pricing`. |
| `format` | `png` (lossless), `jpeg` (quality 80, small) or `pdf` (A4, printable). |
| `waitUntil`, `delayMs`, `waitForSelector`, `scrollToBottom` | Control when the capture happens (lazy-loaded images, animations). |
| `hideCookieBanners`, `hideSelectors`, `darkMode` | Clean, stable screenshots. Banners are only hidden with CSS. Nothing is clicked or accepted. |
| `compareWithPrevious`, `monitorStoreName`, `changeThresholdPercent` | Visual change detection between runs. |
| `navigationTimeoutSecs` | How long to wait for each page. Failed loads are retried once. |

### Output example

You get one dataset row per URL × viewport. The fields are always the same; unused ones are `null`. This row is copied from a real run with change detection on (the second run, so a baseline existed):

```json
{
  "url": "https://news.ycombinator.com/",
  "viewport": "laptop",
  "viewportWidth": 1366,
  "viewportHeight": 768,
  "deviceScaleFactor": 1,
  "format": "png",
  "capturedAt": "2026-10-31T23:37:45.140Z",
  "status": "ok",
  "finalUrl": "https://news.ycombinator.com/",
  "httpStatus": 200,
  "pageTitle": "Hacker News",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/TSLhu0NwlcZrpaIs3/records/news-ycombinator-com-laptop-ee29656e.png",
  "screenshotKey": "news-ycombinator-com-laptop-ee29656e.png",
  "imageWidth": 1366,
  "imageHeight": 1215,
  "fileSizeBytes": 231237,
  "loadTimeMs": 460,
  "truncated": false,
  "loadComplete": true,
  "changePercent": 1.192,
  "changed": true,
  "previousCapturedAt": "2026-10-31T23:36:03.965Z",
  "diffImageUrl": "https://api.apify.com/v2/key-value-stores/TSLhu0NwlcZrpaIs3/records/news-ycombinator-com-laptop-ee29656e-diff.png",
  "errorMessage": null
}
```

A failed capture has `status: "failed"` and an `errorMessage` such as *"Domain could not be resolved (DNS lookup failed). Check the URL."* The run itself still succeeds, so one bad URL never breaks a batch. A run summary is saved to the `OUTPUT` record of the key-value store.

### Pricing

Pay per event, with no monthly fee:

- **$0.002 per screenshot** ($2 per 1,000). Each URL × viewport counts as one screenshot. Change detection and diff images are included.
- **$0.0025 per run start per GB of memory**, so $0.005 with the default 2 GB.
- **Free:** failed captures, pages that return an HTTP error (4xx/5xx), and captures skipped because of your cost limit.

Example: 100 pages in desktop and mobile = 200 screenshots = **$0.405** with default memory. Apify platform compute is included in these prices. Set *Maximum cost per run* in the run options and the Actor stops capturing when it would exceed it.

### Use cases

- **Website change monitoring:** watch competitor pricing pages, terms of service, landing pages or your own site for unexpected changes.
- **Responsive design QA:** see every page on desktop, tablet and phone side by side.
- **Visual regression testing** after deployments (run it from CI through the API).
- **Archiving and compliance:** timestamped full-page PNGs or PDFs of pages.
- **Thumbnails** for directories, link previews and reports.
- **AI agents:** give an agent a visual of a page through Apify's MCP server.

### FAQ

**Where are the image files?** In the run's default key-value store. `screenshotUrl` is a direct link you can open, download or embed.

**How does change detection work?** Every PNG is compared with the baseline from the previous run with the same URL, viewport and capture settings. The baseline lives in a named key-value store in your account (`monitorStoreName`) and is replaced after every comparison. Antialiasing differences are ignored. If the page height changed, the extra area counts as changed.

**Changes keep showing because of ads, carousels or chat widgets.** Add their CSS selectors to *Hide elements*, raise *Change threshold*, or capture only the relevant part with *Capture only this element*.

**Can it log in, solve CAPTCHAs or get past bot protection?** No. The Actor opens public pages like a normal browser, with no logins, stealth plugins or proxies. Sites that block automated browsers return an error page or fail, and you aren't charged for those.

**Why is a mobile screenshot wider than 390 px?** Either *High-DPI* is on (2× density, so a 390 px viewport makes a 780 px image), or the page has no mobile viewport meta tag and is laid out at 980 px, as in a real mobile browser. Full-page captures also include content wider than the viewport.

**A page loads forever. Will the run hang?** No. After the navigation timeout (default 30 s) the page is captured as rendered so far and the row gets `loadComplete: false`. Pages that don't open at all are retried once, then reported as failed.

**Do baselines cost anything?** Baseline PNGs are kept in one named key-value store in your account (one file per URL × viewport) and replaced on every run, so storage stays small.

**Is there a limit on page length?** Full-page captures are cut at `maxFullPageHeight` (default 10,000 CSS px, max 16,000), and such rows have `truncated: true`. This protects you from huge images on infinite-scroll pages.

**Can I call it from code?** Yes. Use the Apify API or the `apify-client` package for JavaScript or Python, and read the results from the dataset.

### Limits

- Public http(s) pages only. Local and private network addresses are rejected.
- PDF output uses one A4 render per URL (no device emulation) and ignores the element selector.
- Change detection requires PNG format.
- The Actor captures about one page in parallel per GB of memory (2 GB default = 2 pages). For big batches, raise the memory to go faster.

Questions or a missing feature? Open an issue on the Actor's Issues tab. Issues are answered there.

# Actor input Schema

## `urls` (type: `array`):

Public web pages to capture, one per line. A missing scheme gets https:// added. Duplicates are removed. Only http(s) URLs on public hosts are allowed.

## `viewports` (type: `array`):

Each URL is captured once per selected viewport. desktop = 1920×1080, laptop = 1366×768, tablet = 768×1024 (touch, tablet user agent), mobile = 390×844 (iPhone-like, touch, mobile user agent). Each capture is billed as one screenshot.

## `highDpi` (type: `boolean`):

Render tablet and mobile at 2× pixel density, like real devices: sharper images, but 4× the pixels, larger files and slower runs.

## `fullPage` (type: `boolean`):

Capture the whole scrollable page, not only the visible viewport. Very long pages are cut at 'Max full-page height'.

## `maxFullPageHeight` (type: `integer`):

Safety limit for full-page captures, to avoid giant images from infinite-scroll pages.

## `selector` (type: `string`):

Optional. Capture only the first element that matches this CSS selector, for example `#pricing` or `header`. If no element matches, the capture fails for that URL with a clear message, and you aren't charged.

## `format` (type: `string`):

PNG is lossless and needed for change detection. JPEG (quality 80) makes much smaller files. PDF renders a printable A4 document (no viewport emulation; the selector option is ignored).

## `waitUntil` (type: `string`):

When the page counts as loaded. 'load' suits most sites. 'networkidle' waits for background requests to stop (slower; can time out on sites that never stop requesting). 'domcontentloaded' is fastest.

## `delayMs` (type: `integer`):

Extra time to wait after the page has loaded, for animations, fonts and late content.

## `waitForSelector` (type: `string`):

Optional. Wait until an element that matches this selector is visible before capturing. It's skipped with a warning if the element doesn't appear within the navigation timeout.

## `scrollToBottom` (type: `boolean`):

Scroll down the page step by step before capturing, so lazy-loaded images and sections render. Recommended for full-page captures of long pages.

## `hideCookieBanners` (type: `boolean`):

Hide common cookie-consent pop-ups (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast, Osano and others) with CSS so they don't cover the content. Nothing is clicked or accepted.

## `hideSelectors` (type: `array`):

Optional CSS selectors of elements to hide before capturing, for example chat widgets, ads or rotating banners. Useful to reduce noise in change detection.

## `darkMode` (type: `boolean`):

Emulate prefers-color-scheme: dark, for sites that support a dark theme.

## `compareWithPrevious` (type: `boolean`):

Compare each PNG screenshot pixel by pixel with the one from the previous run with the same URL, viewport and settings. Output gets changePercent, changed and a highlighted diff image. The first run only stores the baseline. Requires PNG format. Run it on a schedule to monitor pages.

## `monitorStoreName` (type: `string`):

Name of the key-value store in your account that keeps the baseline screenshots between runs. Use different names to keep separate monitoring projects apart. Allowed: letters, digits and '-'.

## `changeThresholdPercent` (type: `number`):

Minimum share of changed pixels (in %) for a capture to be marked changed = true. 0.1 catches small text edits on a typical page. Raise it (for example to 1–5) to ignore noise such as rotating ads or carousels.

## `navigationTimeoutSecs` (type: `integer`):

Maximum time to wait for each page. If the page doesn't reach the 'wait until' event in time, it's captured as rendered so far (loadComplete = false) and isn't dropped. Pages that fail to open are retried once.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://en.wikipedia.org/wiki/Web_scraping"
  ],
  "viewports": [
    "desktop",
    "mobile"
  ],
  "highDpi": false,
  "fullPage": true,
  "maxFullPageHeight": 10000,
  "format": "png",
  "waitUntil": "load",
  "delayMs": 1000,
  "scrollToBottom": false,
  "hideCookieBanners": true,
  "darkMode": false,
  "compareWithPrevious": false,
  "monitorStoreName": "screenshot-monitor-baselines",
  "changeThresholdPercent": 0.1,
  "navigationTimeoutSecs": 30
}
```

# Actor output Schema

## `screenshots` (type: `string`):

One row per URL × viewport with the screenshot link, metadata and change detection results.

## `files` (type: `string`):

Screenshot files, diff images and the OUTPUT run summary.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://apify.com",
        "https://en.wikipedia.org/wiki/Web_scraping"
    ],
    "viewports": [
        "desktop",
        "mobile"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jakob_jerich/website-screenshot-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://apify.com",
        "https://en.wikipedia.org/wiki/Web_scraping",
    ],
    "viewports": [
        "desktop",
        "mobile",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("jakob_jerich/website-screenshot-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://apify.com",
    "https://en.wikipedia.org/wiki/Web_scraping"
  ],
  "viewports": [
    "desktop",
    "mobile"
  ]
}' |
apify call jakob_jerich/website-screenshot-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jakob_jerich/website-screenshot-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/T9XoUxkFeYQzlzHh1/builds/7cAuidvC04iY7L8pl/openapi.json
