kestrel actors › Guest & customer reviews
Booking.com Reviews Scraper — Hotel Guest Reviews & Ratings
A booking.com reviews scraper — a hotel review scraper for booking.com hotel reviews — that returns every guest review a property has, as structured rows: booking.com ratings on the 1–10 scale and out of five, the title, what the guest liked and what they disliked, the language it was written in, posting and stay dates, room, traveller type, reviewer name and country, guest photos and the property's public reply.
Run Booking.com Reviews Scraper on Apify →
Booking.com reviews scraper pricing: what one row costs
| Event | What it is | Price | Charged for |
|---|---|---|---|
review | Review row | $0.005 | One guest review: score on both the 1-10 and 5-star scales, title, what the guest liked and disliked, language, stay dates and room, traveller type, reviewer country, photos and the property's reply. Properties with no reviews are free. |
You pay per delivered review row. Everything else is free: hotel rows, status rows, reviews dropped by maxRating or requireText, properties with no reviews, and lookups that fail. The number of reviews you asked for is the number you pay for, capped by maxReviewsPerHotel and by whatever spending limit you set on the run. A 50-review complaints feed across ten properties is at most 500 review rows a day and usually far fewer, because most properties have few new complaints.
Booking.com reviews scraper input reference
Every field of Booking.com Reviews, straight from the actor's input schema.
| Field | Type | Default | What it does |
|---|---|---|---|
startUrls | array | [] | Booking.com property pages, e.g. https://www.booking.com/hotel/pt/memmo-alfama.html — any language variant (.en-gb.html, .de.html) and query string is fine. The property is resolved through Booking's own autocomplete, and the resolved name comes back in the hotel and status rows so you can check the match. |
hotelNames | array | [] | Property names as you would type them into Booking's search box, e.g. "Memmo Alfama Lisbon" — add the city to disambiguate chains. The first hotel Booking's autocomplete returns is used; its full name comes back in the rows. |
hotelIds | array | [] | Booking's numeric property ids, e.g. 536251 — what an earlier run's hotel_id gives you. The fastest path: no lookup at all. Ids carry no name, so give a URL or a name if you want hotel_name filled. |
maxReviewsPerHotel | integer | 200 | 0 = every review the property has (Booking pages them 25 at a time; big hotels run into the thousands). N = the first N in the order below. The main cost control. Range 0-20000. |
reviewsSort | string (enum) | "most_recent" | Which reviews come first. "Lowest rated" is the fastest way to find complaints; "Most recent" is the one to schedule. One of: most_recent, highest_rated, lowest_rated, most_relevant, oldest. |
languages | array | [] | Leave empty for every language. Otherwise the language codes to keep — en, de, fr, es, it, pt, nl, ru, ja, zh, ko, tr … — as Booking's own language filter uses them. Booking filters server-side, so the reviews you skip are never fetched or charged. The hotel row lists how many reviews each language holds. |
travelerType | string (enum) | "all" | Keep only reviews from one kind of guest, filtered by Booking itself (skipped reviews are never fetched or charged). One of: all, families, couples, friends, solo, business. |
keyword | string | "" | A word or phrase Booking searches the reviews for ("breakfast", "noise", "parking"). Server-side, so only matching reviews are fetched and charged. Leave empty for all. |
maxRating | number | 0 | 0 = keep every review. 1-10 on Booking's scale = keep only reviews at or below it. Reviews are then read lowest score first and paging stops at the threshold, so a complaints feed reads only the pages that carry complaints. Filtered reviews are never charged. Range 0-10. |
requireText | boolean | false | Deliver and pay for only the reviews where the guest wrote something in "liked" or "disliked" — Booking allows title-only reviews, and this drops them before billing. |
locale | string | "en-US" | Booking locale for the request, e.g. en-US, en-GB, de-DE, fr-FR. It sets the language of Booking's labels (traveller type, country names); reviews always come back in the language they were written in. |
includeHotelRow | boolean | true | Also emit one row per property with its id, name and location, Booking's seven category scores (staff, facilities, cleanliness, comfort, value, location, Wi-Fi), the review count and how many reviews each language and traveller type holds. Never charged. |
sessions | integer | 4 | How many proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately. Range 1-16. |
perIp | number | 1 | Pace for each session. Booking answered 10 calls at 1/s from one IP without a hiccup in testing; 1/s per IP is the default. Range 0.1-3. |
proxyConfiguration | object | {"useApifyProxy": true} | Apify Proxy is required so runs rotate IPs. The default datacenter group works for Booking's review endpoint (only Booking's HTML pages need a browser, and this actor never loads one). |
Example input
{
"startUrls": ["https://www.booking.com/hotel/pt/memmo-alfama.html", "https://www.booking.com/hotel/pt/the-lumiares.en-gb.html"],
"maxReviewsPerHotel": 50,
"reviewsSort": "most_recent",
"maxRating": 6,
"requireText": true
}
{
"hotelNames": ["Memmo Alfama Lisbon", "The Lumiares Lisbon", "Hotel Valverde Lisboa"],
"maxReviewsPerHotel": 0,
"languages": ["en"],
"requireText": true
}
Booking.com reviews scraper output fields
One row per guest review (charged), plus a free hotel context row and a free status row per property.
Dataset views in the Apify Console: Overview, Reviews, Complaints, Hotels, Status.
| Field | Type | What it holds |
|---|---|---|
type | string | review | hotel | status |
hotel_id | string | Booking.com's numeric property id |
hotel_name | string | Property name as Booking's autocomplete resolved it (null when the input was a bare id) |
url | string | The Booking.com URL you gave, if any |
locale | string | The site locale the labels came in |
city | string | |
region | string | |
country | string | |
country_code | string | |
latitude | number | |
longitude | number | |
score_staff | number | Booking's category score, 1-10 |
score_facilities | number | |
score_cleanliness | number | |
score_comfort | number | |
score_value | number | |
score_location | number | |
score_wifi | number | |
review_count | integer | Booking's total number of reviews for the property |
languages | array | [{code, count}] — how many reviews each language holds |
traveler_types | array | [{type, count}] — FAMILIES, COUPLES, GROUP_OF_FRIENDS, SOLO_TRAVELLERS, BUSINESS_TRAVELLERS |
seasons | array | [{season, count}] — Booking's time-of-year buckets |
reviews_fetched | integer | |
review_id | string | Booking's review key (stable across runs) |
rating | number | 1-10, Booking's scale |
rating_5 | number | The same score out of five |
title | string | |
positives | string | What the guest liked |
negatives | string | What the guest disliked |
text | string | Liked and disliked joined into one comment (null for score-only reviews) |
language | string | Language code of the review text |
review_date | string | YYYY-MM-DD |
check_in | string | |
check_out | string | |
nights | integer | |
room_type | string | |
room_id | string | |
traveler_type | string | Couple, Family, Solo traveller … in the site language |
traveler_type_code | string | Booking's code: COUPLES, FAMILIES, GROUP_OF_FRIENDS, SOLO_TRAVELLERS, BUSINESS_TRAVELLERS |
stay_status | string | |
reviewer_name | string | |
reviewer_country | string | |
reviewer_country_code | string | |
anonymous | boolean | |
helpful_votes | integer | |
photos | array | Guest photo URLs |
approved | boolean | |
response | string | The property's public reply |
target | string | The input this status row is about |
status | string | ok | no_reviews | error |
reviews | integer | Review rows delivered for this property |
filtered | integer | Reviews fetched but dropped by maxRating / requireText (never charged) |
pages | integer | |
total | integer | Booking's review count for the property (after server-side filters) |
duplicates | integer | 1 when another input already covered this property |
error | string | |
fetched_at | string |
Example row from a real run
Taken from the actor's README — this is the shape your dataset holds.
{
"type": "review",
"url": "https://www.booking.com/hotel/pt/memmo-alfama.html",
"locale": "en-US",
"review_id": "c90f61915f999a43",
"rating": 10.0,
"rating_5": 5.0,
"title": "Best place to stay in Lisbon!",
"positives": "Great location and amazing terrace.",
"negatives": null,
"text": "Great location and amazing terrace.",
"language": "en",
"review_date": "2026-08-28",
"check_in": "2026-08-24",
"check_out": "2026-08-27",
"nights": 3,
"room_type": "Premium Double or Twin Room",
"room_id": "53625108",
"traveler_type": "Couple",
"traveler_type_code": "COUPLES",
"stay_status": "stayed",
"reviewer_name": "Jean",
"reviewer_country": "Canada",
"reviewer_country_code": "ca",
"anonymous": false,
"helpful_votes": 0,
"photos": null,
"approved": true,
"response": null,
"hotel_id": "536251",
"hotel_name": "Memmo Alfama - Design Hotels",
"fetched_at": "2026-08-29T10:12:04+00:00"
}
Booking.com reviews scraper in Python, JavaScript and curl
Python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/booking-reviews-scraper").call(run_input={
"startUrls": ["https://www.booking.com/hotel/pt/memmo-alfama.html"],
"maxReviewsPerHotel": 100,
"reviewsSort": "most_recent",
})
reviews = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "review"]
for r in reviews[:5]:
print(r["rating"], r["traveler_type"], r["title"], "|", r["negatives"])
JavaScript (Node)
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/booking-reviews-scraper').call({
hotelNames: ['Memmo Alfama Lisbon'], maxReviewsPerHotel: 0, maxRating: 6, requireText: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const complaints = items.filter(r => r.type === 'review');
console.log(complaints.length, 'complaints;', complaints.filter(r => r.response).length, 'answered by the hotel');
curl
curl -X POST "https://api.apify.com/v2/acts/kestrel~booking-reviews-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"hotelIds": ["536251"], "maxReviewsPerHotel": 25, "languages": ["en"]}'
The same three calls are runnable files in the examples repository: examples/booking-reviews-scraper.py|.js|.sh.
Limits and honest notes
- Score-only reviews are real. Booking lets guests rate without writing; in a recent sample 11 of 25 reviews had no words.
requireTextdrops them before billing. - Ids carry no name. An id resolves to reviews and scores but not to a name — Booking's lookup runs from name to id, not back. Give a URL or a name if you need
hotel_namefilled. - Unknown ids look like empty properties. Booking answers a wrong id with zero reviews, which is reported as
no_reviews(free). A URL or a name that Booking cannot find is reported aserror(also free). - Filters are Booking's. Language codes must be the ones Booking uses (the hotel row lists them with counts); a code Booking rejects fails that property with a clear message rather than silently harvesting everything.
- No room-level or topic filter. Booking's endpoint accepts language, traveller type and keyword; score and topic filters are applied by this actor after fetching (
maxRatingreads ascending so it rarely over-fetches). - Pace. Booking answered ten calls a second from one IP in testing; the default of one call per second per session is conservative. Each call returns 25 reviews.
FAQ
Does it need a Booking.com API key or login?
No. There is no public Booking.com reviews API for reading reviews; the actor uses the request Booking's own review list makes, which needs no key, no cookies and no browser.
Can I download Booking.com reviews as CSV or Excel?
Yes. Every run writes an Apify dataset; open the Reviews or Complaints view and export booking.com reviews csv, Excel, JSON or XML, or fetch the same through the API with ?format=csv.
How do I get only Booking.com negative reviews?
Set maxRating to 6 (Booking's own wording: 5 is "Poor", 7 is "Good"). The property is read lowest score first and paging stops at the threshold, so you pay only for the complaints. Add requireText: true to skip score-only ratings.
Do I need the hotel id, or can I use the URL?
Either — or the name. URLs and names are resolved through Booking's autocomplete and the matched name comes back in every row; ids skip the lookup. Once you have a hotel_id from a run, use it for the fastest repeat runs.
How many reviews can one hotel return?
All of them. Booking pages reviews 25 at a time and the actor keeps going until the property is exhausted or maxReviewsPerHotel is reached. A large city hotel with 15,000 reviews is 600 calls.
Are reviews translated, and which languages come back?
Reviews come back as written, with language set — multi language hotel reviews from one property, or booking.com reviews by language when you set languages. The hotel row lists every language the property's reviews come in, with counts, so you can pick what to fetch with languages. Booking's labels (traveller type, country names) follow locale.
Does it include the hotel's replies?
Yes — response carries the property's public reply when there is one.
What is traveler_type_code?
Booking's own guest category on the booking behind the review: COUPLES, FAMILIES, GROUP_OF_FRIENDS, SOLO_TRAVELLERS or BUSINESS_TRAVELLERS. traveler_type is the same thing in the site language.
Can I scrape Booking.com reviews without an API?
That is what this is — booking.com reviews without api keys: scrape booking.com reviews by URL, name or id, no key, no browser, pay per review.
Can I use it for hotel sentiment analysis?
Yes. positives and negatives are already split, text joins them for a single column, every row is tagged with language, rating and traveler_type, and requireText keeps the corpus clean.
What does bulk work cost?
Reviews only. A bulk hotel reviews pull of one hundred properties at 200 reviews each is at most 20,000 review rows; with requireText on it is typically 12,000, because score-only reviews are free.
How to use Booking.com Reviews Scraper
Ready-made runs you can open and start
Each one is a saved input on the Apify Store — open it, press Start, and it runs with the fields already filled in. All 163 across the suite.
| Ready-made run | What it does |
|---|---|
| Booking.com Negative Reviews for Any Hotel | Booking.com reviews scored six or below with a written complaint, lowest first: what goes wrong at one Singapore property and how often. |
| Download Booking.com Reviews to CSV | The hundred newest Booking.com reviews of one Berlin hotel with score, title, liked and disliked text, room type and stay dates, exportable as CSV or JSON. |
| Booking.com Reviews by Hotel ID | Four properties by Booking.com hotel id in one run, twenty-five newest reviews each - no lookup step; the ids come from any earlier run's hotel_id column. |
| Booking.com Reviews by Language: German Only | Only German-language reviews of two Vienna and Munich grand hotels, filtered by Booking.com itself so reviews in other languages are never billed. |
| Booking.com Competitor Reviews: Las Vegas Strip | Three Las Vegas Strip resorts compared on Booking.com: seven category scores and review counts per property in one table, thirty newest reviews each. |
| Hotel Guest Reviews from Business Travellers | Only business travellers' Booking.com reviews of two Frankfurt airport hotels, filtered by Booking itself, forty per property, leisure stays never billed. |
| Hotel Review Data by Keyword: Noise Complaints | Only Booking.com reviews that mention noise at one Barcelona hotel, searched server-side so nothing else is fetched or billed - swap in any keyword. |
| Booking.com Guest Reviews from Families | Eighty newest Booking.com reviews written by families at one Dubai resort, filtered by traveller type on Booking's side, with room type and stay dates. |
| Hotel Reputation Monitoring on Booking.com | Twenty newest Booking.com reviews for each of four Berlin properties by hotel id - small enough to schedule daily and alert on when a score drops. |
| Booking.com Ratings: Highest-Scored Reviews First | The highest-scored Booking.com reviews with a written comment for one Tokyo hotel - quotable testimonials with reviewer country and stay dates attached. |
| Booking.com Reviews Without API Access | Booking.com has no public review API. This is the smallest replacement: one property URL, forty newest reviews, no key, no partner contract, no browser. |
| Hotel Sentiment Analysis Dataset from Booking.com | Ninety Booking.com reviews with the liked and disliked text already separated by the guest - a pre-labelled set for hotel sentiment analysis work. |
Related scrapers
Run Booking.com Reviews Scraper
The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/booking-reviews-scraper