# Booking.com Reviews Scraper (`scrapyx/booking-reviews-scraper`) Actor

Extracts guest review data (ratings, text, reviewer demographics, stay details, partner replies) for hotels listed on Booking.com by hotel URL or hotel ID.

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

## Pricing

from $0.49 / 1,000 results

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/platform/actors/running/actors-in-store#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

## Booking.com Reviews Scraper

Extracts guest review data for any hotel listed on Booking.com — just give it a hotel page link or hotel ID.

### Why use this actor

- No account, no login, and no API key needed — works on public hotel pages only.
- Pulls the full review picture: star ratings, written reviews (positive/negative split), reviewer country and trip type, room type and stay dates, helpful-vote counts, and any partner (hotel) reply.
- Also returns the hotel's aggregate rating breakdown — score filters, customer-type breakdown, language breakdown, room-type breakdown, and total review count — as a single summary row per hotel.
- Every hotel you submit gets at least one output row — including a clear diagnostic reason on rows that couldn't be matched or fetched, so nothing silently disappears from your results.
- Works with either a full Booking.com hotel page URL or the raw numeric hotel ID.
- Built-in cap on reviews per hotel (adjustable) so a single wildly popular hotel with tens of thousands of reviews doesn't blow up your run — set it to 0 to pull everything.

### How it works

1. You give the actor one or more Booking.com hotel page links (or numeric hotel IDs).
2. If you gave a URL, the actor first resolves it to Booking's internal hotel ID via the same hotel-search lookup Booking's own search box uses — and double-checks the match against the hotel name in your URL before trusting it, so it never silently returns a different property's reviews.
3. It then pulls the review data directly from Booking.com, page by page, until it runs out of reviews or hits your per-hotel cap.
4. Results are streamed to your dataset as they finish, with automatic retries for temporary hiccups.
5. You export the finished dataset as JSON, CSV, or Excel straight from the Apify Console — no scrapers or infrastructure to manage yourself.

### Input

```json
{
  "hotelUrls": [
    "https://www.booking.com/hotel/sg/marina-bay-sands.html"
  ],
  "sortBy": "most_relevant",
  "maxReviewsPerHotel": 200,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

| Field | Type | Description |
|---|---|---|
| `hotelUrls` | array of strings | Booking.com hotel page URLs (e.g. `https://www.booking.com/hotel/sg/marina-bay-sands.html`) or raw numeric Booking.com hotel IDs (e.g. `245881`). |
| `sortBy` | string | Review order: `most_relevant` (default), `newest_first`, `oldest_first`, `highest_score`, or `lowest_score`. |
| `maxReviewsPerHotel` | integer | Stop after this many reviews per hotel. Default `200`. Set to `0` to fetch every review (can be slow for hotels with tens of thousands of reviews). |
| `maxConcurrency` | integer | How many hotels to process at the same time. Default `3`. |
| `proxyConfiguration` | object | Proxy settings. Residential proxy is turned on by default, though the review API was not observed to gate on IP reputation during testing. |

### Output

Each hotel produces one **summary** row plus one **review** row per guest review. Real examples from a run against the live site (long fields truncated):

**Summary row** (`recordType: "SUMMARY"`):

```json
{
  "_input": "https://www.booking.com/hotel/sg/marina-bay-sands.html",
  "_source": "S1-graphql",
  "_scrapedAt": "2026-08-10T12:32:40Z",
  "recordType": "SUMMARY",
  "hotelId": 245881,
  "matchedHotelName": "Marina Bay Sands",
  "matchScore": 1.0,
  "reviewsCount": 16291,
  "customerTypeFilter": [
    { "name": "Families (5593)", "value": "FAMILIES", "count": 5593 },
    { "name": "Couples (7437)", "value": "COUPLES", "count": 7437 },
    "... 3 more"
  ],
  "reviewScoreFilter": [
    { "name": "Wonderful: 9+ (14116)", "value": "REVIEW_ADJ_SUPERB", "count": 14116 },
    "... 4 more"
  ],
  "sorters": [
    { "name": "Most relevant", "value": "MOST_RELEVANT" },
    "... 4 more"
  ]
}
```

**Review row** (`recordType: "REVIEW"`):

```json
{
  "_input": "https://www.booking.com/hotel/sg/marina-bay-sands.html",
  "_source": "S1-graphql",
  "_scrapedAt": "2026-08-10T12:32:40Z",
  "recordType": "REVIEW",
  "hotelId": 245881,
  "matchedHotelName": "Marina Bay Sands",
  "reviewUrl": "80ba17d8d02303e4",
  "reviewScore": 1.0,
  "reviewedDate": 1776392467,
  "helpfulVotesCount": 1,
  "guestDetails": {
    "username": "Nahalla",
    "countryCode": "au",
    "countryName": "Australia",
    "guestTypeTranslation": "Family",
    "anonymous": false
  },
  "bookingDetails": {
    "customerType": "FAMILIES",
    "roomType": { "id": "24588264", "name": "Sands Family Suite" },
    "checkinDate": "2026-04-03",
    "checkoutDate": "2026-04-13",
    "numNights": 10,
    "stayStatus": "stayed"
  },
  "textDetails": {
    "title": "Will never stay again",
    "positiveText": "The location was good, a lot of things to do and places to go .",
    "negativeText": "Not child friendly, the room was expensive for no reason...",
    "lang": "en"
  },
  "partnerReply": null,
  "isApproved": true
}
```

When a hotel URL can't be confidently matched to a specific property, or the input can't be understood, the actor still emits one row for that input with a clear reason, instead of leaving it out silently:

```json
{
  "_input": "https://www.booking.com/hotel/ae/burj-al-arab.html",
  "_source": "none",
  "_scrapedAt": "2026-08-10T12:32:40Z",
  "recordType": "ERROR",
  "_error": "hotel_not_confidently_resolved",
  "_errorDetail": "No confident match for 'burj al arab' (country=ae). Best candidate: 'GuestReady - A Glimpse of Burj Al Arab' (score=0.50, need >=0.8). Try passing the raw numeric hotel ID instead."
}
```

| Field | Type | Description |
|---|---|---|
| `_input` | string | The hotel URL or ID you submitted. |
| `_source` | string | Which internal strategy produced this row. `"none"` on a failed row. |
| `_scrapedAt` | string | UTC timestamp of when this row was produced. |
| `recordType` | string | `"SUMMARY"`, `"REVIEW"`, or `"ERROR"`. |
| `hotelId` | integer | Booking.com's numeric ID for the hotel (present on successful rows). |
| `matchedHotelName` | string | The hotel name Booking.com's own search returned for your URL (present when a URL was resolved; blank when you passed a raw numeric ID directly). |
| `matchScore` | number | How confidently the URL was matched to that hotel, from 0 to 1 (present when a URL was resolved). |
| `_error` | string | Present only on rows that couldn't be fetched, e.g. `hotel_not_confidently_resolved`, `invalid_input`. |
| `_errorDetail` | string | Human-readable detail for the error, present only alongside `_error`. |
| *(all other fields)* | — | On a successful row, every review or rating field Booking.com provides is passed through unchanged — the exact set can vary slightly by hotel. |

### Notes / limits

- Very popular hotels can have tens of thousands of reviews. The default 200-per-hotel cap keeps runs fast and predictable; raise it (or set it to `0`) if you need the complete history for a specific property.
- Booking.com hotel URLs don't embed a numeric ID, so the actor resolves your URL to a hotel via Booking's own hotel-name search and cross-checks the match — for a handful of very generic or landmark-style names (e.g. searching just a tourist landmark's name rather than the exact hotel name on your URL) this match can come back inconclusive. If that happens, open the hotel's page in your browser once and copy the numeric ID from Booking's own listing, or try a more specific hotel URL.
- Reviews are exactly as written by the guest — no automatic translation is applied by this actor (Booking.com's own `lang` field on each review tells you what language it's in).

### Other actors in this collection

| Actor | What it returns |
|---|---|
| Agoda Reviews Scraper | Guest reviews for hotels listed on Agoda.com |
| Orbitz Hotel Reviews Scraper | Guest reviews for hotels listed on Orbitz.com |
| GetYourGuide Reviews Scraper | Guest reviews for tours/activities listed on GetYourGuide.com |

# Actor input Schema

## `hotelUrls` (type: `array`):

Booking.com hotel detail page URLs (e.g. https://www.booking.com/hotel/sg/marina-bay-sands.html) or raw numeric Booking.com hotel IDs.

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

How reviews are ordered when fetched. Matches the sort options exposed on the Booking.com reviews widget itself.

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

Stop paginating a hotel after this many reviews. Popular hotels can have tens of thousands of reviews, so this is capped by default. Set to 0 for unlimited (fetch every review -- can be slow and costly for very-large hotels).

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

Maximum number of hotels processed in parallel.

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

Residential proxy is recommended for production runs, though neither the review API nor the hotel-name resolver were observed to gate on IP reputation during recon.

## Actor input object example

```json
{
  "hotelUrls": [
    "https://www.booking.com/hotel/sg/marina-bay-sands.html"
  ],
  "sortBy": "most_relevant",
  "maxReviewsPerHotel": 200,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "hotelUrls": [
        "https://www.booking.com/hotel/sg/marina-bay-sands.html"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/booking-reviews-scraper").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 = { "hotelUrls": ["https://www.booking.com/hotel/sg/marina-bay-sands.html"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/booking-reviews-scraper").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 '{
  "hotelUrls": [
    "https://www.booking.com/hotel/sg/marina-bay-sands.html"
  ]
}' |
apify call scrapyx/booking-reviews-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/dCqe8MU04QrLy0la8/builds/Sds10H6irJDy9S4FO/openapi.json
