# Booking Reviews Scraper | Guest Reviews & Hotel Replies (`s-r/booking-reviews`) Actor

Export Booking.com hotel reviews with ratings, positive and negative text, guest countries, stay dates, rooms, photos and hotel replies. Filter by language, traveler type, season and score.

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

## Pricing

$1.50 / 1,000 guest reviews

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

## Booking Reviews Scraper

Booking reviews scraper exports public Booking.com guest feedback for one or more hotels. Collect scores, original positive and negative comments, room and stay details, guest countries, photos and hotel replies in one consistent dataset.

Use the results to compare properties, monitor recent guest feedback, investigate recurring complaints or connect hotel reviews to your own property catalogue. Each delivered row represents one guest review. Hotel information is saved separately and joins to the reviews through the Booking hotel ID.

### Booking reviews scraper input

Supply hotel page addresses in `start_urls`, or supply Booking hotel IDs in `hotel_ids` with a two-letter `country_code`. A hotel URL may include its language suffix or query parameters; these are normalized before collection. You can provide both URLs and IDs in the same run. If they identify the same hotel, its review limit applies once.

```json
{
  "start_urls": ["https://www.booking.com/hotel/nl/zoku-amsterdam.html"],
  "max_reviews": 5
}
```

`max_reviews` defaults to 100 and applies to each unique hotel across all pages. Set it between 1 and 100000. The available number depends on the hotel, your filters and the reviews currently exposed by Booking.com. Requesting a larger number does not create additional records when fewer reviews are available.

For ID-only input, `country_code` supplies request context. It does not establish the hotel's actual country. Hotel names, page addresses and aggregate ratings remain empty unless a hotel page was supplied and successfully read. Reviews, category scores and filter counts remain available from the review response.

#### Filters and sorting

Use `languages` for two-letter language codes such as `en`, `nl`, `de` or `ja`. An empty list includes all languages. Review text remains in its original language. Booking's special English and Portuguese codes are normalized in `language` and preserved in `language_raw`.

`search_text` searches guest review text. Search highlighting is removed from the delivered title and comments. `customer_type` supports families, couples, groups of friends, solo travelers and business travelers. `time_of_year` filters by stay season. `score_range` selects superb, good, average, poor or very poor reviews, or all scores.

Choose `newest` or `oldest` for complete chronological exports of the exposed review list. `highest_score` and `lowest_score` collect stable score bands in the requested direction, with newest reviews first inside each band. A band can contain two different scores, so this ordering does not promise a strict numerical sort of every individual review. `most_relevant` preserves Booking's relevance order, but deep relevance pagination is not complete or reproducible. The output includes a warning when that mode is used.

For recent-review monitoring, `cutoff_date` accepts a publication date in `YYYY-MM-DD` format and works with `newest` sorting. Reviews older than that day are excluded. The date concerns publication, rather than the guest's check-in or check-out.

### Output and joining hotel information

The default dataset contains only reviews. Main fields include `review_id`, `place_id`, `place_name`, `place_url`, `rating`, `rating_scale`, `title`, `positive_text`, `negative_text`, `published_at`, `language`, `author_name`, `author_country_code` and `reply_text`. Additional fields cover traveler type, room name and ID, check-in, check-out, nights, helpful votes and the original photo sizes and addresses.

Ratings preserve Booking's original scale of 1 to 10. A helpful vote count of zero remains zero. Missing text or hotel replies remain empty rather than being replaced by generated content. Positive and negative comments stay separate; there is no synthesized combined review text. A review ID identifies the source record and is not a public review URL.

Every row includes `source`, `platform` and the actual `fetched_at` time. `place_id` is the Booking hotel ID as text; `hotel_id` carries the same value as a number. This makes the output suitable for joining reviews to hotel records from other Booking datasets.

### Booking reviews scraper output example

This is one real review row, collected from the Zoku Amsterdam hotel page. Every row has the same fields, so exports from different hotels stack into one table.

```json
{
  "source": "booking-reviews",
  "platform": "booking",
  "fetched_at": "2026-10-11T23:14:58.355Z",
  "review_id": "6c1a930fce3e31fa",
  "place_id": "1707393",
  "place_name": "Zoku Amsterdam",
  "place_url": "https://www.booking.com/hotel/nl/zoku-amsterdam.html",
  "rating": 8,
  "rating_scale": 10,
  "title": null,
  "positive_text": null,
  "negative_text": null,
  "author_name": "Elisabeth",
  "author_country_code": "DE",
  "author_country": "Germany",
  "author_avatar_url": "https://xx.bstatic.com/static/img/review/avatars/ava-e.png",
  "author_anonymous": false,
  "traveler_type": "Couple",
  "customer_type": "COUPLES",
  "published_at": "2026-10-10T11:01:14.000Z",
  "language": "de",
  "language_raw": "de",
  "likes": null,
  "reply_text": null,
  "room_name": "Loft",
  "room_id": "170739304",
  "checkin_date": "2026-09-11",
  "checkout_date": "2026-09-13",
  "nights": 2,
  "stay_status": "stayed",
  "hotel_id": 1707393,
  "sort": "newest",
  "photos": []
}
```

With `include_hotel_info: true`, the key-value store's `hotels` record contains snapshots for hotels with delivered reviews. These include the hotel-page identity when known, category scores and filter counts. `reviews_count` means the hotel-page aggregate count, while `reviews_available` means the paginatable total for the requested filters. Those counts can differ. Snapshots remain consistent while a run collects multiple pages. With `include_hotel_info: false`, the hotel list is empty.

### Pagination and incomplete results

The Actor automatically follows continuation cursors. Direct API users repeat their original inputs with the returned `next_cursor` until it becomes null. Keep the hotel inputs, limits, filters and sort unchanged when resuming. The per-hotel limit does not reset between pages, and duplicate review IDs for the same hotel are removed across continuation requests.

`partial` and `warnings` explain remaining work or known source limitations. A failure for one hotel is recorded in `errors` while other hotels continue. A hotel with no matching reviews does not produce a substitute hotel row in the dataset. An unavailable hotel, source rejection or invalid input is reported explicitly.

### Booking reviews scraper use cases

- **Hotel chains and revenue managers:** track guest scores and recurring complaints per property, and compare them with competing hotels in the same city.
- **Reputation monitoring:** run daily with `newest` sorting and a `cutoff_date` to receive only reviews published since the last run.
- **Review analysis:** feed positive and negative comments, room names and traveler types into sentiment or topic models without reformatting.
- **Market research for OTA analysts:** compare scores by guest country, language and season across many hotels.
- **Guest-service quality control:** read hotel replies next to the complaint they answer and see which problems stay unanswered.

### How this Booking reviews scraper compares

Many hotel scrapers return only the handful of featured reviews that Booking.com shows on the hotel page. This Actor pages through the full review list instead, so a hotel with thousands of reviews can be exported completely. It also keeps positive and negative text separate, preserves the original rating scale and language, supports filters on language, traveler type, season, score and free text, and charges only for reviews that are actually delivered. Input and output names of earlier review collectors are accepted, so existing workflows keep running.

### Pricing

Charging follows delivered review rows. The current per-review price is displayed on the Actor listing. Hotel snapshots, filter counters and diagnostic errors do not count as reviews. Empty results have zero review charges. The configured free allowance limits delivery to ten reviews in total, including runs with multiple hotels; it is not ten reviews per hotel.

To estimate the result volume, start with a small per-hotel limit and inspect the matching totals in the hotel snapshots. Then increase the limit or narrow your filters. A requested limit is a delivery ceiling, not a guarantee that Booking exposes that many reviews.

### Switching from another review collector

Existing input conventions are accepted through `startUrls`, including objects with a `url`, `maxReviewsPerHotel`, `sortReviewsBy`, `reviewScores` and `cutoffDate`. Canonical fields take precedence when both forms are supplied.

| Previous output name | Field in this dataset |
|---|---|
| `likedText` | `positive_text` |
| `dislikedText` | `negative_text` |
| `reviewDate` | `published_at` |
| `propertyResponse` | `reply_text` |
| `hotelId` | `place_id` or numeric `hotel_id` |
| `numberOfNights` | `nights` |
| `reviewLanguage` | `language` and `language_raw` |

### FAQ

#### Can I export every review for a hotel?

Use chronological sorting and a sufficiently high `max_reviews`. The Actor collects the reviews Booking currently exposes for your filters. Reviews removed by the source or outside its retention window are unavailable. For a complete export, prefer `newest` or `oldest` over relevance sorting.

#### Are hotel replies included?

Yes, when Booking supplies a public reply, it appears in `reply_text`. Missing replies remain empty. The source does not provide a reply timestamp here, so no timestamp is inferred.

#### Why do hotel totals differ from the number of delivered rows?

Your filters and per-hotel limit determine delivery. Hotel-page counts and the review list also describe different source totals. Check `reviews_available`, your requested limit, `next_cursor`, `partial` and `errors` before treating a smaller delivery as a complete hotel export.

#### Does the Actor translate reviews?

No. The language filter selects reviews by source language, and the original text is preserved. The normalized language field helps downstream translation or analysis without changing the guest's words.

# Actor input Schema

## `start_urls` (type: `array`):

Booking.com hotel URLs; a single URL string is also accepted.

## `hotel_ids` (type: `array`):

Positive Booking hotel IDs. Requires country_code; skips hotel-page lookup.

## `country_code` (type: `string`):

Two-letter request country code, required with hotel_ids. Does not prove the hotel location.

## `sort` (type: `string`):

Defaults to newest. Score sorting groups by score band, newest first within each band. most_relevant is not complete or reproducible.

## `languages` (type: `array`):

Two-letter review language codes; omitted means all languages.

## `customer_type` (type: `string`):

Traveler filter. Defaults to all.

## `time_of_year` (type: `string`):

Season filter. Defaults to all.

## `score_range` (type: `string`):

Score band filter. Defaults to all.

## `search_text` (type: `string`):

Search in original guest review text; defaults to an empty string.

## `max_reviews` (type: `integer`):

Maximum delivered unique reviews per hotel, across all cursor pages. Defaults to 100.

## `include_hotel_info` (type: `boolean`):

Include a hotel snapshot for hotels with delivered reviews. Defaults to true.

## `cursor` (type: `string`):

Opaque next_cursor from the previous response. Repeat all original inputs unchanged.

## `cutoff_date` (type: `string`):

Optional oldest included review publication date, YYYY-MM-DD; only with newest sorting.

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

Alias for start_urls; also accepts objects with a url property.

## `maxReviewsPerHotel` (type: `integer`):

Alias for max_reviews.

## `sortReviewsBy` (type: `string`):

Alias for sort.

## `reviewScores` (type: `string`):

Alias for score_range.

## `cutoffDate` (type: `string`):

Alias for cutoff_date, YYYY-MM-DD.

## Actor input object example

```json
{
  "start_urls": [
    "https://www.booking.com/hotel/nl/zoku-amsterdam.html"
  ],
  "max_reviews": 100
}
```

# Actor output Schema

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

One row per delivered guest review.

## `hotels` (type: `string`):

Hotel information linked to delivered reviews.

## `output` (type: `string`):

Run status and delivery counts.

# 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 = {
    "start_urls": [
        "https://www.booking.com/hotel/nl/zoku-amsterdam.html"
    ],
    "max_reviews": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/booking-reviews").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 = {
    "start_urls": ["https://www.booking.com/hotel/nl/zoku-amsterdam.html"],
    "max_reviews": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/booking-reviews").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 '{
  "start_urls": [
    "https://www.booking.com/hotel/nl/zoku-amsterdam.html"
  ],
  "max_reviews": 100
}' |
apify call s-r/booking-reviews --silent --output-dataset

```

## MCP server setup

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

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/CFGdIwkGWeucYsOX0/builds/85SlTYJWbjcfQLx0a/openapi.json
