# Booking.com Reviews Scraper (`luminar/booking-reviews-scraper-private-v1`) Actor

Export flat, deduplicated Booking.com reviews with positive and negative text, scores, stay context, property replies when exposed, filters, coverage, and optional changes-only monitoring.

- **URL**: https://apify.com/luminar/booking-reviews-scraper-private-v1.md
- **Developed by:** [Luka](https://apify.com/luminar) (community)
- **Categories:** Business, Marketing, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 delivered reviews

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?

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

**Use this Booking.com reviews scraper to convert property URLs into flat, deduplicated review rows with scores, stay details, traveler context, property replies, stable identity, and explicit coverage.** Use the default recent export for a quick dataset or changes-only output for recurring reputation monitoring.

This **hotel reviews scraper** is built for review analysis, property-response tracking, guest-experience research, and scheduled changefeeds. It preserves positive and negative review text separately, provides a normalized combined text field, and distinguishes source exhaustion from a bounded, capped, or failed collection window.

![Booking Reviews output showing review scores, stay context, text, replies, and bounded coverage](https://api.apify.com/v2/key-value-stores/DJgPR6wwS2tIaLDQW/records/booking_reviews--6540e3f193d9ddb9-booking-reviews-results.png)

### 🚀 Start in 60 seconds

Paste one public property URL and keep the 25-review prefill:

```json
{
  "propertyUrls": [
    "https://www.booking.com/hotel/me/regent-porto-montenegro.html"
  ],
  "mode": "recent",
  "maxReviewsPerProperty": 25,
  "incrementalState": true,
  "outputMode": "all"
}
```

After the run finishes, open:

- **Review overview** for text, score, stay, traveler, reply, state, and coverage fields.
- **Full export** for every schema-backed review field.
- `OUTPUT` in the default key-value store for matched, emitted, suppressed, coverage, and PPE counts.

Start with one property before increasing concurrency or review limits. The run report exposes total source requests, transferred bytes, runtime, limits, coverage, and billing without disclosing the extraction implementation.

### 🎯 Choose the right workflow

#### Recent review export

Keep `mode: "recent"` for a low-work newest-first window. Recent mode is intentionally reported as `BOUNDED_WINDOW` unless the source positively ends within the requested limit.

This is recommended for daily or weekly monitoring, recent sentiment analysis, and quick review samples.

#### Full pagination

Use `mode: "full"` when you need older reviews. The Actor continues until positive source exhaustion, `maxReviewsPerProperty`, or a hard request, runtime, cost, or charge ceiling.

Full mode can claim complete coverage only after positive source exhaustion. Receiving the requested review count proves the delivery cap was met; it does not prove there were no more source reviews.

#### All rows or changes-only monitoring

Keep `outputMode: "all"` to write every unique matching review. For a changefeed, use:

```json
{
  "incrementalState": true,
  "outputMode": "changes_only",
  "propertyUrls": [
    "https://www.booking.com/hotel/me/regent-porto-montenegro.html"
  ]
}
```

The first state-less run establishes the baseline and writes every matched review as `NEW`. Compatible repeats write only `NEW` or `UPDATED` reviews; `UNCHANGED` rows are observed and suppressed for free. A newly exposed property reply counts as `UPDATED`.

Changes-only mode requires `incrementalState: true` and accepts at most three unique properties per run. Partial or capped snapshots never infer that a missing review was deleted.

### 📦 What you get

Each paid dataset item is one unique Booking.com review. Core fields include:

- stable review, property, and delivery identity so interrupted runs can resume without duplicate paid rows;
- positive, negative, and normalized combined review text;
- overall score, review subratings, and property score breakdown when exposed;
- publication and stay dates, stay length, and room type;
- traveler type, stay tags, reviewer name, and reviewer country;
- property response text and date when included;
- helpful votes, source language, and photo URLs when exposed;
- `NEW`, `UPDATED`, or `UNCHANGED` change status;
- canonical coverage status and buyer-safe warnings.

A representative row shape:

```json
{
  "deliveryId": "booking-review-delivery:example-stable-delivery-id",
  "reviewId": "f609f97d2d681458",
  "propertyId": "9247370",
  "propertyName": "Example Hotel",
  "propertyUrl": "https://www.booking.com/hotel/gb/example.html",
  "positiveText": "Representative positive comment",
  "negativeText": "Representative negative comment",
  "normalizedReviewText": "Representative positive comment\nRepresentative negative comment",
  "score": 8,
  "publishedDate": "2026-08-23",
  "travelerType": "Solo traveller",
  "reviewerCountry": "Italy",
  "propertyResponseText": null,
  "changeStatus": "NEW",
  "coverageState": "CAPPED",
  "coverageStatus": "BOUNDED_WINDOW"
}
```

Missing source values remain `null`; the Actor does not invent review text, scores, stay details, replies, or reviewer attributes. Duplicate targets and stable review identities are not delivered or charged twice.

### 🎛️ Input guide

#### Review selection

| Input                                | Use it when                                                        | Work and result impact                                                      |
| ------------------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `propertyUrls`                       | You have public Booking.com property URLs or compact `cc/slug` IDs | Each unique property adds source work; all-mode accepts up to 100 targets   |
| `mode`                               | You need a recent window or deeper history                         | Full pagination can require many more pages                                 |
| `maxReviewsPerProperty`              | You need a bounded number of rows per property                     | Higher values can increase requests, runtime, and transfer                  |
| `language`                           | You need one source language                                       | Blank keeps every language                                                  |
| `sortBy`                             | You need newest, relevance, oldest, or score order                 | Newest is recommended for monitoring and lower-date early stopping          |
| `dateFrom`, `dateTo`                 | You need a publication-date window                                 | Deterministic filters; newest-first can stop after crossing the lower bound |
| `minScore`, `maxScore`               | You need an inclusive 0–10 score range                             | Applied to extracted rows                                                   |
| `travelerTypes`, `reviewerCountries` | You need audience segments                                         | Empty keeps every exposed traveler type or country                          |

#### Replies and monitoring

Turn on `repliesOnly` to keep only reviews with an official property response. `includePropertyResponse` controls whether response text and date are included; disabling it reduces output depth but does not reduce source requests.

Keep `incrementalState` on for stable change classification. Choose `changes_only` only when you want the dataset to contain NEW and UPDATED rows instead of all observed rows.

#### Limits and coverage

- `maxConcurrency` parallelizes properties, not pages inside one property.
- `maxRequests` is the hard total across metadata lookup, pages, retries, and automatic routing.
- `maxRuntimeSecs` stops new source work before the wall-clock ceiling.

The defaults are safer than maximizing every limit. Raise one dimension at a time and inspect coverage after each larger run.

### 💰 Pricing

This Actor uses pay per event. The prices below are the active Store prices for all six Apify plans.

```text
run charge = $0.0018 verified actor start
      + delivered unique review rows × your Apify-tier review price
```

| Apify plan | Actor start per verified run | Price per delivered review | Price per 1,000 delivered reviews |
| ---------- | ---------------------------: | -------------------------: | --------------------------------: |
| Free       |                    `$0.0018` |                 `$0.00075` |                           `$0.75` |
| Bronze     |                    `$0.0018` |                 `$0.00070` |                           `$0.70` |
| Silver     |                    `$0.0018` |                 `$0.00065` |                           `$0.65` |
| Gold       |                    `$0.0018` |                 `$0.00060` |                           `$0.60` |
| Platinum   |                    `$0.0018` |                 `$0.00034` |                           `$0.34` |
| Diamond    |                    `$0.0018` |                 `$0.00020` |                           `$0.20` |

- **Free example:** 25 delivered reviews cost `$0.0018 + (25 × $0.00075) = $0.02055`.
- **Gold example:** the same 25 reviews cost `$0.0018 + (25 × $0.00060) = $0.01680`.

Gold reduces the review event by `20%` compared with Free. At 25 delivered reviews, the flat start makes the complete-run saving `18.25%` (`$0.00375`).

The start is charged only after at least one target returns usable verified review observations or positively confirmed empty coverage. Blocked, failed, and unverified source attempts are free. On a compatible changes-only repeat with zero NEW or UPDATED rows, a successful verified scan costs only the `$0.0018` start. Suppressed unchanged rows, duplicates, and run-report evidence are free. The first changes-only baseline writes every matching review as NEW, so those delivered rows are charged normally.

### ✅ Coverage you can trust

Each review row includes coverage context, and the machine-readable run report explains the whole run.

- `COMPLETE` requires positive source exhaustion for the requested extraction shape.
- `BOUNDED_WINDOW` means recent mode or the delivery limit produced a useful window without a full-history claim.
- `CAPPED` means a review, request, runtime, cost, or buyer-charge ceiling stopped additional work.
- `PARTIAL` means some requested work succeeded but at least one part could not be verified.
- `BLOCKED` or `FAILED` remains explicit and is never converted to empty success.
- `EMPTY_CONFIRMED` requires positive source evidence that no matching reviews exist.

Coverage warnings explain limits and filters. The run report separates matched, emitted, suppressed, intended, charged, and free counts. Monitoring state advances only from a safe compatible observation; missing rows in partial snapshots do not become deletion events.

### 🔌 API and automation

Use the same input through the Apify API. Keep the Actor ID and token in environment variables:

```bash
curl -X POST "https://api.apify.com/v2/acts/$ACTOR_ID/runs?token=$APIFY_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{
    "propertyUrls": ["https://www.booking.com/hotel/me/regent-porto-montenegro.html"],
    "mode": "recent",
    "maxReviewsPerProperty": 25,
    "incrementalState": true,
    "outputMode": "all"
  }'
```

After completion, read `defaultDatasetId` from the run and fetch:

```text
GET https://api.apify.com/v2/datasets/{defaultDatasetId}/items?clean=true&format=json
```

For scheduled changefeeds, reuse the same property targets, filters, sort order, mode, and review cap so state comparisons remain compatible. The Actor does not create schedules itself.

### ❓ FAQ and support

#### Why are all reviews NEW on the first changes-only run?

There is no earlier compatible snapshot yet. The first run establishes the baseline; later runs can identify unchanged and updated rows.

#### Why is recent mode not COMPLETE when it returned the requested rows?

Because a delivery cap is not source exhaustion. Recent mode is a useful newest-first window and is labeled accordingly unless the source positively ends.

#### Does a missing review mean it was deleted?

No. V1 does not infer deletion from partial, capped, or otherwise incomplete observations.

#### What should I include in a support report?

Include the Apify run ID, input with tokens or secrets removed, the affected property, expected and observed behavior, and the relevant coverage warnings or `OUTPUT` report. Never send an API token in a support message.

# Actor input Schema

## `propertyUrls` (type: `array`):

One or more Booking.com property URLs, or compact cc/slug IDs such as me/regent-porto-montenegro. Duplicate targets are removed before requests. Changes-only monitoring accepts at most 3 unique properties per run so zero-change cost stays bounded.

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

Recent is a fast newest-first window and is reported as bounded unless the source positively ends. Full keeps paginating until source exhaustion, the review cap or a safety ceiling.

## `maxReviewsPerProperty` (type: `integer`):

Maximum review rows considered for each property. Higher limits can require more pages and runtime. Reaching this cap is reported as bounded or capped, never source-complete.

## `language` (type: `string`):

Optional Booking language code such as en, de or fr. Leave blank to keep every source language.

## `sortBy` (type: `string`):

Newest first is recommended for repeat monitoring. Other orders can require more paging before date filters can stop work.

## `dateFrom` (type: `string`):

Optional review publication-date lower bound. Newest-first runs stop early once older dated rows are reached.

## `dateTo` (type: `string`):

Optional review publication-date upper bound.

## `minScore` (type: `number`):

Inclusive 0–10 score filter applied to extracted rows.

## `maxScore` (type: `number`):

Inclusive 0–10 score filter applied to extracted rows.

## `travelerTypes` (type: `array`):

Optional deterministic match against source traveler/stay tags.

## `reviewerCountries` (type: `array`):

Optional exact, case-insensitive match against the public country label exposed by Booking.com.

## `repliesOnly` (type: `boolean`):

Turn on to keep only reviews where Booking.com exposes an official property response.

## `includePropertyResponse` (type: `boolean`):

Preserve official property reply text and date when exposed. Disabling this reduces output depth but does not reduce source requests.

## `incrementalState` (type: `boolean`):

Classify stable reviews as NEW, UPDATED or UNCHANGED. A newly added property reply is UPDATED. Partial or capped snapshots never infer deletion.

## `outputMode` (type: `string`):

All matching reviews writes every unique row. Changes-only is for monitoring: the first run writes all rows as NEW; compatible repeats write only NEW or UPDATED rows and suppress UNCHANGED rows for free. It requires review-status tracking and accepts at most 3 unique properties per run.

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

Parallelizes properties, not pages within one property. Excessive concurrency can increase challenge/retry cost.

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

Hard total across all pages and retries.

## `maxRuntimeSecs` (type: `integer`):

Stops new page work before the requested runtime boundary.

## `maxCostUsd` (type: `number`):

Internal conservative compute-and-transfer guard; this is not the buyer PPE charge.

## Actor input object example

```json
{
  "propertyUrls": [
    "https://www.booking.com/hotel/me/regent-porto-montenegro.html"
  ],
  "mode": "recent",
  "maxReviewsPerProperty": 25,
  "language": "",
  "sortBy": "newest",
  "repliesOnly": false,
  "includePropertyResponse": true,
  "incrementalState": true,
  "outputMode": "all",
  "maxConcurrency": 3,
  "maxRequests": 120,
  "maxRuntimeSecs": 240,
  "maxCostUsd": 1
}
```

# Actor output Schema

## `reviews` (type: `string`):

One deduplicated review per row with stable identity, positive and negative text, score, stay and traveler context, optional property response, change status, and coverage warnings.

## `runReport` (type: `string`):

The OUTPUT record with matched, emitted, unchanged-suppressed, capped, failed, free, and charged counts for every requested property and the complete run.

# 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 = {
    "propertyUrls": [
        "https://www.booking.com/hotel/me/regent-porto-montenegro.html"
    ],
    "maxReviewsPerProperty": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("luminar/booking-reviews-scraper-private-v1").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 = {
    "propertyUrls": ["https://www.booking.com/hotel/me/regent-porto-montenegro.html"],
    "maxReviewsPerProperty": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("luminar/booking-reviews-scraper-private-v1").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 '{
  "propertyUrls": [
    "https://www.booking.com/hotel/me/regent-porto-montenegro.html"
  ],
  "maxReviewsPerProperty": 25
}' |
apify call luminar/booking-reviews-scraper-private-v1 --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,luminar/booking-reviews-scraper-private-v1"
        }
    }
}

```

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/T67Ds0NdwbvpoVJ3N/builds/Sa1h3mwE0YHePaLq9/openapi.json
