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

Watch any web page and get told exactly what changed since the last run: the added lines, the removed lines and how much of the page moved. Narrow it to one region with a CSS selector, and ignore the clocks and counters that change on every load.

- **URL**: https://apify.com/arched\_friend/website-change-monitor.md
- **Developed by:** [Peach O](https://apify.com/arched_friend) (community)
- **Categories:** SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 37.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.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: Know Exactly What Changed on Any Page

Watch any page and get told what changed since the last run — the lines that appeared, the lines that disappeared, and how much of the page moved. Not "something changed": the actual text.

Built for people who need to hear about a competitor's price change, a policy update or a quietly edited claim on the day it happens.

### How it works

```mermaid
flowchart LR
    A["Pages to watch"] --> B["Fetch<br/>(optionally in a browser)"]
    B --> C["Narrow to your selector"]
    C --> D["Strip clocks, tokens,<br/>ignored regions"]
    D --> E["Hash and compare<br/>with last run"]
    E -->|different| F["changed + line diff"]
    E -->|same| G["unchanged"]
    E -->|selector gone| H["page redesigned"]
```

### It stays quiet unless something real happened

A monitor that cries wolf gets switched off, so this one works hard at silence:

- **Clocks, dates, CSRF tokens and cache busters are stripped** before comparing. A page carrying "Updated at 14:32:01" differs on every single load; that is not a change.
- **Whitespace is normalised**, so a template reflow that reindents every line is not a change.
- **Reordering is not a change.** The diff is set-based, so blocks moving around do not produce a wall of false differences.
- **`minChangePercent`** lets you ignore pages with constant small churn until something substantial moves.
- **`ignoreSelectors`** removes the ad slots, cookie banners and live counters before anything is compared.

### Built for

- **Competitor watching** — pricing pages, feature lists, positioning copy
- **Compliance and legal** — terms, privacy policies, regulatory pages
- **Procurement** — supplier price lists and stock pages
- **Engineering** — status pages, changelogs, API docs, a sitemap or a JSON endpoint

### Input

```json
{
  "urls": ["https://competitor.com/pricing"],
  "selector": ".pricing-table",
  "ignoreSelectors": [".cookie-banner", ".live-visitor-count"],
  "onlyChanged": true,
  "minChangePercent": 2
}
```

| Setting | What it does |
| --- | --- |
| `urls` | The pages to watch, each tracked separately |
| `selector` | Narrow the watch to one region, so the nav and footer are ignored |
| `ignoreSelectors` | Strip the parts that change on every load |
| `renderJavaScript` | Load in a real browser, for React and Vue pages |
| `ignoreNumbers` | Mask every digit — turn this **off** when watching a price |
| `minChangePercent` | Only report when this much of the page moved |
| `onlyChanged` | Return nothing on a quiet run |
| `timeBudgetSecs` | How long a run may spend checking before it returns what it has |

### Output

```json
{
  "url": "https://competitor.com/pricing",
  "changeType": "changed",
  "changed": true,
  "changePercent": 6.7,
  "addedLines": 1,
  "removedLines": 1,
  "summary": "1 line added, 1 line removed (6.7% of the page)\n+ Pro plan $49 / month\n- Pro plan $39 / month",
  "added": ["Pro plan $49 / month"],
  "removed": ["Pro plan $39 / month"],
  "checkedAt": "2026-09-17T15:54:02.881Z"
}
```

`summary` is written to be pasted straight into a Slack message or an email alert.

`changeType` is one of `changed`, `unchanged`, `first-run`, `below-threshold`, `selector-not-found` or `failed`.

### A selector that stops matching is itself a signal

If your CSS selector matches nothing, the run does not silently report "no change" — it returns `selector-not-found`. That almost always means the page was redesigned, which is exactly when you want to know.

### It is not limited to HTML

A JSON API response, an XML sitemap, `robots.txt` or a plain-text changelog all work. Anything fetchable over HTTP is compared as text, so you can watch an endpoint the same way you watch a page.

### Run it as an API

```bash
curl -X POST "https://api.apify.com/v2/acts/arched_friend~website-change-monitor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": ["https://competitor.com/pricing"],
    "selector": "main",
    "onlyChanged": true
  }'
```

### Pricing

You pay $0.003 for each page checked. A quiet run costs exactly the same as one that finds a change.

| Watching 10 pages, twice a day | Monthly cost |
| --- | --- |
| Checking by hand | roughly 10 hours |
| Most page-watching tools | $10 to $100 per month |
| This Actor | $1.80 per month |

### Common questions

**Why did the first run report no change?** There was nothing to compare against. The first run records the baseline and reports `first-run`; every run after that compares to it.

**The page uses React and comes back empty.** Turn on `renderJavaScript`. It loads the page in a real browser first, which is slower and costs more compute, but sees what a visitor sees.

**It is reporting changes I do not care about.** Narrow `selector` to the region you care about, add the noisy parts to `ignoreSelectors`, or raise `minChangePercent`. In that order.

**Where is the previous version kept?** Each run stores the normalised text it saw and compares the next run against it, so history builds from your first run. Turn on `includeContentSnapshot` to keep your own copy in the dataset.

### Related products

- **Broken Link Checker** to find the dead links on the pages you are watching
- **SEO Meta & Content Auditor** to catch title and description changes across a whole site
- **Competitor Content Tracker** to watch what a rival publishes, not just what they edit
- **Shopify Price Tracker** and **Amazon Price Tracker** for structured price watching
- **Domain & SSL Monitor** to catch expiry and certificate problems on the same domains

# Actor input Schema

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

The pages to monitor, one per line. Each is tracked separately across runs. Required in practice - a run with none returns a row telling you so.

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

Optional CSS selector. Narrow the watch to one region, for example main or .pricing-table, so a change in the navigation or footer does not fire an alert.

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

CSS selectors to strip before comparing, for example .ad, .cookie-banner or .live-counter. Anything that changes on every load belongs here.

## `renderJavaScript` (type: `boolean`):

Load the page in a real browser first. Turn this on for pages built with React, Vue or similar, where the useful content is not in the raw HTML. Slower and costs more compute.

## `ignoreNumbers` (type: `boolean`):

Mask every digit before comparing, so view counts and stock numbers do not register as changes. Turn this off when you are watching a price.

## `minChangePercent` (type: `integer`):

Only report a change when at least this percent of the watched text moved. 0 reports everything. Useful on pages with constant small churn.

## `onlyChanged` (type: `boolean`):

Return a row only when something actually changed. Turn this on for scheduled monitoring so a quiet run returns nothing.

## `includeContentSnapshot` (type: `boolean`):

Attach the normalised text of the page to each row, capped at 20,000 characters. Useful for keeping your own archive.

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

How many pages to load at once.

## `proxyConfiguration` (type: `object`):

Optional. Useful for pages that block cloud IP addresses or serve different content by country.

## `timeBudgetSecs` (type: `integer`):

How long the run may spend checking pages before it stops and returns what it has. Prevents a slow site from stalling a scheduled run. Raise it when watching a long page list.

## Actor input object example

```json
{
  "urls": [
    "https://en.wikipedia.org/wiki/Main_Page"
  ],
  "renderJavaScript": false,
  "ignoreNumbers": false,
  "minChangePercent": 0,
  "onlyChanged": false,
  "includeContentSnapshot": false,
  "maxConcurrency": 5,
  "timeBudgetSecs": 240
}
```

# Actor output Schema

## `changes` (type: `string`):

One row per watched page, with what changed, the added and removed lines, and how much of the page moved.

## `runSummary` (type: `string`):

How many pages changed, stayed the same, fell below the threshold or failed to load.

# 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://en.wikipedia.org/wiki/Main_Page"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arched_friend/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://en.wikipedia.org/wiki/Main_Page"] }

# Run the Actor and wait for it to finish
run = client.actor("arched_friend/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://en.wikipedia.org/wiki/Main_Page"
  ]
}' |
apify call arched_friend/website-change-monitor --silent --output-dataset

```

## MCP server setup

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