# Booking.com Reviews Scraper: Hotel Guest Reviews (`aleksandrbooking/booking-reviews-scraper`) Actor

Get the guest reviews of any Booking.com hotel: score, title, liked and disliked text, reviewer country and type of trip, room, dates of stay and the hotel reply. Sort and filter by score, language, guest type or words. Plain HTTP, no browser. Not affiliated with Booking.com.

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

## Pricing

from $0.80 / 1,000 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?

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

Get the guest reviews of any Booking.com hotel: the score, the title, what guests liked and disliked, the reviewer's country and kind of trip, the room, the dates of the stay and the hotel's reply. Give it hotel links, hotel numbers, or just a city, and it reads the reviews of the hotels there. One dataset row per review.

There is no login, no Booking.com account, no partner API and no browser. The Actor reads the same review list Booking.com's own hotel page loads, over plain HTTP, which is why it runs in 512 MB, reads about eighty reviews a second and can be priced low. Run it once, put it on a schedule, or call it from the API, and forward the result to Google Sheets, Slack, Make, Zapier or an MCP client.

### Who is this Booking.com reviews scraper for?

| You are | What you run | What you get |
|---|---|---|
| A hotel owner or reputation manager | your hotel and four competitors, the last 30 days | what guests say about you and about them, by score, language and kind of trip |
| An analyst or researcher | the most reviewed hotels of a city, a few thousand reviews | review text with scores and metadata, ready for sentiment or topic work |
| A travel agency or destination marketer | a destination, the top hotels | which hotels guests praise and what they complain about |
| Building a product or an AI workflow | hotels through the API, new reviews every day | clean rows to forward to a database, a model or an alert |

### What makes this different from other Booking.com review scrapers?

- **Three ways in.** Paste hotel links, give Booking.com's own hotel numbers, or name a place and let the Actor read the reviews of the hotels Booking.com lists there. No link list is needed for a city.
- **Booking.com does the filtering.** Sort order, score band, review language, kind of trip and a word search are applied by Booking.com's server, so you read and pay only for the reviews you asked for. A search for the 32 poor reviews of a hotel with 1,200 reviews reads two pages, not fifty.
- **Daily runs are cheap.** With "newest first" and a start date the Actor stops at the first review that is older, so a daily run for yesterday's reviews costs one request per hotel and bills only what is new.
- **Complete and without repeats.** Measured on 11 October 2026 on the Apify platform: a hotel with 30,730 reviews was read to its last review, 30,730 rows, all different, in 6 minutes 29 seconds, with a peak of 153 MB of memory.
- **It says what it could not do.** A hotel link that cannot be matched to a hotel is listed with the reason, not guessed and not billed. A hotel that stopped answering is listed with the number of reviews delivered before that.

### What data does it return?

One row per review.

| Field | What it is |
|---|---|
| **id** | Booking.com's own id of the review. Stable between runs, so it is the key to de-duplicate a schedule on. |
| **hotelId**, **propertyId**, **hotelName**, **hotelUrl** | The hotel. `hotelId` is the country and page name from the hotel's link, `propertyId` is Booking.com's number for it, the same number the Booking.com Scraper Actor returns as `hotelId`. |
| **reviewPage** | The page of the review list (25 reviews a page) the review was read from, counted from 1. |
| **userName**, **userLocation**, **userCountryCode** | The reviewer's display name, country and country code. `userName` is `null` for an anonymous reviewer. |
| **guestType**, **customerType** | The kind of trip as Booking.com words it ("Family") and as its code (`FAMILIES`, `COUPLES`, `GROUP_OF_FRIENDS`, `SOLO_TRAVELLERS`). |
| **roomInfo**, **roomId** | The room the guest stayed in. |
| **stayDate**, **checkInDate**, **checkOutDate**, **stayLength**, **numNights**, **stayStatus** | When they stayed: the month, the dates, the length, and "stayed". |
| **reviewDate**, **reviewedAt** | When the review was written, as "October 9, 2026" and as ISO-8601 UTC. |
| **reviewTitle** | The title of the review, or `null` when the guest wrote none. |
| **rating** | The score, 1 to 10. |
| **reviewTextParts** | The text: `Liked` and `Disliked`, each `null` when empty. |
| **reviewLanguage** | The language of the text as a code (`en`, `de`, `ru`...), or `null` when Booking.com could not tell. |
| **helpfulVotes** | How many guests found the review helpful. |
| **propertyReply** | The hotel's reply, `{ text, date }`, or `null`. |
| **photoUrls** | Links to the photos the guest attached, largest size. |
| **customData** | What you attached to the hotel link as `userData`. With a place, `customData.destination` is the place the hotel came from. |
| **scrapedAt** | When it was collected, ISO-8601 UTC. |

An example row. The reviewer's name and the text of the hotel's reply are replaced; the rest is as Booking.com returned it.

```json
{
  "id": "7b598841bbd9be60",
  "hotelId": "ge/radisson-blu-batumi",
  "propertyId": 320991,
  "hotelName": "Radisson Blu Hotel Batumi",
  "hotelUrl": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html",
  "reviewPage": 1,
  "userName": "Anna",
  "userLocation": "United Kingdom",
  "userCountryCode": "gb",
  "guestType": "Family",
  "customerType": "FAMILIES",
  "roomInfo": "SPA Suite with Sea View",
  "roomId": 32099106,
  "stayDate": "October 2026",
  "checkInDate": "2026-10-04",
  "checkOutDate": "2026-10-07",
  "stayLength": "3 nights",
  "numNights": 3,
  "stayStatus": "stayed",
  "reviewDate": "October 9, 2026",
  "reviewedAt": "2026-10-09T18:20:26.000Z",
  "reviewTitle": "Great hotel for exploring Batumi.",
  "rating": 10,
  "reviewTextParts": {
    "Liked": "We had great room. SPA is very relaxing. Proximity to the old town offers many possibilities for dining.",
    "Disliked": "Nothing"
  },
  "reviewLanguage": "en",
  "helpfulVotes": 0,
  "propertyReply": {
    "text": "Dear Anna, thank you for staying with us and for the kind words. We hope to see you again.",
    "date": null
  },
  "photoUrls": [],
  "customData": {},
  "scrapedAt": "2026-10-11T12:00:00.000Z"
}
```

A second record, `HOTELS` in the run's key-value store, has one entry per hotel: how many reviews Booking.com lists for it, how many were delivered, why the read stopped, Booking.com's score breakdown (cleanliness, comfort, facilities, location, staff, value for money) and the number of reviews per language. `FAILED_HOTELS` lists what could not be read and why.

### How do I scrape Booking.com reviews?

1. Add what you want to read: hotel links in **Hotel links**, Booking.com hotel numbers in **Hotel numbers**, or places in **Places**. They can be mixed.
2. Choose how many reviews per hotel (the default is 100) and the order.
3. Add filters if you want a slice: a start date, score bands, languages, kinds of trip, a word.
4. Run it. Download the dataset as JSON, CSV or Excel, or read it through the API.

| Field | Default | What it does |
|---|---|---|
| `startUrls` | none | Links of Booking.com hotel pages, with any language and any parameters. Add `userData` to a link to get it back as `customData`. |
| `hotelIds` | none | Booking.com's numbers of hotels, such as `320991`. Exact and needs no lookup. |
| `destinations` | none | Places: a city, region, district, landmark, airport or country by name, or a pasted Booking.com search results link. Up to 50. |
| `maxHotelsPerDestination` | 10 | How many hotels to read for each place, 1 to 1,000. Hotels with no reviews are left out and do not count. |
| `hotelSort` | `popularity` | Which hotels of a place: Booking.com's ranking (`popularity`), the hotels with the most reviews (`most_reviewed`) or its best-score order (`top_rated`). |
| `maxReviewsPerHotel` | 100 | The most reviews per hotel, 1 to 1,000,000. With the filters below it counts the reviews that pass them. |
| `sortReviewsBy` | `f_relevance` | Booking.com's own orders: `f_relevance`, `f_recent_desc` (newest first), `f_recent_asc`, `f_score_desc`, `f_score_asc`. |
| `cutoffDate` | none | Only reviews written on or after this day: `2026-01-01`, or a period back from today such as `30 days`, `6 months`. With newest first the Actor stops at the first older review. |
| `reviewScores` | all | Score bands: `review_adj_superb` (9+), `review_adj_good` (7 to 9), `review_adj_average_passable` (5 to 7), `review_adj_poor` (3 to 5). Each band is read separately. |
| `reviewLanguages` | all | Language codes: `en`, `de`, `fr`, `es`, `it`, `ru`, `tr`, `ar`, `he`, `zh`. |
| `guestTypes` | all | `COUPLES`, `FAMILIES`, `GROUP_OF_FRIENDS`, `SOLO_TRAVELLERS`, `BUSINESS_TRAVELLERS`. |
| `searchText` | none | Only reviews whose text contains this word or phrase. Booking.com does the search. |
| `skipReviewsWithoutText` | `false` | Leave out reviews that are only a score. They are not billed. |
| `concurrency` | 6 | Requests at the same time across all hotels, 1 to 16. |
| `proxy` | Apify datacenter | What the Actor is priced on. Other Apify proxy groups are refused; your own proxies are accepted. |

#### Examples

The newest 100 reviews of one hotel:

```json
{ "startUrls": [{ "url": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html" }], "maxReviewsPerHotel": 100, "sortReviewsBy": "f_recent_desc" }
```

Yesterday's new reviews of three hotels, every day (put it on a schedule and de-duplicate on `id`):

```json
{ "hotelIds": [320991, 359372, 1054549], "maxReviewsPerHotel": 1000, "sortReviewsBy": "f_recent_desc", "cutoffDate": "1 day" }
```

Only the complaints, English and German, of one hotel:

```json
{ "hotelIds": [320991], "reviewScores": ["review_adj_poor", "review_adj_average_passable"], "reviewLanguages": ["en", "de"], "maxReviewsPerHotel": 500 }
```

What guests say about breakfast, in the 20 most reviewed hotels of Lisbon:

```json
{ "destinations": ["Lisbon"], "hotelSort": "most_reviewed", "maxHotelsPerDestination": 20, "searchText": "breakfast", "maxReviewsPerHotel": 200 }
```

### Where do I get hotel numbers?

Booking.com's review list is keyed on a number for each hotel (Radisson Blu Hotel Batumi is `320991`), and a hotel's link does not contain it. Three ways to give the Actor what it needs:

- **A place** in `destinations`. The hotels come with their numbers, so every hotel found is read.
- **The `hotelId` column of the [Booking.com Scraper](https://apify.com/aleksandrbooking/booking-scraper) Actor's output**, which lists hotels by place with prices. Pass the column to `hotelIds`. This is exact.
- **A link** in `startUrls`. The Actor looks the hotel up by the name in the link and accepts a match only when the page name and the country are the same as the link's. A link keeps the name a hotel had when its page was made, so renamed hotels and long transliterated names are not always found. On a mixed sample of 150 hotels from twelve big cities, about two links in three were found. The links that are not found are listed in `FAILED_HOTELS` with the reason and are not billed; give those as numbers.

### How much does it cost to scrape Booking.com reviews?

**One billed row is one review.** Nothing else is charged except the platform's own two small events.

| Event | Price | When |
|---|---|---|
| Review | $0.0015, falling to $0.0008 by plan | Per review row that reaches the dataset |
| Actor start | $0.00005 | Once per run; at 512 MB it is a single start event |
| Result | $0.00001 | Per stored row, about one cent per thousand rows |

The review price steps down with your Apify plan: $0.0015 on the free plan and on Bronze, $0.0012 on Silver, and $0.0008 on Gold and above.

What that means for real jobs, all three charges included:

| Job | Rows | Free plan | Gold and above |
|---|---|---|---|
| 100 reviews of one hotel | 100 | **$0.15** | **$0.08** |
| 1,000 reviews of one hotel | 1,000 | **$1.51** | **$0.81** |
| Every review of a hotel with 1,200 reviews | 1,200 | **$1.81** | **$0.97** |
| The 10 most reviewed hotels of a city, 100 reviews each | 1,000 | **$1.51** | **$0.81** |
| Every review of a hotel with 30,000 reviews | 30,000 | **$45.30** | **$24.30** |

Reviews you filter out, reviews that are only a score when you skip them, and hotels that cannot be read are not billed. Set **Maximum total charge** on the run to cap the cost; the Actor stops cleanly when it is reached and says so in `FAILED_HOTELS`.

### What should I know about the data?

- **Booking.com lists the reviews of the last three years.** The oldest review measured was dated exactly three years back. Older reviews are not there for anyone, and a hotel's review count includes only the listed ones.
- **About half of the reviews are only a score.** Measured on two hotels: 43% and 53% of the reviews had any text. They are delivered by default so that your count matches Booking.com's, and `skipReviewsWithoutText` leaves them out.
- **Hotel replies to the newest reviews can be missing.** Measured on 11 October 2026: the reply to the two newest reviews of one hotel was there in some requests and not in others, while every older page was identical every time. A reply posted in the last day may not be in a run that reads it right away; a later run has it.
- **The language code is Booking.com's.** `null` means it could not tell. Review text is delivered as written, not translated.
- **Place lists hold the hotels that have a room tomorrow.** That is how Booking.com's search works: a fully booked hotel is not listed. One search reaches about 1,000 hotels of a place; for a hotel that is not listed, give its number.
- **Reviewer names are public on Booking.com and are personal data in many countries.** The Actor collects what a visitor of the hotel page sees. What you may do with names and countries is your decision under the rules that apply to you; if you do not need them, drop the columns.
- **Booking.com can change the page.** Every response is checked, and a hotel whose answer is not a review list is reported instead of billed.

### FAQ

**Do I need a Booking.com account or a partner API key?** No. The Actor reads what any visitor of a hotel page can read.

**How do I get only new reviews?** Run it on a schedule with `sortReviewsBy` set to `f_recent_desc` and `cutoffDate` set to a period slightly longer than the schedule ("2 days" for a daily run), and de-duplicate on `id`. The Actor stops at the first review older than the cutoff, so the run is short and bills only the recent reviews.

**Why was a hotel link not read?** The reason is in `FAILED_HOTELS`. Most often the name in the link does not match what the hotel is called now. Give its number in `hotelIds`.

**Can I read reviews in one language?** Yes: `reviewLanguages`, for example `["en"]`. The `HOTELS` record shows how many reviews each language has.

**Why do I get fewer reviews than the hotel's total?** The total on the hotel page counts what Booking.com lists under your filters; `maxReviewsPerHotel` stops the read at the number you set, and `reviewsOnBooking` in `HOTELS` is the hotel's whole count.

**Is it allowed?** The Actor reads public pages of Booking.com without logging in. Booking.com's terms and the data protection rules of your country apply to what you do with the data; read them before you build on it. This Actor is not affiliated with or endorsed by Booking.com.

### Other Actors for Booking.com data

- [Booking.com Scraper](https://apify.com/aleksandrbooking/booking-scraper): the hotels of a city, region, landmark or airport with prices, ratings and availability for your dates. Its `hotelId` column is what this Actor takes as a hotel number.
- [Booking.com Hotel Price Monitor](https://apify.com/aleksandrbooking/booking-hotel-price-monitor): one hotel's price and availability for each date of the next year.
- [Booking.com Hotel Price Calendar Scraper](https://apify.com/aleksandrbooking/booking-rate-calendar): every room rate of a hotel with its cancellation and prepayment terms.

# Changelog

This Actor's version history is a separate document: https://apify.com/aleksandrbooking/booking-reviews-scraper/changelog.md

# Actor input Schema

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

Links of Booking.com hotel pages, like https://www.booking.com/hotel/ge/radisson-blu-batumi.html. Any language version and any extra parameters work; one hotel is read once however many links name it. Add "userData" to a link to get it back on every review of that hotel under "customData". Booking.com does not put the hotel number in a link, so a link is looked up by name: about 7 in 10 are found. A link that is not found is listed in FAILED_HOTELS and is not charged; use "Hotel numbers" for those.

## `hotelIds` (type: `array`):

Booking.com's own numbers of hotels, like 320991: the "hotelId" column of the Booking.com Scraper Actor's output. A number is exact and needs no lookup, so use it when you have it. Can be combined with links.

## `destinations` (type: `array`):

A city, region, district, landmark, airport or country: the Actor reads the reviews of the hotels Booking.com lists for it, no hotel links needed. Type a name such as "Batumi" or "Lisbon, Portugal", or paste the link of a Booking.com search results page. Booking.com lists the hotels that have a room for tomorrow, up to about a thousand per place. Every review carries the place in "customData.destination". Can be combined with links and numbers.

## `maxHotelsPerDestination` (type: `integer`):

How many hotels to read for each place, in the order chosen below. Hotels that have no reviews are left out and do not count. A thousand hotels is about what one Booking.com search reaches.

## `hotelSort` (type: `string`):

Which hotels of a place you get when you ask for fewer than the place has: Booking.com's own ranking, the hotels with the most reviews (the whole list that one Booking.com search reaches is read to find them), or Booking.com's best-score order.

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

The most reviews to collect for each hotel. One row is one billable review. Booking.com lists reviews of the last three years only. With the filters below, the limit counts the reviews that pass them.

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

The order of Booking.com's own review list. With a limit below the number of reviews, this decides which reviews you get.

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

Keep reviews written on or after this day: a date like 2026-01-01, or a period back from today such as "30 days", "6 months", "1 year". With "Newest first" the Actor stops at the first older review, so a daily run for the last day costs one request.

## `reviewScores` (type: `array`):

Only reviews in these score bands, as Booking.com groups them. Each band is read separately, so a narrow band is cheap even for a hotel with thousands of reviews.

## `reviewLanguages` (type: `array`):

Only reviews written in these languages, as codes: en, de, fr, es, it, ru, tr, ar, he, zh. Empty means all.

## `guestTypes` (type: `array`):

Only reviews from these kinds of trips. Empty means all.

## `searchText` (type: `string`):

Only reviews whose text contains this word or phrase, for example "breakfast" or "noise". Booking.com does the search; empty means no text filter.

## `skipReviewsWithoutText` (type: `boolean`):

A part of Booking.com reviews are only a score with the stay details. They are delivered by default, and counted in the hotel's review total. Turn this on to get written reviews only; skipped reviews are not charged.

## `concurrency` (type: `integer`):

How many requests run at the same time, across all hotels. The default reads on the order of a hundred reviews a second.

## `proxy` (type: `object`):

Apify datacenter proxies are the default and what this Actor is priced on. Apify residential groups are not available here; a run that asks for one is refused in the first seconds, before any review is charged. For your own proxies choose Custom proxies and supply them.

## `dryRun` (type: `boolean`):

Produce data without charging events. Honored only for runs started by the Actor owner; ignored for everyone else.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html"
    }
  ],
  "hotelIds": [],
  "destinations": [],
  "maxHotelsPerDestination": 10,
  "hotelSort": "popularity",
  "maxReviewsPerHotel": 50,
  "sortReviewsBy": "f_relevance",
  "reviewScores": [
    "ALL"
  ],
  "reviewLanguages": [],
  "guestTypes": [],
  "skipReviewsWithoutText": false,
  "concurrency": 6,
  "proxy": {
    "useApifyProxy": true
  },
  "dryRun": false
}
```

# Actor output Schema

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

No description

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

No description

## `failedHotels` (type: `string`):

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html"
        }
    ],
    "maxReviewsPerHotel": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("aleksandrbooking/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 = {
    "startUrls": [{ "url": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html" }],
    "maxReviewsPerHotel": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("aleksandrbooking/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 '{
  "startUrls": [
    {
      "url": "https://www.booking.com/hotel/ge/radisson-blu-batumi.html"
    }
  ],
  "maxReviewsPerHotel": 50
}' |
apify call aleksandrbooking/booking-reviews-scraper --silent --output-dataset

```

## MCP server setup

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