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

Monitor any web page(s) for content changes. Fetch, fingerprint, diff against the last run; report what changed with a preview. No API key.

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

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Website Change Monitor

**Watch any web page for changes.** Give it one or more URLs; each run fetches
them, builds a stable content fingerprint, and compares it with the fingerprint
saved from the **previous run**. You get a row per page saying whether it
changed, by how much, and a short diff preview — **RSS for pages that don't have
a feed**.

### What does Website Change Monitor do?

- Fetches each URL and extracts its **main content** — nav, header, footer,
  sidebars, cookie/consent banners, ads and forms are stripped, and a
  `<main>` / `<article>` / `#content` container is preferred — so the monitor
  isn't tripped by boilerplate. A **CSS selector** overrides this.
- Fingerprints the content (SHA-256) and stores it in a persistent key-value
  store, keyed per URL.
- On the next run, compares fresh content to the stored baseline and emits a
  **structured change report**: `changed_sections` (added / removed blocks),
  a `word_diff` (`[-removed-]` `{+added+}`), `similarity_score`,
  `content_length_before` / `after`, and `significant_change` (true when more
  than 5 % of the content changed).
- No API key, no browser, deterministic. Schedule it on Apify (hourly, daily…)
  and wire the output into Slack, email, webhooks or Zapier via Apify
  integrations.

### Why use it?

- **Competitor & market watch** — pricing pages, feature lists, product pages.
- **Compliance & legal** — terms of service, privacy policies, regulatory pages.
- **Jobs & opportunities** — a careers page, a grants list, a tender board.
- **Ops** — status pages, release notes, documentation, changelogs.
- **Research** — track how any public page evolves over time.

### How to use it

1. Add one or more **URLs**.
2. *(Optional)* set a **CSS selector** to compare only that region, and
   **Ignore patterns** (regex) to strip volatile bits like timestamps or view
   counts.
3. Run it once to store the baseline (every page comes back `first_seen: true`).
4. **Schedule** the Actor. From then on, each run flags what changed.
5. *(Optional)* turn on **Only output changed pages** so the dataset stays a
   clean change-log.

### Input

| field | notes |
|---|---|
| `urls` | list of page URLs to monitor (or `url` for a single one) |
| `selector` | CSS selector to isolate a region — empty = whole page body |
| `mode` | `text` (visible text, ignores markup churn) or `html` (raw HTML) |
| `ignorePatterns` | regexes stripped before comparing (timestamps, tokens, ad slots…) |
| `notifyOnly` | output a row only for changed / first-seen / errored pages |
| `stateKey` | name of the fingerprint store — use different names for independent monitors |

```json
{
  "urls": ["https://apify.com/pricing"],
  "selector": "main",
  "ignorePatterns": ["\\d{4}-\\d{2}-\\d{2}"],
  "notifyOnly": true
}
```

### Output

One row per URL:

| field | meaning |
|---|---|
| `url`, `checked_at`, `http_status`, `status` | request outcome (`ok` / `error`) |
| `changed` | `true` if content differs from the last run |
| `significant_change` | `true` when more than 5 % of the content changed |
| `first_seen` | `true` on the first run for this URL (baseline stored) |
| `similarity_score` | 0–1 similarity between old and new content |
| `content_length_before`, `content_length_after` | main-content size, chars |
| `changed_sections` | array of `{ type: "added" \| "removed", content }` blocks |
| `word_diff` | inline diff string: `[-removed-]` `{+added+}` |
| `current_hash`, `previous_hash`, `previous_checked_at` | fingerprints |
| `error` | message when the fetch or selector failed |

```json
{
  "url": "https://example.com/pricing",
  "changed": true,
  "significant_change": true,
  "similarity_score": 0.83,
  "content_length_before": 4120,
  "content_length_after": 4460,
  "changed_sections": [
    { "type": "removed", "content": "Starter — $29 / month" },
    { "type": "added", "content": "Starter — $39 / month" }
  ],
  "word_diff": "Starter — $[-29-]{+39+} / month"
}
```

A run summary (`checked`, `changed`, `significant_changes`, `first_seen`,
`errors`, `pushed`) is written to the key-value store as `OUTPUT`. Download the
dataset as JSON, CSV, Excel or XML.

### Pricing

Pay-per-event: **`url-checked`** once per URL fetched, plus **`change-detected`**
once per URL with a *significant* change. Each check is one lightweight HTTP
request plus a little text processing — a monitor over a handful of URLs runs in
seconds. Cost scales with the number of URLs and how often you schedule it.

### Tips

- Use a **selector** — page-wide monitoring trips on cookie banners, CSRF
  tokens and rotating ads. Watch `.pricing`, `#content`, `main`.
- Add **ignore patterns** for anything time-based (`\d{2}:\d{2}`, `"lastModified":\s*\d+`).
- Keep separate **`stateKey`** namespaces for unrelated monitors so a schedule
  change doesn't reset everything.
- JavaScript-rendered pages: this Actor reads the served HTML, so content that
  only appears after client-side rendering won't be seen.

### FAQ & disclaimers

The Actor performs plain GET requests with a descriptive User-Agent and stores
only content fingerprints and a text snippet for diffing — no personal data.
Respect the target site's Terms of Service and robots policy, and pick a sane
schedule. Bug or feature request? Use the Actor's **Issues** tab.

# Actor input Schema

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

One or more page URLs. Each is fetched and compared with its fingerprint from the previous run.

## `mainContentOnly` (type: `boolean`):

Strip nav, header, footer, sidebars, cookie/consent banners, ads and forms before comparing, and prefer a <main> / <article> / #content container. Turn off to compare the whole body.

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

Optional. Compare only the part of the page matching this selector (e.g. main, #content, .price-table). Overrides 'Main content only'.

## `mode` (type: `string`):

'text' = visible text only (ignores markup churn). 'html' = raw HTML of the selection.

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

Regular expressions stripped from the content before comparing — use for timestamps, CSRF tokens, view counts, ad slots, etc.

## `notifyOnly` (type: `boolean`):

If on, the dataset gets a row only for pages that changed, were seen for the first time, or errored.

## `stateKey` (type: `string`):

Name of the key-value store holding the fingerprints. Use different names to run independent monitors that don't share baselines.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com/pricing"
  ],
  "mainContentOnly": true,
  "mode": "text",
  "ignorePatterns": [],
  "notifyOnly": false,
  "stateKey": "website-change-monitor"
}
```

# Actor output Schema

## `results` (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://apify.com/pricing"
    ],
    "ignorePatterns": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("cordate_jebel/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://apify.com/pricing"],
    "ignorePatterns": [],
}

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

```

## MCP server setup

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