# Web Page Change Monitor (`dramatic_jonquil/web-page-change-monitor`) Actor

Monitor any web page for meaningful changes. Watch full pages or specific CSS selectors, filter noisy timestamps and tokens, and get diffs plus optional webhook alerts when content actually changes.

- **URL**: https://apify.com/dramatic\_jonquil/web-page-change-monitor.md
- **Developed by:** [Alex Mercer](https://apify.com/dramatic_jonquil) (community)
- **Categories:** Automation, Developer tools, SEO tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 url changeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Web Page Change Monitor

Monitor any web page for changes. Get notified the moment prices, terms, or content shift.

### Who it's for

- SEO agencies watching competitor pages and SERP-adjacent content.
- Procurement teams tracking pricing, terms, and vendor policy changes.
- Competitive intelligence teams monitoring launch pages and changelogs.
- Product managers following release notes, docs, and policy updates.

### Why this Actor

- Selector-scoped monitoring lets you watch only the part of the page that matters.
- Noise filters strip dates, ads, rotating tokens, and other false-positive bait before diffing.
- Usage-based pricing is a better fit than flat SaaS plans for bursty or large monitoring fleets.
- Native Apify integrations mean dataset exports, webhooks, Make, Zapier, and n8n work immediately.

### Use cases

#### 1. Track a competitor pricing page

Watch only the pricing table instead of the whole page.

```json
{
  "urls": [
    {
      "url": "https://apify.com/pricing",
      "userData": {
        "selector": ".PricingHeader-plans"
      }
    }
  ],
  "renderJs": true,
  "minChangedChars": 5
}
````

#### 2. Monitor a SaaS changelog

Focus on the newest changelog entry and ignore everything else.

```json
{
  "urls": [
    {
      "url": "https://github.blog/changelog/",
      "userData": {
        "selector": "main article:first-of-type"
      }
    }
  ],
  "renderJs": true,
  "notifyOnNoChange": true
}
```

#### 3. Watch a government policy page

Monitor the full page while ignoring date stamps that update every publish.

```json
{
  "urls": [
    {
      "url": "https://www.gsa.gov/website-information/website-policies"
    }
  ],
  "renderJs": false,
  "ignorePatterns": [
    "\\\\d{4}-\\\\d{2}-\\\\d{2}"
  ]
}
```

### Input reference

#### `urls`

One or more pages to monitor. Each entry can be a plain string or a full Apify request object. Use `userData.selector` to override the default selector per URL.

```json
[
  {
    "url": "https://example.com"
  },
  {
    "url": "https://example.com/pricing",
    "userData": {
      "selector": ".pricing-table"
    }
  }
]
```

#### `selector`

Default CSS selector for all URLs unless overridden per request.

Example: `"selector": "main article:first-child"`

#### `ignorePatterns`

Regex strings applied line-by-line after extraction and before diffing.

Example:

```json
{
  "ignorePatterns": [
    "\\\\d{4}-\\\\d{2}-\\\\d{2}",
    "Last updated: .*",
    "Ad served at .*"
  ]
}
```

#### `renderJs`

Enable browser rendering for JavaScript-heavy pages. This is the default.

Example: `"renderJs": true`

Cost note: JS rendering is roughly 10x more expensive than static checks. Use `"renderJs": false` for simple HTML pages whenever possible.

#### `waitForSelector`

Wait until a selector appears before extracting page content.

Example: `"waitForSelector": ".loaded-state"`

#### `webhookUrl`

Optional URL that receives a JSON payload for every detected change.

Example: `"webhookUrl": "https://webhook.site/your-token"`

#### `notifyOnNoChange`

Emit dataset rows for unchanged pages too. Helpful for audit trails and heartbeat monitoring.

Example: `"notifyOnNoChange": true`

#### `minChangedChars`

Minimum number of changed characters required before a diff is treated as a real change.

Example: `"minChangedChars": 25`

#### `maxConcurrency`

Optional per-run concurrency override. If omitted, the Actor uses `APIFY_CONCURRENCY` when set, otherwise `5`.

Example: `"maxConcurrency": 10`

#### `maxRequestRetries`

Optional retry count for transient network failures before a page is marked as `error`.

Example: `"maxRequestRetries": 2`

### Output reference

Dataset rows use this shape:

```json
{
  "url": "https://example.com/pricing",
  "selector": ".pricing-table",
  "checkedAt": "2026-04-17T17:05:18.123Z",
  "status": "changed",
  "changedChars": 42,
  "diff": "*** content\\n--- before\\n+++ after\\n@@ -1 +1 @@\\n-Price: $49\\n+Price: $59\\n",
  "before": "Starter\\nPrice: $49",
  "after": "Starter\\nPrice: $59",
  "errorMessage": null,
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/xxxx/records/snapshot-abc-change-2026-04-17T17-05-18-123Z.png"
}
```

Status meanings:

- `initial`: first snapshot stored for that URL and selector pair.
- `changed`: content changed and exceeded `minChangedChars`.
- `unchanged`: content did not change, or changed below threshold when `notifyOnNoChange` is enabled.
- `error`: the page could not be fetched or the selector did not match.

Large `before`, `after`, and `diff` fields are truncated before being written to the dataset so oversized pages do not blow up row size limits.
If a `diff` field is truncated, treat it as an inspection aid rather than a patch you can apply directly, because truncation can cut through a unified-diff hunk.

### Webhook payload

Webhook payloads are identical to dataset rows and are sent only for `changed` events.

```json
{
  "url": "https://example.com/changelog",
  "selector": "main article:first-child",
  "checkedAt": "2026-04-17T17:07:44.000Z",
  "status": "changed",
  "changedChars": 128,
  "diff": "*** content\\n--- before\\n+++ after\\n@@ -1,2 +1,2 @@\\n-Added feature A\\n+Added feature B\\n",
  "before": "Release 1.0\\nAdded feature A",
  "after": "Release 1.1\\nAdded feature B",
  "errorMessage": null,
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/xxxx/records/snapshot-def-change-2026-04-17T17-07-44-000Z.png"
}
```

### Pricing note

Store billing is usage-based through Apify events. In the current default setup, users are billed for Actor starts and for rows written to the default dataset.

Expected platform usage:

- `1 compute unit per check` with JavaScript rendering enabled.
- `0.1 compute unit per check` for static HTML checks.

This Actor is a strong fit for teams that need occasional bursts, hundreds of URLs, or selector-specific monitoring without committing to a monthly seat-based tool.

### Local development

Install dependencies:

```bash
npm install
```

Run tests:

```bash
npm test
```

Run locally with Apify storage:

```bash
apify run --purge
```

### Known limitations

- Infinite-scroll pages are not supported in v1.
- Auth-walled pages are not supported yet.
- CAPTCHA-walled pages are intentionally not supported.
- This version monitors text and text-derived structural changes, not pixel diffs.

# Actor input Schema

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

One or more URLs. Each can optionally specify a CSS selector via userData.selector.

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

If set, only text inside this selector is monitored. Per-URL selectors in userData override.

## `ignorePatterns` (type: `array`):

e.g. \d{4}-\d{2}-\d{2} to ignore dates. Applied line-by-line.

## `renderJs` (type: `boolean`):

Disable for static HTML — 10x faster and cheaper.

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

Optional CSS selector to wait for before extracting content.

## `webhookUrl` (type: `string`):

POSTed a JSON payload on every detected change.

## `notifyOnNoChange` (type: `boolean`):

When enabled, unchanged pages also emit dataset rows.

## `minChangedChars` (type: `integer`):

Small diffs below this threshold are treated as unchanged.

## `maxConcurrency` (type: `integer`):

How many URLs can be checked in parallel. Falls back to APIFY\_CONCURRENCY or 5.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before emitting an error row.

## Actor input object example

```json
{
  "ignorePatterns": [],
  "renderJs": true,
  "notifyOnNoChange": false,
  "minChangedChars": 1,
  "maxRequestRetries": 0
}
```

# Actor output Schema

## `results` (type: `string`):

Rows from the default dataset describing each monitored page check, including status, changed character count, normalized content, unified diff text, and screenshot URL for changed pages.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("dramatic_jonquil/web-page-change-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("dramatic_jonquil/web-page-change-monitor").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call dramatic_jonquil/web-page-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=dramatic_jonquil/web-page-change-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Web Page Change Monitor",
        "description": "Monitor any web page for meaningful changes. Watch full pages or specific CSS selectors, filter noisy timestamps and tokens, and get diffs plus optional webhook alerts when content actually changes.",
        "version": "0.1",
        "x-build-id": "l4guK9nzmLuWjV4f4"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/dramatic_jonquil~web-page-change-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-dramatic_jonquil-web-page-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/dramatic_jonquil~web-page-change-monitor/runs": {
            "post": {
                "operationId": "runs-sync-dramatic_jonquil-web-page-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/dramatic_jonquil~web-page-change-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-dramatic_jonquil-web-page-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "urls"
                ],
                "properties": {
                    "urls": {
                        "title": "URLs to monitor",
                        "type": "array",
                        "description": "One or more URLs. Each can optionally specify a CSS selector via userData.selector.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "selector": {
                        "title": "Default CSS selector",
                        "type": "string",
                        "description": "If set, only text inside this selector is monitored. Per-URL selectors in userData override."
                    },
                    "ignorePatterns": {
                        "title": "Regex patterns to strip before diffing",
                        "type": "array",
                        "description": "e.g. \\d{4}-\\d{2}-\\d{2} to ignore dates. Applied line-by-line.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "renderJs": {
                        "title": "Render JavaScript (headless browser)",
                        "type": "boolean",
                        "description": "Disable for static HTML — 10x faster and cheaper.",
                        "default": true
                    },
                    "waitForSelector": {
                        "title": "Wait for selector before capture",
                        "type": "string",
                        "description": "Optional CSS selector to wait for before extracting content."
                    },
                    "webhookUrl": {
                        "title": "Webhook URL for change notifications",
                        "type": "string",
                        "description": "POSTed a JSON payload on every detected change."
                    },
                    "notifyOnNoChange": {
                        "title": "Emit dataset item even when unchanged",
                        "type": "boolean",
                        "description": "When enabled, unchanged pages also emit dataset rows.",
                        "default": false
                    },
                    "minChangedChars": {
                        "title": "Minimum changed characters to count as a change",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Small diffs below this threshold are treated as unchanged.",
                        "default": 1
                    },
                    "maxConcurrency": {
                        "title": "Maximum concurrency",
                        "minimum": 1,
                        "type": "integer",
                        "description": "How many URLs can be checked in parallel. Falls back to APIFY_CONCURRENCY or 5."
                    },
                    "maxRequestRetries": {
                        "title": "Request retries",
                        "minimum": 0,
                        "type": "integer",
                        "description": "How many times to retry a failed request before emitting an error row.",
                        "default": 0
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
