# Webpage Field Change Detection Scraper (`automation-lab/selector-webpage-field-change-monitor`) Actor

Extract named CSS text and attribute fields from public static webpages. Compare caller-supplied previous values and export current values plus changed-field old/new pairs for spreadsheet monitoring.

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

## Pricing

from $1.68 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Webpage Field Change Detection Scraper

Webpage field change detection for spreadsheet monitoring: extract named CSS text or attribute fields from public static HTML URLs, compare your supplied previous values, and export current values plus changed-field old/new pairs with observation timestamps.

For operations analysts checking a handful of prices, headings, availability labels or links twice daily. Unlike whole-page hashes or line diffs, the result keeps your field names and excludes unchanged fields from `changes`. It does not keep snapshots or send alerts automatically.

### What it does

- Fetches the supplied anonymous public HTTP(S) pages, in order.
- Extracts the first matching element for each named CSS selector.
- Reads descendant text or an optional raw HTML attribute.
- Compares only keys you supply in each page's `previousValues`.
- Returns one page observation in the default dataset, including unchanged observations.

No JavaScript rendering, login, CAPTCHA handling, XPath, crawling or link discovery is included. Attribute links remain raw rather than resolved URLs.

### Who is it for?

Operations analysts, spreadsheet owners and data engineers comparing configured public-page fields against their own prior observations.

### Why use this Actor?

Keep comparison state in your spreadsheet or pipeline rather than hidden Actor storage. Use the same field definitions on every page and pass a different baseline per URL. Field-level changes let you distinguish a changed price from an unrelated footer edit. Missing fields are visible as null, not silently dropped.

### Getting started

1. Run the prefilled Books to Scrape demonstration input.
2. Replace the URL with a public static HTML page you are permitted to fetch.
3. Define up to 30 named CSS fields; inspect page source, not just rendered browser content.
4. On a first observation, omit previous values to establish a baseline.
5. Export `currentValues` and pass them back in `previousValues` next time.
6. Filter `hasChanges` or inspect `changes` to update your spreadsheet.

### Input parameters

| Parameter | Meaning |
|---|---|
| `pages` | 1–1000 unique objects with `url` and optional `previousValues`. |
| `fields` | 1–30 shared definitions: `name`, CSS `selector`, optional `attribute`. |
| `maxItems` | Global page observation cap, default 10, range 1–1000. Only the first supplied pages are fetched. |

```json
{
  "pages": [{
    "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
    "previousValues": {"heading": "Old heading", "price": "£51.77"}
  }],
  "fields": [
    {"name": "heading", "selector": "h1"},
    {"name": "price", "selector": ".product_main .price_color"},
    {"name": "homeLink", "selector": ".breadcrumb a", "attribute": "href"}
  ],
  "maxItems": 1
}
```

Field names are case-sensitive safe identifiers starting with a letter, at most 64 characters. Duplicate names and unknown baseline keys are rejected. Duplicate canonical URLs (after removing fragments) are rejected to avoid conflicting baselines. Zero/unlimited limits are unsupported.

### How webpage field change detection compares values

Text and attributes are trimmed and all whitespace runs collapse to a single space. Equality is then exact and case-sensitive; there is no numeric, currency or fuzzy matching. HTML entities are decoded by the parser. The first matching element is used; its descendant text is included.

An omitted baseline key establishes a baseline and is listed in `baselineFields`, not in `changes`. An explicit null baseline means the field was previously missing. A missing current element or attribute is null, distinct from an empty string. Other values must be strings. Each value is bounded to 10000 characters; oversized fields fail instead of truncating.

### Output fields

| Field | Meaning |
|---|---|
| `url` | Canonical input URL associated with this baseline. |
| `finalUrl` | Public destination after redirects. |
| `observedAt` | UTC ISO timestamp after fetching. |
| `currentValues` | Every named field's string or null. |
| `changes` | Changed fields only, each with `oldValue` and `newValue`. |
| `changedFields` | Names in changes. |
| `missingFields` | Fields with no matching element or attribute. |
| `baselineFields` | Fields without supplied previous values. |
| `hasChanges` | True when at least one supplied baseline differs. |

### Output example

From the demonstrated product-page input (the observation time varies):

```json
{
  "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "finalUrl": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "observedAt": "2026-10-12T14:14:01.130Z",
  "currentValues": {"heading": "A Light in the Attic", "price": "£51.77", "homeLink": "../../index.html"},
  "changes": {"heading": {"oldValue": "Old heading", "newValue": "A Light in the Attic"}},
  "changedFields": ["heading"],
  "missingFields": [],
  "baselineFields": ["homeLink"],
  "hasChanges": true
}
```

The unchanged price is absent from changes. The home link has no baseline, so it is not reported as a change. Export through Apify's dataset tools as JSON, CSV or Excel; nested objects may need flattening downstream.

### How much does it cost to compare fields on webpages?

Pay-per-event billing has a one-time `start` fee of $0.005 and one `item` event per successfully stored page observation, whether changed, unchanged or first baseline. There is no additional per-field or change event. Failed pages do not produce an item charge; a run that starts fetching can still incur its start fee.

BRONZE pricing is $0.0028 per page observation. Estimated event totals including one start: one page $0.0078, ten pages $0.033, 100 pages $0.285. FREE is $0.00322, SILVER $0.002184, and GOLD/PLATINUM/DIAMOND $0.00168 per page. Spend tiers follow your qualifying aggregate monthly Apify Store spend, not volume within this Actor. Consult the active Pricing tab for plan-specific rates. Platform usage is included in the event tariff, not an extra Actor event. Your integrations can have separate costs.

### Limits and failure behavior

Requests are sequential. Each attempt has a 20-second timeout, at most five redirects and at most 2 MiB of decompressed UTF-8 HTML. Transient network faults, 429 and selected 5xx responses get up to two retries with bounded backoff. No proxy or browser fallback is enabled.

HTTP errors, non-HTML responses, private destinations and pages where every configured field is missing fail the run. Earlier rows are partial, not proof that all requested pages were checked. The Actor does not treat arbitrary challenge pages as good observations; use precise selectors and review missing fields. A page with some missing fields still returns an observation and can report removals.

No 100000-URLs/hour guarantee is made. `maxItems` is a page cap, not a throughput guarantee; runtime and spending limits can stop a run earlier. Increase timeout when supplying many pages, and keep target load reasonable. Concurrent mutation during observation is not a transactional snapshot.

### Integrations

For Google Sheets or Excel, export current values and use them as the next baseline. A Make, n8n or Zapier workflow can call the Actor, flatten `changes`, then send its own notifications. Apify schedules can trigger repeated runs, but your external workflow must refresh baselines; a fixed scheduled input keeps comparing to the same old values.

For a data pipeline, retain URL, observedAt and currentValues as your snapshot table. Consume only hasChanges rows for your change log. Never derive a successful complete check from a failed or timed-out run.

### API use

Start one run with the example JSON saved as `input.json`:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~selector-webpage-field-change-monitor/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' --data-binary @input.json
```

JavaScript with the Apify client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/selector-webpage-field-change-monitor').call(input);
if (run.status !== 'SUCCEEDED') throw new Error(`Incomplete run: ${run.id}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 20 });
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/selector-webpage-field-change-monitor').call(run_input=input_data)
if run['status'] != 'SUCCEEDED':
    raise RuntimeError('Incomplete run: ' + run['id'])
print(client.dataset(run['defaultDatasetId']).list_items(limit=20).items)
```

### MCP setup

For Claude Code:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/selector-webpage-field-change-monitor'
```

Claude Desktop, Cursor, and VS Code configuration (use each client's supported HTTP MCP setup):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/selector-webpage-field-change-monitor"}}}
```

#### Example prompts

- “Extract a first baseline of the title and price from this public static product page.”
- “Compare this page against my previous title and price, and return only changed fields from the observation.”

Configure authentication through your client. Discover the actual tool names and schemas with scoped `tools/list`. Actor selection also exposes run/storage helpers, not exactly one tool. Example authorized prompt: “Compare the heading and price on this public product page against these previous values; show changed fields only.”

Start once and retain its run and storage IDs; they are metadata, not rows. `call-actor` waits at most 45 seconds (default 30). If pending, use `get-actor-run` for the same run with 2/4/8-second backoff capped at 10 seconds and a 120-second total deadline. Set the client timeout above the server wait plus transport margin. At deadline, report pending and the run ID; do not restart to poll.

After success, use `get-dataset-items` with an explicit offset, limit and needed fields. Example consumer budget: pages of 20 source rows, at most 100 rows or 64 KiB of UTF-8 result content admitted to model context, whichever comes first. Enforce bytes host-side, omit/summarize oversized rows with disclosure, and report the continuation offset and partial status. If the client cannot intercept large responses, do not claim a hard byte guarantee. Keep full exports outside model context. No numeric token-savings claim is made.

### Legality and responsible use

Fetch only anonymous public pages you are permitted to access. Respect terms, robots guidance, rate limits and applicable privacy law. Do not submit credentials, private intranet addresses, sensitive personal data or URLs that require an account. Only ports 80/443 are supported, and redirects/DNS destinations must remain public.

This Actor uses no AI at runtime. Apify stores your input, dataset and run logs according to your account's retention settings; delete runs/storage through Apify when no longer needed. There is no cross-run baseline cache. For help, open an issue on this Actor's Apify page.

Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair; secret fields and URL query strings are removed and reports are retained for 30 days. Do not put secrets into field names or baseline values.

### FAQ and troubleshooting

**Why did every field fail to match?** Inspect raw page source. The data may be generated by JavaScript or your selectors may no longer match. This Actor does not render a browser.

**Why did an unchanged field appear changed?** Compare normalized strings, including case and punctuation. Prices are text; `12` and `12.00` differ.

**Can it alert me or remember last time?** Not automatically. Store currentValues and provide them as the next previousValues; use an integration to notify you.

**Can I fetch an attribute?** Set attribute, for example `href`, `content` or `data-price`. Raw values are normalized but URLs are not resolved.

**Does an absent field indicate removal?** Only when a supplied baseline differs from null. Inspect missingFields and the source before assuming a real business change.

### Related workflows

This is a standalone utility: it operates on caller-supplied URLs rather than one service. Pair its JSON exports with your existing Apify dataset consumer or spreadsheet workflow. No other Actor is required, and no unrelated source support is implied.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/selector-webpage-field-change-monitor/changelog.md

# Actor input Schema

## `pages` (type: `array`):

1–1000 unique public HTTP(S) page objects: {url, previousValues?}. previousValues maps configured field names to strings or null. Omitted keys establish a baseline, not a change. Duplicate canonical URLs, credentials, local/private addresses and nonstandard ports are rejected. Redirects must stay public. Pages are checked sequentially in supplied order.

## `fields` (type: `array`):

1–30 definitions shared by all pages: {name, selector, attribute?}. CSS only; XPath is unsupported. The first matching element supplies descendant text, or the named raw attribute when attribute is provided. Names must be unique identifiers beginning with a letter, max 64 characters. Missing element/attribute yields null. Text and attributes are trimmed and whitespace collapsed; comparison is case-sensitive exact equality after this normalization. Values over 10000 characters fail rather than truncate. If every field is missing, the page fails.

## `maxItems` (type: `integer`):

Global run limit in pages, 1–1000 (default 10). Only the first maxItems supplied pages are fetched. Zero/unlimited is unsupported. No links or pagination are discovered. Runtime or billing limits can stop the run earlier; a failed page fails the run and earlier rows are partial results.

## Actor input object example

```json
{
  "pages": [
    {
      "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
      "previousValues": {
        "heading": "Old heading",
        "price": "£51.77"
      }
    }
  ],
  "fields": [
    {
      "name": "heading",
      "selector": "h1"
    },
    {
      "name": "price",
      "selector": ".product_main .price_color"
    },
    {
      "name": "homeLink",
      "selector": ".breadcrumb a",
      "attribute": "href"
    }
  ],
  "maxItems": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset; export as JSON, CSV or Excel through Apify.

# 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 = {
    "pages": [
        {
            "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
            "previousValues": {
                "heading": "Old heading",
                "price": "£51.77"
            }
        }
    ],
    "fields": [
        {
            "name": "heading",
            "selector": "h1"
        },
        {
            "name": "price",
            "selector": ".product_main .price_color"
        },
        {
            "name": "homeLink",
            "selector": ".breadcrumb a",
            "attribute": "href"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/selector-webpage-field-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 = {
    "pages": [{
            "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
            "previousValues": {
                "heading": "Old heading",
                "price": "£51.77",
            },
        }],
    "fields": [
        {
            "name": "heading",
            "selector": "h1",
        },
        {
            "name": "price",
            "selector": ".product_main .price_color",
        },
        {
            "name": "homeLink",
            "selector": ".breadcrumb a",
            "attribute": "href",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/selector-webpage-field-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 '{
  "pages": [
    {
      "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
      "previousValues": {
        "heading": "Old heading",
        "price": "£51.77"
      }
    }
  ],
  "fields": [
    {
      "name": "heading",
      "selector": "h1"
    },
    {
      "name": "price",
      "selector": ".product_main .price_color"
    },
    {
      "name": "homeLink",
      "selector": ".breadcrumb a",
      "attribute": "href"
    }
  ]
}' |
apify call automation-lab/selector-webpage-field-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/selector-webpage-field-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/njRIQWWaCOgQeUEaR/builds/lilChxWfBlcPMh4tE/openapi.json
