# Business Review Monitor — New & Changed Reviews (`yearly_register/business-review-monitor`) Actor

Monitor public business-review pages for new and changed reviews with persistent state, structured output, and selector fallback when Schema.org data is unavailable.

- **URL**: https://apify.com/yearly\_register/business-review-monitor.md
- **Developed by:** [Automation Tech](https://apify.com/yearly_register) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 source 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

## Business Review Monitor — New & Changed Reviews

Monitor public business-review pages and receive structured review records ready for alerts, dashboards, reputation workflows, and scheduled automation.

The Actor uses **Schema.org / JSON-LD review data first**, with optional CSS selectors for sources that expose reviews in regular HTML.

**Persistent monitoring · New/changed detection · Stable review IDs · Pay only for delivered review rows**

### Quick start

Add one or more public review pages:

```json
{
  "startUrls": [
    { "url": "https://example.com/customer-reviews" }
  ],
  "maxResults": 100
}
```

Each delivered row contains a stable `uniqueKey`, source URL, scrape timestamp, and normalized review fields such as business name, review text, author, publication date, review URL, and rating when the page exposes them.

### Turn a snapshot into a monitor

Enable persistent state and reuse the same `stateKey` on scheduled runs:

```json
{
  "startUrls": [
    { "url": "https://example.com/customer-reviews" }
  ],
  "monitor": true,
  "stateKey": "example-business",
  "emitMode": "new_and_changed",
  "maxResults": 100
}
```

Monitoring modes:

- `new` — deliver reviews not previously acknowledged by this monitor
- `changed` — deliver previously known review records whose normalized content changed
- `new_and_changed` — deliver both
- `all` — deliver all observed reviews

Monitoring state is stored in the named Apify Key-Value Store `business-review-monitor-state`. State is updated only after a selected row is actually delivered. Reviews blocked by `maxResults` or the PPE spending limit are **not** incorrectly marked as delivered, so they remain eligible on a later run.

`changeRetentionDays` and `maxStateRecords` keep state bounded.

### What counts as a changed review?

A review is matched by its stable `uniqueKey`. When the same key is observed again, the Actor hashes the normalized review data. A different hash is classified as `changed`.

Typical changes can include:

- review text edits
- rating changes on the same review
- title/author/date metadata changes exposed by the source

This does not claim to monitor an aggregate business rating unless the target review records themselves expose that information.

### Extraction

#### Automatic structured-data mode

When a page exposes Schema.org `Review`, `LocalBusiness`, `Organization`, or compatible JSON-LD, the Actor normalizes those reviews automatically.

#### CSS selector fallback

For a source without useful structured review markup, configure:

- Review card selector
- Review title selector
- Review body selector
- Rating selector
- Author selector
- Published date selector
- Optional stable review ID attribute

Selectors are source-specific and may need maintenance if the website redesigns its markup.

### Output

The Reviews dataset view exposes useful monitoring columns directly:

- monitor status
- business
- rating
- review title/body
- author
- published date
- review URL
- source URL
- stable unique key

The `OUTPUT` record contains run-level diagnostics:

- requested / successful / failed targets
- emitted and charged rows
- PPE charge-limit status
- new / changed / unchanged counts
- rows intentionally skipped by monitoring mode
- persisted state size
- output warnings

### Billing semantics

The live Store uses Apify's synthetic `apify-default-dataset-item` event for each review row written to the default Dataset. The current event price is **$0.0015 per delivered review row ($1.50 / 1,000)**, plus the standard Actor-start event at **$0.00005**.

Duplicate rows, unchanged reviews excluded by monitoring mode, failed targets, and rows stopped by the result or spending cap are not written as delivered review rows and therefore do not create a default-Dataset-item event.

The current customer price and any future pricing changes remain visible in the Apify **Pricing** tab.

### Scheduling

For recurring monitoring:

1. Test each source with a small manual run.
2. Enable `monitor`.
3. Give the workflow a stable `stateKey`.
4. Choose `new` or `new_and_changed`.
5. Schedule the same input hourly, daily, or weekly through Apify.

The first `new` / `new_and_changed` run treats previously unknown reviews as new. A `changed`-only first run establishes a baseline without emitting those initial reviews.

### Reliability and limits

Public review sites differ significantly.

- Schema.org markup may be incomplete.
- CSS selectors can stop matching after a source redesign.
- Some sites return login walls, CAPTCHAs, or HTTP 403 responses.
- The Actor does **not** bypass authentication or private APIs.
- Blocked pages are target failures, not evidence that a business has zero reviews.
- Some Trustpilot/G2-style surfaces may block generic public crawling and should not be advertised as supported unless a live run has been verified.

A run where every target fails is failed rather than silently presented as an empty successful scrape. Successful targets from a partial run are preserved.

### Responsible use

Use the Actor for lawful collection of public review information. Follow applicable privacy, content, consumer-protection, and platform requirements.

# Actor input Schema

## `startUrls` (type: `array`):

Add one or more public business/review pages. The Actor uses Schema.org review data when available or your optional CSS selectors.

## `maxResults` (type: `integer`):

Global cap for review rows delivered by this run.

## `monitor` (type: `boolean`):

Persist review state between runs. Reuse the same Monitor name on schedules to suppress unchanged reviews.

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

Stable identifier for this monitor. Reuse the same value on future runs.

## `emitMode` (type: `string`):

new = unseen reviews; changed = previously seen reviews whose normalized content changed; new\_and\_changed = both; all = all observed reviews.

## `changeRetentionDays` (type: `integer`):

Monitoring state older than this is pruned.

## `maxStateRecords` (type: `integer`):

Maximum review snapshots kept for this monitor after pruning.

## `maxRequests` (type: `integer`):

Hard crawl request limit.

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

Maximum concurrent requests.

## `maxRequestRetries` (type: `integer`):

Retries after transient request failures.

## `maxRequestsPerMinute` (type: `integer`):

Maximum requests per minute.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout.

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

Optional Apify or custom proxy configuration.

## `debug` (type: `boolean`):

Enable verbose debug logs.

## `reviewSelector` (type: `string`):

Optional CSS selector for review cards. Leave empty to use structured JSON-LD only.

## `titleSelector` (type: `string`):

CSS selector relative to each review card.

## `bodySelector` (type: `string`):

CSS selector relative to each review card.

## `ratingSelector` (type: `string`):

CSS selector relative to each review card.

## `authorSelector` (type: `string`):

CSS selector relative to each review card.

## `dateSelector` (type: `string`):

CSS selector relative to each review card.

## `maxReviewsPerPage` (type: `integer`):

Maximum valid review candidates retained from one page.

## `maxReviewBodyLength` (type: `integer`):

Maximum body characters delivered; change detection still hashes the full normalized body.

## `ratingScaleMax` (type: `number`):

Fallback maximum rating when structured bestRating is unavailable.

## `reviewIdAttribute` (type: `string`):

Optional card attribute containing a stable source review ID.

## Actor input object example

```json
{
  "startUrls": [],
  "maxResults": 250,
  "monitor": false,
  "stateKey": "default",
  "emitMode": "new",
  "changeRetentionDays": 30,
  "maxStateRecords": 50000,
  "maxRequests": 10000,
  "maxConcurrency": 10,
  "maxRequestRetries": 3,
  "maxRequestsPerMinute": 60,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "debug": false,
  "reviewSelector": "",
  "titleSelector": "",
  "bodySelector": "[itemprop=reviewBody]",
  "ratingSelector": "[itemprop=ratingValue]",
  "authorSelector": "[itemprop=author]",
  "dateSelector": "[itemprop=datePublished]",
  "maxReviewsPerPage": 100,
  "maxReviewBodyLength": 10000,
  "ratingScaleMax": 5,
  "reviewIdAttribute": ""
}
```

# Actor output Schema

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

Normalized review rows delivered by this run.

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

Target success/failure, billing, charge-limit, and monitoring-state counters.

# 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 = {
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yearly_register/business-review-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 = { "proxyConfiguration": { "useApifyProxy": False } }

# Run the Actor and wait for it to finish
run = client.actor("yearly_register/business-review-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 '{
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call yearly_register/business-review-monitor --silent --output-dataset

```

## MCP server setup

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