# Website Change Monitor — Detect Page Diffs on Schedule (`evanatlasx/website-change-monitor`) Actor

Detect content changes on any public webpage. Cheerio HTTP checks (no browser), persistent SHA-256 snapshots, optional CSS selectors. Outputs new/changed/unchanged/error. Pay only for pages checked and changes detected — failed URLs are free.

- **URL**: https://apify.com/evanatlasx/website-change-monitor.md
- **Developed by:** [evanatlasx](https://apify.com/evanatlasx) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 16.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 page checkeds

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 Change Monitor

Detect content changes on any public webpage. The Actor fetches each URL with a lightweight HTTP + Cheerio parser (no browser), hashes the visible text (or a CSS-selected region), and compares it to the previous run's snapshot. Results are written to the dataset as `new`, `changed`, `unchanged`, or `error` rows. Snapshots live in a named Key-Value Store so they persist across scheduled runs.

Ideal for price-page monitoring, competitor landing pages, terms-of-service diffs, status pages, and any text that should not change silently.

### Features

- **Cheap HTTP checks** — Cheerio only; no Playwright/Puppeteer browser cost.
- **Persistent snapshots** — Named KV store (default `website-change-monitor-snapshots`) survives across runs and schedules.
- **CSS targeting** — Optional `selector` and `ignoreSelectors` to focus on the region that matters.
- **Resilient** — Per-URL failures are recorded as `error` rows; the run still SUCCEEDS.
- **Pay per event** — Charged only for successful `page-checked` and `change-detected` events (plus a tiny Actor start fee). Errors are free.

### Input

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `urls` | string\[] | yes | Public `http(s)` URLs to check (max 100; truncated by `maxUrls`). |
| `selector` | string | no | CSS selector for the region to hash. Empty = whole `<body>`. |
| `ignoreSelectors` | string\[] | no | CSS selectors to strip before hashing (ads, timestamps, cookie banners). |
| `userAgent` | string | no | Custom User-Agent. |
| `maxUrls` | integer | no | Cap on URLs processed (default 50, max 100). |
| `timeoutSecs` | integer | no | Per-URL timeout in seconds (default 30). |
| `followRedirects` | boolean | no | Follow HTTP redirects (default true). |
| `saveHtml` | boolean | no | Save previous + current HTML to the snapshot store and include keys in the dataset (default false). |
| `snapshotStoreName` | string | no | Named KV store for snapshots (default `website-change-monitor-snapshots`). Use a different name per project if you want isolated histories. |

#### Example input

```json
{
  "urls": [
    "https://example.com",
    "https://httpbin.org/html"
  ],
  "selector": "body",
  "ignoreSelectors": [".cookie-banner", "time"],
  "maxUrls": 10,
  "timeoutSecs": 30,
  "followRedirects": true,
  "saveHtml": false,
  "snapshotStoreName": "website-change-monitor-snapshots"
}
```

### Output

Each dataset item:

| Field | Meaning |
|-------|---------|
| `url` | Input URL |
| `finalUrl` | URL after redirects |
| `changeType` | `new` | `changed` | `unchanged` | `error` |
| `changed` | `true` only when `changeType` is `changed` |
| `statusCode` | HTTP status |
| `checkedAt` | ISO timestamp |
| `contentHash` | SHA-256 of normalized text |
| `previousHash` | Prior hash (null on first sight) |
| `textSnippet` | First ~280 chars of extracted text |
| `error` | Error message when `changeType` is `error` |
| `httpElapsedMs` | Request duration |
| `selectorUsed` | Selector that was applied |
| `htmlKey` / `previousHtmlKey` | Present only when `saveHtml` is true |

### Scheduling tip

Create an Apify Schedule that runs this Actor every hour/day with the same `urls` and the same `snapshotStoreName`. The first run baselines every page as `new`; later runs report `changed` or `unchanged`.

### Pricing (pay-per-event)

- **Actor start** — small fixed fee per run
- **page-checked** — each URL that returned a successful check (`new` / `changed` / `unchanged`)
- **change-detected** — each URL where `changed === true`

Failed URLs (`error`) are not charged as page-checked or change-detected.

### Limitations

- Does not execute JavaScript — content rendered only by client-side JS will not appear.
- Bot-protected / login-walled pages may return errors or empty content.
- Very large pages are truncated in the text snippet; the hash still covers the extracted region.

### Support

Questions or feature requests: open an issue on the Actor page or email the publisher listed on the Store.

# Actor input Schema

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

Public http(s) pages to check. Each page is compared with its snapshot from the previous run (stored in the default key-value store). Duplicates are checked once.

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

If set, only the plain text of elements matching this CSS selector is hashed (e.g. "main", "#price", ".article h1"). Leave empty to hash the cleaned text of the whole <body>.

## `ignoreSelectors` (type: `array`):

CSS selectors removed before hashing, to ignore noisy parts such as nav, footer, .ads, .cookie-banner. <script>, <style> and <noscript> are always ignored.

## `userAgent` (type: `string`):

Optional custom User-Agent header. Defaults to a descriptive WebsiteChangeMonitor UA.

## `maxUrls` (type: `integer`):

Maximum number of URLs checked per run (after de-duplication).

## `timeoutSecs` (type: `integer`):

Timeout for a single HTTP request (including redirects and body download).

## `followRedirects` (type: `boolean`):

Follow HTTP 3xx redirects (up to 10). If off, a redirect is reported as an error row.

## `saveHtml` (type: `boolean`):

Store the fetched HTML in the default key-value store (key html-<contentHash>) and put the reference into the htmlKey output field.

## `snapshotStoreName` (type: `string`):

Named Apify Key-Value Store used to persist page snapshots across runs. Change this only if you want separate snapshot histories (e.g. per project). Defaults to website-change-monitor-snapshots.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://example.org"
  ],
  "maxUrls": 50,
  "timeoutSecs": 30,
  "followRedirects": true,
  "saveHtml": false,
  "snapshotStoreName": "website-change-monitor-snapshots"
}
```

# Actor output Schema

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

One row per URL with changeType new / changed / unchanged / error.

## `snapshots` (type: `string`):

Named key-value store holding snapshot-\<sha1(url)> records and, if saveHtml is on, html-<sha256> pages. Open it from Storage → Key-value stores using the store name from input.

# 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://example.com",
        "https://example.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("evanatlasx/website-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 = { "urls": [
        "https://example.com",
        "https://example.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("evanatlasx/website-change-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://example.com",
    "https://example.org"
  ]
}' |
apify call evanatlasx/website-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,evanatlasx/website-change-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/bw72kDKq4obSnGROE/builds/y6biLVDmDDAczhlu0/openapi.json
