Booking Scraper avatar

Booking Scraper

Pricing

from $4.99 / 1,000 results

Go to Apify Store
Booking Scraper

Booking Scraper

๐Ÿจ Booking Scraper pulls structured hotel data from Booking.com โ€” prices, availability, reviews, ratings, photos, amenities & location. โš™๏ธ Scheduling, proxy-ready, retries. ๐Ÿ“Š Export CSV/JSON or API. ๐Ÿš€ Ideal for price monitoring, market research, lead gen & travel SEO.

Pricing

from $4.99 / 1,000 results

Rating

0.0

(0)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

13

Total users

2

Monthly active users

15 days ago

Last modified

Share

Booking.com Scraper โ€” Hotel Prices, Rooms, Ratings and Facilities

Booking.com Scraper extracts public Booking.com hotel detail pages and search results into structured hotel rows: name, lowest available room price, guest rating and review count, star rating, address and GPS coordinates, room list, facilities, photos and trader/host details. Every row is typed JSON with the same 36 keys on every run โ€” no HTML parsing, no selectors. Enter a destination or paste Booking.com URLs and press Start.

โš ๏ธ Read this before your first run. Thirteen of the eighteen inputs โ€” dates, occupancy, currency, language, sorting and every filter โ€” are used only to build a destination search. When you supply startUrls, the Actor fetches those URLs exactly as given and those inputs have no effect. Put your dates and filters in the URL query string instead.

What is Booking.com Scraper?

Booking.com Scraper is an Apify Actor that turns a destination search or a list of Booking.com URLs into a clean accommodation dataset. It drives a headless Chromium browser through an Apify Residential proxy, clears Booking.com's AWS WAF challenge, then reads each property page's embedded Apollo GraphQL state, schema.org JSON-LD and window.booking variables โ€” merging all three into one row per property.

No Booking.com account, login or cookie is required. Every field it returns is visible to any anonymous visitor on the property page.

It is built for revenue managers benchmarking competitor rates, travel analysts and OTAs building accommodation feeds, and developers piping hotel supply and pricing data into their own tools.

What Booking.com hotel data is publicly available to scrape?

Booking.com renders the full public offer on a property page before any login: name, description, star rating, guest score, address, photos, room types, facilities, house rules and โ€” when a date range is set โ€” availability pricing. What sits behind a gate is the guest's own booking record, not the property data.

Data CategoryPublicly AvailableBehind a Booking.com account / partner login
Property name, description, type, star ratingโœ… Publicโ€”
Guest review score, review count, category sub-scoresโœ… Publicโ€”
Street address, city, country, GPS coordinatesโœ… Publicโ€”
Room names, sizes, occupancy and per-room pricesโœ… Public โ€” needs a date range in the requestโ€”
Property and room photo galleriesโœ… Publicโ€”
Facilities, highlights, house rules, fine printโœ… Publicโ€”
Trader / host identity and registration detailsโœ… Public โ€” Booking.com publishes these under EU trader-transparency rulesโ€”
Individual guest review text and reviewer namesโŒ Not returned by this Actorโ€”
Your own reservations, payments and messagesโŒAccount
Extranet rates, occupancy and revenue reportsโŒPartner extranet

Booking.com Scraper only returns publicly visible data โ€” what any visitor sees on a property page. Nothing behind a login wall.

What data can I extract with Booking.com Scraper?

Every row carries five groups of data: what the property is, what it costs, where it is, what is inside it, and who legally operates it.

Field NameDescription
order1-based position of the property in this run's URL list
urlCanonical property URL from the page's JSON-LD, falling back to the URL fetched
source_urlThe exact Booking.com URL the Actor fetched for this row
startUrlOrQueryThe search value if one was supplied, otherwise the property URL
nameProperty name
typeBooking.com's property-type anchor; the literal string "hotel" when the page does not expose one
descriptionLong-form property description
starsOfficial star rating as a number, null for unrated properties
priceLowest room price found on the page โ€” see the pricing note below
currencyCurrency code the property quotes in, from Booking.com's own page variables; "USD" when absent
ratingGuest review score out of 10, as a float
ratingLabelBooking.com's localised score word, e.g. "Superb"
reviewsNumber of guest reviews, integer, 0 when absent
breakfast"Available" when a breakfast signal is found, otherwise null
checkInThe property's check-in time window, e.g. "15:00 - 00:00"
checkOutThe property's check-out time window, e.g. "Until 12:00"
checkInDateThe checkIn date you supplied as input, normalised to YYYY-MM-DD
checkOutDateThe checkOut date you supplied as input, normalised to YYYY-MM-DD
locationObject: lat, lng โ€” GPS coordinates as floats
addressObject: full, city, country
imageFirst photo URL found, "" when none
imagesProperty photo URLs, capped at 50 by the Actor
roomImagesArray of objects: roomId, images โ€” photos grouped per room type
roomsArray of room objects: id, name, description, size, occupancy, price
facilitiesArray of objects: name, id
highlightsFacility-highlight titles as a string array
finePrintBooking.com's fine-print paragraphs joined into one string
policiesReal house-rule phrases โ€” check-in requirements, age limits, groups, smoking, quiet hours, parties, curfew, city regulations
categoryReviewsArray of objects: name, score โ€” per-category guest sub-scores such as Staff, Cleanliness, Location
hotelChainChain or brand name, null for independents
hotelIdBooking.com's internal property ID, as a string
licenseInfoFirst licence / registration number published for the property
hostInfoHost display name, or the trader's first and last name when no company name is published
traderInfoObject with the property's legal trader record โ€” see the table below
breadcrumbsArray of objects: name, url โ€” Booking.com's own location hierarchy for the property
timeOfScrapeISOUTC ISO 8601 timestamp of when the row was built

source_url is the only snake_case key in the row โ€” every other key is camelCase. It is kept that way so existing field mappings do not break.

Identity, location and rating fields

name, hotelId, type, description and stars identify the property. address gives you full (street line only), city and country as an uppercased ISO code, and location gives lat / lng as floats for geo-analysis. breadcrumbs carries Booking.com's own country โ†’ region โ†’ city hierarchy, which is more consistent than parsing the address string.

rating, reviews, ratingLabel and categoryReviews are the guest-sentiment block. ratingLabel comes from Booking.com's localised score text when the page provides it; when it does not, the Actor derives it from the score and returns an English word (Superb at 9+, Very good at 8+, Good at 7+, Pleasant at 6+) regardless of the language you selected. Scores under 6 with no upstream label return null.

Price, room and availability fields

price is the lowest price found across the property's room blocks โ€” not a nightly average and not a labelled stay total. Three sources are tried in order: the minimum room-block amount from the Apollo state, then Booking.com's own b_cheapest_price_that_fits_search_eur page variable, then the visible price element in the DOM.

That middle fallback is denominated in euros by Booking.com regardless of your currency input, while the currency field reports the currency the property quotes in. If you need a guaranteed currency match, read rooms[].price โ€” those come from the room blocks in the page's own currency โ€” and treat a price with no matching room entry as approximate.

rooms gives you name, description, size, occupancy (max guests) and price per room type. Room-level prices and availability only appear when Booking.com has a date range to price against, which is why the input schema groups checkIn and checkOut under "Set dates to get additional information (e.g. price)". Run without dates and expect price to be null on many properties and rooms to carry names only.

Facilities, policy and trader fields

facilities, highlights, finePrint and policies describe what the property offers and what it demands. policies is built from Booking.com's real house-rule phrases rather than being inferred from the facility list, and returns [] when a page publishes none. breakfast is "Available" or null only โ€” it never returns a negative, so null means "no breakfast signal found", not "no breakfast".

traderInfo is the property's legal operator record, which Booking.com publishes for EU trader transparency:

traderInfo keyDescription
isBusinessWhether Booking.com flags the operator as a trader
companyNameRegistered legal company name
firstNameTrader contact first name, for individually operated properties
middleNameTrader contact middle name
lastNameTrader contact last name
emailPublished trader email address
phonePublished trader phone number
registrationNumberCompany registration number
tradeRegisterNameName of the trade register the company is entered in
addressObject: street, street2, postalCode, city, countryCode, state

traderInfo.address.postalCode is the only place a postal code appears โ€” the top-level address.full is the street line only. licenseInfo returns the first licence number published for the property, even when the page lists several.

๐Ÿค– Add-on: Need additional accommodation data?

Two Scraper-Engine Actors pair naturally with this one. Apartments.com Scraper covers the long-stay and residential rental side of the same market, with rent, beds, baths and availability. Google Maps Scraper With Reviews Sentiment Analysis adds guest review text and sentiment for the same properties, which this Actor deliberately does not collect.

Why not build this yourself?

Booking.com's programmatic route is the Demand API, and it is not self-serve: its published prerequisites require you to be a registered Booking.com Managed Affiliate Partner, with Partner Centre access "provided by your Booking.com Account Manager after signing the agreed contract", before you can generate the API key and X-Affiliate-Id a request needs (Booking.com Demand API prerequisites, checked 25 July 2026). Without that partnership, scraping the public pages is the alternative โ€” and the cost sits in four places.

Anti-bot. Booking.com is protected by AWS WAF. A browser-impersonating HTTP client gets 302-redirected, and a single Playwright goto does not clear the JavaScript challenge โ€” you get a challenge shell of a few kilobytes instead of a page. Clearing it means a real Chromium, waiting for the aws-waf-token cookie to be issued, reloading until the genuine page renders, and โ€” critically โ€” keeping one browser context alive for the whole run so the solved token is reused. The obvious block check does not work either: a solved page still inlines the WAF challenge script, so detection has to be based on page size and page markers rather than on the WAF string.

Egress. Datacenter IPs do not survive this. Residential proxying is a running cost before a single row is parsed.

Parameter surface. A Booking.com search URL is not one flat query string. Dates, occupancy and currency are top-level parameters; star rating, property type and minimum review score are all packed into a single semicolon-joined nflt value using numeric property-type IDs; price range splits across price_range_min / price_range_max; language changes the domain path of the search endpoint, not just a parameter. Pagination is a 25-step offset.

Extraction. Property pages do not put their data in the visible HTML. It lives in an Apollo GraphQL store full of __ref pointers you have to resolve by hand, alongside schema.org JSON-LD and inline window.booking variables โ€” with different fields reliable in each. This Actor merges all four layers and falls back through them per field, so a moved key degrades one field rather than failing the row.

How to use Booking.com Scraper

Booking.com Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API.

  1. Open Booking.com Scraper on Apify and click Try for free
  2. Type a city, region or landmark into Destination (search) โ€” or paste Booking.com URLs into Start URLs instead
  3. Set Check-in date and Check-out date. No input is marked required in the schema, but without dates Booking.com has nothing to price against and price and rooms[].price will often be null
  4. Set Max results (maxItems) and adjust occupancy โ€” Number of rooms, adults, children
  5. Optionally narrow the search with Property type, Stars count, Minimum rating, Price range and Order results by
  6. Leave Proxy Configuration on Apify Residential โ€” Booking.com blocks direct traffic
  7. Click Start, then export the dataset as JSON, CSV, Excel, XML or HTML

A run with an empty search and an empty startUrls logs "Nothing to do" and exits without pushing any rows.

To build a start URL, run the search you want on booking.com in your browser and copy the address bar โ€” filters, dates and occupancy travel with the link. That is the only way to apply them in startUrls mode.

How to scale to bulk hotel extraction

startUrls is a list and accepts both property detail pages and search-results pages. A detail page becomes one row; a search-results page is paginated in steps of 25 until it has yielded enough properties. Plain strings and { "url": "..." } objects both work, and an undocumented urls key is accepted as an alias. Any entry that does not contain booking.com is silently dropped.

Two limits to plan around. First, search accepts a single destination string, not a list โ€” for many cities, either run the Actor once per city or paste one search-results URL per city into startUrls. Second, maxItems is applied as a total across the whole run, despite its title reading "per destination or URL": the combined list of property URLs is truncated to maxItems before scraping begins. Ten detail URLs with maxItems: 5 scrapes five properties, not ten. Set maxItems to the total row count you want.

What can you do with Booking.com hotel data?

  • ๐Ÿ’ฐ A revenue manager benchmarking a property re-runs the same destination and date range weekly, then diffs price and rooms[].price per hotelId to see where competitors moved before it shows up in their own occupancy.
  • ๐Ÿ—บ๏ธ A travel analyst mapping supply in a city pulls location.lat, location.lng, stars and type across a search and plots pricing tiers by district using breadcrumbs.
  • ๐Ÿจ An OTA product team building a comparison page ingests name, images, facilities, rating and reviews to populate property cards without assembling them by hand.
  • ๐Ÿ“‹ A compliance researcher auditing short-let regulation in a market reads licenseInfo, traderInfo.registrationNumber and traderInfo.isBusiness to separate registered operators from unregistered listings.
  • โญ A market researcher studying guest satisfaction ranks properties on rating, reviews and categoryReviews to find where cleanliness or staff scores diverge from the headline score.
  • ๐Ÿค– An AI engineer building a travel assistant indexes description, facilities, policies and finePrint into a vector store, so an agent can answer "which 4-star hotels near this district allow late check-in and include breakfast" against live property data instead of a stale snapshot.

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.

How does Booking.com Scraper handle rate limits and blocking?

Booking.com is protected by AWS WAF, and this Actor is built specifically around clearing it.

Every page is loaded in a headless Chromium browser via Playwright, routed through an Apify Residential proxy, with a desktop Chrome 131 user agent, a 1366ร—900 viewport, en-GB locale and automation-control blink features disabled. One browser context is created and reused for the entire run, so the aws-waf-token cookie earned by the first successful load is carried into every subsequent page load rather than being re-solved each time.

A page is accepted as real only when the HTML exceeds 60,000 characters and contains either the Apollo store marker or a property-card marker โ€” the WAF challenge string itself is not used as the block signal, because Booking.com inlines it on solved pages too. If a load does not pass that check, the Actor waits up to 15 seconds for the WAF token cookie to appear, pauses, and reloads, up to five times. If all five reloads fail, it tears the browser context down and rebuilds it on a fresh proxy IP, up to three times per URL. Navigation timeout is 90 seconds per attempt. Pages are fetched one at a time, with a one-second pause between search-result pages.

There is no third-party CAPTCHA-solving service involved, and none is required from you. When a property page cannot be recovered after all three context rotations โ€” or when no property name can be extracted from the HTML that did load โ€” the row is skipped with a log line and the run continues. No partial or placeholder row is written, so a blocked page lowers your row count rather than corrupting the dataset.

โฌ‡๏ธ Input

No input is marked required in the schema. In practice you need either search or startUrls. All eighteen parameters below match the Actor's input schema exactly.

ParameterRequiredTypeDescriptionExample Value
searchNostringCity, region or landmark to search for accommodation. Ignored when Start URLs are provided. Single destination only โ€” not a list. Default ""."New York"
startUrlsNoarrayOne or more Booking.com URLs: property detail pages or search-results pages. Plain strings and { "url": "..." } objects both accepted. Non-Booking.com entries are dropped. Default [].["https://www.booking.com/hotel/gb/the-savoy.html"]
maxItemsNointegerMaximum number of hotels to scrape. Applied as a total across the whole run. Minimum 1, maximum 20000. Default 10.50
scrapeAdditionalHotelDataNobooleanExtract the full add-on blocks โ€” rooms, facilities, highlights, images, roomImages, breadcrumbs, categoryReviews, finePrint, policies, hotelChain, licenseInfo, hostInfo, traderInfo. Default true.true
propertyTypeNostringFilter by accommodation type. 15 options: none (Any), hotels, apartments, hostels, guest houses, homestays, bed and breakfasts, holiday homes, boats, villas, motels, resorts, holiday parks, campsites, luxury tents. Default "none"."apartments"
sortByNostringHow to sort search results: distance_from_search (Distance from city centre), price (Price, lowest first), review_score_and_price (Review score and price), review_score (Review score), class (Star rating). Default "distance_from_search"."review_score"
minimumRatingNostringMinimum guest rating, e.g. 7, 8, 9. Truncated to a whole number before use. Empty means no filter. Default ""."8"
starsCountFilterNostringFilter by star rating: any, 1, 2, 3, 4, 5. Default "any"."4"
currencyNostringCurrency for displayed prices. 11 options: USD, EUR, GBP, CAD, AUD, CHF, JPY, CNY, INR, BRL, MXN. Default "USD"."GBP"
languageNostringInterface language for the search; also selects the Booking.com search domain path. 12 options: en-gb, en-us, de, fr, es, it, pt-br, nl, pl, ru, ja, zh. Default "en-gb"."en-gb"
checkInNostringAbsolute date YYYY-MM-DD or relative, e.g. 2 weeks. Empty means a flexible search with no dates. Default ""."2026-09-14"
checkOutNostringAbsolute date YYYY-MM-DD or relative, e.g. 1 week. Empty means a flexible search with no dates. Default ""."2026-09-17"
flexWindowNostringAllow the stay dates to shift by this many days: 0 (Exact dates only), 1, 2, 3, 7. Default "0"."0"
roomsNointegerNumber of rooms for the stay. Minimum 1, maximum 9. Default 1.1
adultsNointegerNumber of adult guests. Minimum 1, maximum 30. Default 2.2
childrenNointegerNumber of children. Minimum 0, maximum 30. Default 0.0
minMaxPriceNostringMin-max price filter, e.g. 50-200, or 100+ for a minimum only. The default value sends no filter at all. Default "0-999999"."80-300"
proxyConfigurationNoobjectApify Proxy settings. Booking.com blocks direct traffic, so Residential is prefilled. Default {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Inputs that do not behave the way their names suggest. These are worth knowing before you debug a run:

  • startUrls overrides thirteen inputs, not just search. checkIn, checkOut, flexWindow, rooms, adults, children, currency, language, sortBy, propertyType, starsCountFilter, minimumRating and minMaxPrice are only ever used to construct a destination search URL. In startUrls mode that construction never happens, so those values go unused โ€” put them in the URL query string. The one exception is cosmetic: checkInDate and checkOutDate in the output are still filled from your checkIn / checkOut inputs, so in startUrls mode they report what you typed, not what the page was priced for.
  • โš ๏ธ Relative dates are both measured from today, not from each other. checkIn: "2 weeks" with checkOut: "1 week" produces a check-out date before the check-in date. Relative units are approximated as 30-day months and 365-day years. A value matching neither the YYYY-MM-DD form nor the N days/weeks/months/years form is silently treated as empty rather than raising an error.
  • maxItems is a run total. Its title says "per destination or URL", but the assembled property-URL list is truncated to maxItems before scraping begins.
  • minimumRating is truncated to an integer. "8.5" becomes 8. Values below 1 and non-numeric values are ignored.
  • minMaxPrice at its default sends nothing. "0-999999" is treated as "no filter". A malformed range is ignored silently.
  • proxyConfiguration only honours groups and country. The Actor always requests an Apify proxy for the browser; setting useApifyProxy: false does not disable it, and there is no field for supplying an external proxy URL. apifyProxyGroups and apifyProxyCountry are respected, and RESIDENTIAL is used when no group is given.
  • scrapeAdditionalHotelData: false does not lower cost. It empties the add-on blocks in the output โ€” the seven arrays become [], and finePrint, hotelChain, licenseInfo, hostInfo and traderInfo become null โ€” but the keys stay present and charging is per pushed row either way.
  • search still shows up in the output in URL mode. startUrlOrQuery prefers search when it is non-empty, so if you fill in both fields, that column shows the destination string that was never actually searched.

Example input

{
"search": "Amsterdam",
"startUrls": [],
"maxItems": 25,
"scrapeAdditionalHotelData": true,
"propertyType": "hotels",
"sortBy": "review_score",
"minimumRating": "8",
"starsCountFilter": "4",
"currency": "EUR",
"language": "en-gb",
"checkIn": "2026-09-14",
"checkOut": "2026-09-17",
"flexWindow": "0",
"rooms": 1,
"adults": 2,
"children": 0,
"minMaxPrice": "80-300",
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

โฌ†๏ธ Output

Every property is one dataset row with the same 36 keys in the same order, run after run. Rows are pushed live as each property page finishes, so the dataset fills while the run is still going. Nested values arrive as plain JSON objects and arrays, and absent values come back as null, "" or [] rather than as a missing key โ€” downstream code never needs a presence check. Export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.

Only successfully extracted properties are written. There are no error or accounting rows in the dataset, and therefore no errorReason field and no filter expression you need to exclude anything โ€” every row is a real property. Failures appear in the run log only ([HOTEL] Could not load ... for an uncleared WAF challenge, [HOTEL] No hotel name extracted ... for a page that loaded but yielded nothing). Compare your returned item count against maxItems to detect them.

Example output

{
"order": 3,
"url": "https://www.booking.com/hotel/nl/pulitzeramsterdam.html",
"startUrlOrQuery": "Amsterdam",
"name": "Pulitzer Amsterdam",
"type": "hotel",
"description": "Set across 25 canal houses in Amsterdam's Nine Streets district, Pulitzer Amsterdam offers individually designed rooms, a courtyard garden and two on-site restaurants.",
"stars": 5,
"price": 428,
"currency": "EUR",
"rating": 8.9,
"ratingLabel": "Fabulous",
"reviews": 4127,
"breakfast": "Available",
"checkIn": "15:00 - 00:00",
"checkOut": "Until 12:00",
"checkInDate": "2026-09-14",
"checkOutDate": "2026-09-17",
"location": { "lat": 52.373611, "lng": 4.885 },
"address": {
"full": "Prinsengracht 323",
"country": "NL",
"city": "Amsterdam"
},
"image": "https://cf.bstatic.com/xdata/images/hotel/max1024x768/318472901.jpg",
"rooms": [
{
"id": "1204580103",
"name": "Collectors Room",
"description": "A canal-house room with original wooden beams and a curated art selection.",
"size": 22,
"occupancy": 2,
"price": 428
},
{
"id": "1204580107",
"name": "Deluxe Room Canal View",
"description": "Larger room overlooking the Prinsengracht canal.",
"size": 30,
"occupancy": 3,
"price": 596
}
],
"highlights": ["Free WiFi", "Bar", "Room service", "Non-smoking rooms"],
"finePrint": "Please note that the property has no on-site parking. Guests arriving by car can use the nearby public garage at an additional charge.",
"policies": [
"Guests are required to show a photo identification and credit card upon check-in.",
"Smoking is not allowed in any of the rooms.",
"Parties/events are not allowed."
],
"images": [
"https://cf.bstatic.com/xdata/images/hotel/max1024x768/318472901.jpg",
"https://cf.bstatic.com/xdata/images/hotel/max1024x768/318472933.jpg",
"https://cf.bstatic.com/xdata/images/hotel/max1024x768/318473012.jpg"
],
"roomImages": [
{
"roomId": "1204580103",
"images": [
"https://cf.bstatic.com/xdata/images/hotel/max1024x768/318475221.jpg",
"https://cf.bstatic.com/xdata/images/hotel/max1024x768/318475238.jpg"
]
}
],
"categoryReviews": [
{ "name": "Staff", "score": 9.4 },
{ "name": "Facilities", "score": 8.8 },
{ "name": "Cleanliness", "score": 9.1 },
{ "name": "Comfort", "score": 9.0 },
{ "name": "Location", "score": 9.6 },
{ "name": "Value", "score": 8.2 }
],
"hotelChain": null,
"licenseInfo": "0363 1B21 8A5C 9F00 0001",
"hostInfo": null,
"traderInfo": {
"isBusiness": true,
"email": "reservations@pulitzeramsterdam.com",
"phone": "+31205235235",
"companyName": "Pulitzer Amsterdam B.V.",
"firstName": null,
"middleName": null,
"lastName": null,
"registrationNumber": "34118831",
"tradeRegisterName": "Kamer van Koophandel",
"address": {
"street": "Prinsengracht 323",
"street2": null,
"postalCode": "1016 GZ",
"city": "Amsterdam",
"countryCode": "NL",
"state": null
}
},
"breadcrumbs": [
{ "name": "Netherlands", "url": "https://www.booking.com/country/nl.html" },
{ "name": "North Holland", "url": "https://www.booking.com/region/nl/north-holland.html" },
{ "name": "Amsterdam", "url": "https://www.booking.com/city/nl/amsterdam.html" }
],
"hotelId": "42391",
"facilities": [
{ "name": "Free WiFi", "id": 107 },
{ "name": "Bar", "id": 11 },
{ "name": "Fitness Centre", "id": 11 },
{ "name": "Airport Shuttle", "id": 17 }
],
"timeOfScrapeISO": "2026-07-25T09:41:18.204731+00:00",
"source_url": "https://www.booking.com/hotel/nl/pulitzeramsterdam.html"
}

How does it work?

Booking.com puts its property data in an embedded GraphQL store rather than in the visible HTML โ€” and it puts an AWS WAF challenge in front of the page.

Booking.com Scraper opens a headless Chromium browser through an Apify Residential proxy and keeps a single browser context alive for the whole run. The first page load triggers the WAF challenge; the Actor waits for the aws-waf-token cookie, reloads until a genuine page renders, then reuses that solved token for every later page. In destination mode it builds a Booking.com search URL from your filters, walks the result pages in steps of 25 collecting property links, then visits each property page in turn.

Each page is parsed once into four layers โ€” the Apollo GraphQL store, schema.org JSON-LD, inline window.booking variables, and data-testid DOM selectors โ€” and every field falls back through them in that order. Because most fields come from structured payloads rather than rendered markup, a Booking.com visual redesign generally does not affect the output, and your key names and types stay the same. Only publicly visible property data is collected; no account, cookie or login session is used.

Integrations

Booking.com Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.

Calling Booking.com Scraper from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/booking-scraper").call(run_input={
"search": "Amsterdam",
"checkIn": "2026-09-14",
"checkOut": "2026-09-17",
"adults": 2,
"currency": "EUR",
"maxItems": 25,
"proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},
})
for hotel in client.dataset(run["defaultDatasetId"]).iterate_items():
print(hotel["name"], hotel["price"], hotel["currency"], hotel["rating"], hotel["reviews"])

Works in Go, Ruby, Node.js, cURL โ€” any language that can make an HTTP request.

Scheduling and webhooks

Use Apify Schedules to re-run the same destination and date range daily or weekly โ€” that is how you build a rate-history series, since each run writes its own dataset and every row is stamped with timeOfScrapeISO. Attach an Apify webhook on run success to push the finished dataset into your own endpoint, or send it to Google Sheets, Slack or a database through the Actor's integrations tab.

No-code tools (n8n, Make, Zapier)

In n8n, use the Apify node โ€” or an HTTP Request node pointed at the Apify run endpoint with your token โ€” and pass the same JSON input shown above; a Split Out node on rooms then gives you one item per room type. In Make, use the Apify module's run-and-wait action and map the dataset output into a Google Sheets or Airtable step. Both handle scheduling, so a weekly competitor-rate sweep needs no code.

Scraping publicly listed accommodation data is broadly treated as permissible where the data is accessible without authentication, and Booking.com Scraper collects only what an anonymous visitor already sees on a property page. No login is used and no access control is bypassed.

The bulk of the output is commercial business and product data: property names, room types, prices, availability, star ratings, aggregate guest scores, facilities and house rules. These are business records, not personal data, so the personal-data regimes that govern profile scraping do not attach to this output the way they would to a scraper that collects individual people. This Actor does not return guest review text or reviewer names.

Two field groups are the exception and should be handled as personal data. For individually operated properties, traderInfo.firstName, traderInfo.middleName, traderInfo.lastName, traderInfo.email, traderInfo.phone and hostInfo can identify a natural person โ€” Booking.com publishes these under EU trader-transparency obligations, but that does not make them exempt for you. If you store or process those six fields at scale, GDPR and equivalent regimes apply and you need a lawful basis. Set scrapeAdditionalHotelData to false to drop traderInfo and hostInfo entirely if you do not need them.

What otherwise applies is contractual and database-related: Booking.com's terms of service, and database or unfair-competition rules covering systematic reuse of a compiled listings dataset. Consult legal counsel if your use case involves bulk storage of personal data, or before republishing extracted listings as a competing inventory.

โ“ Frequently asked questions

What Booking.com hotel fields does Booking.com Scraper return?

The five most used are name, price, rating, address and rooms. Thirty-six keys are returned per row, including nested objects for location, address and traderInfo โ€” see the data fields table above for all of them.

Does Booking.com Scraper require a Booking.com account or login?

No. No Booking.com account, no cookie, no session. The Actor loads public property pages anonymously. The only credential you need is your Apify token, and the only paid resource is the Apify Residential proxy the Actor uses by default.

How many hotels can I extract in one run?

maxItems accepts 1 to 20,000, and it is a total across the run rather than a per-URL figure โ€” the assembled property-URL list is truncated to maxItems before scraping starts. Search results are paginated 25 at a time until the target is reached or a page returns nothing new. The Actor's configured pay-per-event prices are $0.005 for the actor start and $0.01 per hotel result pushed to the dataset.

Do I need check-in and check-out dates to get prices?

In practice, yes. Room-level availability and pricing only render when Booking.com has a date range to price against, which is why the schema groups these two inputs under "Set dates to get additional information (e.g. price)". Without dates, expect price to be null on many properties and rooms entries to carry names without prices. If you use startUrls, the dates must be in the URL query string โ€” the checkIn / checkOut inputs do not modify a supplied URL.

Why does price not match the currency I selected?

Because price and currency come from different places. currency reports the currency the property quotes in, read from Booking.com's own page variables. price is the lowest room-block amount when one is available; when it is not, the Actor falls back to Booking.com's b_cheapest_price_that_fits_search_eur page variable, which is denominated in euros regardless of your currency input. For a guaranteed currency match, read rooms[].price.

What happens if a property page is blocked or removed?

The row is skipped. When the AWS WAF challenge cannot be cleared after five reloads across three fresh browser contexts, or when the page loads but no property name can be extracted, the Actor logs the failure and moves to the next URL. Nothing is pushed for that property โ€” there are no error rows, no errorReason field and no placeholder values in the dataset. Detect it by comparing your returned item count against maxItems and checking the run log.

Can I scrape multiple Booking.com hotels or destinations at once?

Yes for hotels, with a caveat for destinations. startUrls is a list and accepts any mix of property detail pages and search-results pages. search, however, takes a single destination string โ€” for several cities, either run the Actor once per city or paste one search-results URL per city into startUrls. Remember that maxItems caps the combined total.

Does Booking.com Scraper work with Claude, ChatGPT and other AI agent tools?

Yes. It is callable as a standard HTTP endpoint through the Apify API, so any agent framework that can make a request โ€” LangChain, CrewAI, n8n, or a hand-written tool definition โ€” can invoke it and receive typed JSON with no parsing step.

How does Booking.com Scraper compare to other Booking.com scrapers?

Checked on the Apify Store on 25 July 2026: automation-lab/booking-scraper documents 21 flat search-result fields and states it uses Playwright with residential proxies against AWS WAF. santamaria-automations/booking-com-scraper documents 34+ search-result fields and describes an HTTP-only architecture calling Booking.com's internal GraphQL API with no browser. plowdata/booking-com-review-scraper covers a different entity โ€” individual guest reviews and per-hotel score breakdowns โ€” and its listing notes that Booking.com blocks shared Apify proxies. Room-level detail, house rules and trader records are not documented on any of the three listings. Booking.com Scraper's difference is that it opens each property page rather than reading only the search-results card: that is where rooms with per-room prices and occupancy, facilities, policies, finePrint, roomImages, categoryReviews, licenseInfo and the full traderInfo legal record come from. The trade-off is honest โ€” one browser page load per property is slower than parsing a results grid.

Does Booking.com Scraper return data in a format LLMs can use directly?

Yes. Every row is typed, normalized JSON with the same field names on every run. No HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool.

What happens when Booking.com changes its layout or anti-bot system?

The scraper is maintained, and because most fields are read from Booking.com's embedded Apollo store and JSON-LD rather than from rendered markup, a visual redesign generally does not affect them. The layered fallback โ€” Apollo, then JSON-LD, then window.booking variables, then DOM selectors โ€” means a structural change usually degrades a single field instead of failing the row. Your field names and types do not change on your end.

Can I use Booking.com Scraper without managing proxies or browser infrastructure?

Yes. The Actor launches and manages its own Playwright Chromium instance, requests an Apify proxy for it, clears the AWS WAF challenge, keeps the solved token in a reused browser context, and rotates to a fresh context and IP when a page fails. You never install a browser or create a proxy account. You can pick proxy groups and a country through proxyConfiguration; the browser always runs through an Apify proxy.

Which Booking.com fields work best for AI training data and RAG indexing?

For RAG indexing: description, policies, finePrint, facilities and highlights carry the most descriptive text per property and chunk cleanly by hotelId. For training data: price, rating, reviews, stars and categoryReviews[].score are the most structurally consistent fields across records, and location.lat / location.lng give you clean numeric geo features. All values return as typed primitives, arrays or plain nested objects, so no normalization pass is needed before indexing.

Scraper NameWhat it extracts
Apartments.com ScraperRental listings with rent, beds, baths, amenities and availability
Google Maps Scraper With Reviews Sentiment AnalysisPlace records plus review text and sentiment for the same properties
Realtor.com Agents ScraperReal-estate agent records for the residential side of a market
Google News Scraper By Publisher Site FilterDestination and travel-market news filtered by publisher
Quora Search ScraperQuestion-intent results for destination and travel content research

๐Ÿ’ฌ Your feedback

Found a bug, or need a field that is in Booking.com's page payload but not in the output? Open an issue on the Actor's Issues tab and it will be looked at. Reports that include the exact input JSON and the Booking.com URL you ran are the fastest to reproduce and fix.