# Website Change Monitor (`pinkish_gallop/website-change-monitor`) Actor

Monitor web pages for content changes. Hashes the visible text (or a CSS-selected part), compares with the previous run and returns changed/unchanged plus a word-level diff. No browser, $1 per 1,000 checks.

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

## Pricing

from $1.00 / 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

Watch any list of web pages and find out **what changed since the last run**. The Actor fetches each page, keeps only the visible text (optionally just the part matched by a CSS selector), hashes it and compares it with the baseline saved by the previous run. Changed pages come with a compact **word-level diff**, so you can see exactly what was added or removed.

It uses plain HTTP requests, not a headless browser, so a check takes well under a second and costs a fraction of a cent. That makes it cheap to run on a schedule every hour or every day.

### Use cases

- Competitor pricing pages, product pages and changelogs
- Terms of service, privacy policies and legal pages
- Job boards, "careers" pages, tender and grant listings
- Documentation and release notes (e.g. "is there a new version?")
- Government notices, regulations and public registries

### How it works

1. For each URL the Actor downloads the HTML (redirects followed), removes scripts, styles, SVGs and iframes, and collapses whitespace.
2. If you gave a CSS selector, only the text inside it is used. That way ads, timestamps and "related posts" don't trigger false alarms.
3. The text is hashed (SHA-256) and compared with the baseline stored in a **named key-value store** (default `change-monitor-baselines`). Named stores persist between runs, so schedules just work.
4. The first run creates the baseline (`changed: null`). Later runs report `changed: true` or `false`, plus a `diff` when something changed.

Use a different `baselineStoreName` for each independent monitor (for example `pricing-pages` and `legal-pages`).

### Input

```json
{
  "urls": [
    "https://www.python.org/downloads/",
    { "url": "https://news.ycombinator.com/", "selector": "#hnmain .titleline", "label": "HN titles" }
  ],
  "includeDiff": true,
  "onlyChanges": false,
  "baselineStoreName": "change-monitor-baselines"
}
```

| Field | Description |
|---|---|
| `urls` | Required. URLs/domains, or objects `{url, selector, label}` |
| `selector` | Default CSS selector for URLs without their own |
| `includeDiff` | Store text snapshots and output a word-level diff on change (default `true`) |
| `onlyChanges` | Only push changed pages (and errors) to the dataset, which is ideal for alerts |
| `baselineStoreName` | Named key-value store holding baselines |
| `updateBaseline` | Overwrite baseline after a successful check (default `true`) |
| `waitMs`, `timeoutMs`, `maxConcurrency` | Politeness and performance settings |

### Output

One dataset row per page:

```json
{
  "url": "https://news.ycombinator.com/",
  "label": "HN titles",
  "selector": "#hnmain .titleline",
  "changed": true,
  "diff": "… Show HN: -Old +New title …",
  "hash": "3f1c…",
  "previousHash": "a9b2…",
  "statusCode": 200,
  "textLen": 2841,
  "textPreview": "…",
  "warning": null,
  "error": null,
  "fetchedAt": "2026-10-04T10:00:00.000Z"
}
```

The `OUTPUT` record in the default key-value store has a run summary: totals, the list of `changedUrls` and how many pages were charged.

`warning: "selector_not_found:…"` means the selector matched nothing (the page layout may have changed); such rows are free and don't update the baseline. `error` is set for timeouts, DNS failures and HTTP status ≥ 400. In those cases the baseline is not touched and you aren't charged.

### Alerts

Schedule the Actor (Console → Schedules), turn on `onlyChanges`, and add an integration: Slack, email, Zapier, Make or a webhook on *Run succeeded*. Since the dataset then only holds changes, your alert fires with exactly the changed pages.

### Pricing

Pay per event:

- **$0.001 per page checked** (= $1 per 1,000 checks). Failed fetches, timeouts and HTTP errors are free.
- Plus Apify's tiny actor start fee ($0.00005).

Example: 20 pages checked every hour comes to about 14,400 checks a month, or about $14.40.

### Limitations

- Pages that render their content with JavaScript only (empty HTML shell) cannot be monitored by text. Use [Website Screenshot Pro](https://apify.com/pinkish_gallop/website-screenshot-pro), which has a visual-diff mode, for those.
- Content that changes on every request (CSRF tokens in text, live counters, rotating testimonials) will look like a change. Use a CSS selector to scope the check.
- Please respect each site's terms and don't schedule more often than you need.

### Related Actors

- [Website Screenshot Pro](https://apify.com/pinkish_gallop/website-screenshot-pro): screenshots, PDFs and visual (pixel) change detection
- [Website Contact & Tech Enricher](https://apify.com/pinkish_gallop/website-contact-enricher): emails, phones, socials and tech stack from websites

# Actor input Schema

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

Pages to check. Plain URLs/domains, or objects {"url": ..., "selector": "CSS selector", "label": "name"} to watch only one part of a page.

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

Optional. Applied when a URL entry has no per-URL selector. Hash is computed from text inside this region.

## `waitMs` (type: `integer`):

Polite delay before each request.

## `timeoutMs` (type: `integer`):

Abort a single fetch after this many milliseconds.

## `baselineStoreName` (type: `string`):

Named key-value store that keeps previous content hashes. Use different names for separate monitors.

## `updateBaseline` (type: `boolean`):

When true, successful hashes overwrite the baseline for the next run.

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

How many pages to fetch in parallel.

## `includeDiff` (type: `boolean`):

When true, store text snapshots in the baseline KV and emit a compact word-level diff on changes.

## `onlyChanges` (type: `boolean`):

When true, the dataset only contains pages whose content changed (plus errors). Handy for webhooks/alerts. Every successfully checked page is still billed as one check.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/downloads/",
    {
      "url": "https://news.ycombinator.com/",
      "selector": "#hnmain .titleline",
      "label": "HN front page titles"
    }
  ],
  "waitMs": 0,
  "timeoutMs": 20000,
  "baselineStoreName": "change-monitor-baselines",
  "updateBaseline": true,
  "maxConcurrency": 3,
  "includeDiff": true,
  "onlyChanges": false
}
```

# Actor output Schema

## `checks` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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://www.python.org/downloads/",
        {
            "url": "https://news.ycombinator.com/",
            "selector": "#hnmain .titleline",
            "label": "HN front page titles"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pinkish_gallop/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://www.python.org/downloads/",
        {
            "url": "https://news.ycombinator.com/",
            "selector": "#hnmain .titleline",
            "label": "HN front page titles",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pinkish_gallop/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://www.python.org/downloads/",
    {
      "url": "https://news.ycombinator.com/",
      "selector": "#hnmain .titleline",
      "label": "HN front page titles"
    }
  ]
}' |
apify call pinkish_gallop/website-change-monitor --silent --output-dataset

```

## MCP server setup

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