Booking.com Scraper โ€” Rate & Value Report avatar

Booking.com Scraper โ€” Rate & Value Report

Pricing

from $4.99 / 1,000 results

Go to Apify Store
Booking.com Scraper โ€” Rate & Value Report

Booking.com Scraper โ€” Rate & Value Report

๐Ÿจ BookingScraper extracts real-time Booking.com listings data โ€” prices, availability, ratings, reviews, amenities & location. โšก Ideal for market research, price monitoring, travel analytics, and lead gen. ๐Ÿ“Š Fast, accurate, anti-blocking scraping with CSV/JSON export.

Pricing

from $4.99 / 1,000 results

Rating

0.0

(0)

Developer

API Empire

API Empire

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

1

Monthly active users

15 days ago

Last modified

Share

Booking.com Scraper โ€” Hotels, Room Rates and Value Scores as JSON

Booking.com Scraper โ€” Rate & Value Report extracts three linked entity types from public Booking.com pages in one run: property records (name, stars, guest rating, facilities, address, trader identity), per-room rate options (base price, discounted price, discount %, taxes and fees, board type, refundable flag), and a derived value ranking that scores each property on rating against price and re-orders the report best-value first. Every row is typed, normalized JSON โ€” no HTML, no selectors, no parsing. By the end of this page you will know which fields you get, what unlocks each of them, and where the honest gaps are.


What is Booking.com Scraper โ€” Rate & Value Report?

Booking.com Scraper โ€” Rate & Value Report is an Apify Actor that turns a destination name or a list of Booking.com URLs into a ranked accommodation dataset. It scrapes public search-results and hotel detail pages, then adds two derived layers: a value score (valueScore, valueRatio, valueRank) computed across the whole result set, and a per-room rate breakdown parsed from Booking's own room blocks.

No Booking.com account, cookie, or login is required. The Actor never authenticates โ€” it fetches the same pages an anonymous visitor sees. It reads no member-only rates, touches no booking or payment flow, and performs no in-platform action.

  • Scrape hotels and other accommodation โ€” hotels, apartments, hostels, villas, resorts, campsites and 9 more categories via stayCategory
  • Scrape per-room rate options โ€” basePrice, discountedPrice, discountPct, taxesAndFees, totalPrice, boardType, refundable
  • Scrape derived value rankings โ€” valueScore (0โ€“100 percentile), valueRank (1 = best rating-for-price), valueRatio (raw rating รท price)
  • Export as JSON, CSV, Excel, XML or HTML from the Apify dataset, or read it straight from the Apify API
  • No proxy management and no parsing โ€” a residential proxy ladder and two independent fetch engines are built in

What data does the Rate & Value Report collect?

One run returns a single dataset item per property, and each item carries the property record, its room inventory, its rate options, its review breakdown, its trader identity block, and the derived value fields. Forty top-level keys are written on every row.

Data TypeKey FieldsJSON Field Names
Property recordName, Booking hotel id, star class, accommodation type, description, canonical URLname, hotelId, stars, type, description, url
Value ranking (derived)Percentile value score, value rank, raw rating-per-price ratio, output positionvalueScore, valueRank, valueRatio, order
Room rate optionsPre-discount price, discounted price, discount %, taxes and fees, total, board type, refundabilityrateBreakdown[] โ†’ .basePrice, .discountedPrice, .discountPct, .taxesAndFees, .totalPrice, .boardType, .refundable, .currency, .roomId, .roomName
Room inventoryRoom id, name, description, size, max occupancy, cheapest block pricerooms[] โ†’ .id, .name, .description, .size, .occupancy, .price
Guest ratingsScore, localized label, review count, per-category subscoresrating, ratingLabel, reviews, categoryReviews[].name, categoryReviews[].score
Facilities and highlightsFacility names and ids, marketing highlights, breakfast availabilityfacilities[].name, facilities[].id, highlights, breakfast
Location and addressLatitude, longitude, street, city, country code, breadcrumb traillocation.lat, location.lng, address.full, address.city, address.country, breadcrumbs[]
House rules and fine printCheck-in window, check-out window, house-rule lines, fine printcheckIn, checkOut, policies, finePrint
Business / trader identityTrader flag, legal company name, email, phone, registration number, postal addresstraderInfo โ†’ .isBusiness, .companyName, .email, .phone, .registrationNumber, .tradeRegisterName, .address.*
Host, chain and licenceHost name, hotel chain, first published licence numberhostInfo, hotelChain, licenseInfo
MediaHero image, deduplicated gallery (capped at 50), per-room photo setsimage, images, roomImages[].roomId, roomImages[].images
Run metadataScrape timestamp, queued URL, originating query, resolved stay dates, price, currencytimeOfScrapeISO, source_url, startUrlOrQuery, checkInDate, checkOutDate, price, currency

Need more accommodation and local-market data?

API Empire does not currently publish a second Booking.com Actor, so there is no companion review or availability scraper for this platform to point you at. For adjacent work, the Apartments.com Scraper โ€” Neighborhood Rent & Livability Report covers long-stay rental pricing in the same value-report style, and the Google Maps Scraper with Lead Contact Enrichment is the usual pairing when you need on-the-ground contact details for the properties you just ranked.


How does this differ from the official Booking.com Demand API?

Booking.com's own programmatic surface is the Demand API at developers.booking.com; access is granted through an approved affiliate or distribution partnership with a signed agreement, and the endpoints are built for partners who send bookings back to Booking.com. This Actor is the opposite shape: a read-only extractor over public pages that anyone with an Apify account can start immediately.

FeatureBooking.com Demand APIRate & Value Report
AccessApproved partnership and signed agreement required, per Booking.com's published Demand API onboarding docsOpen โ€” start the Actor from the Apify Console or Apify API with your Apify token
Intended purposeDistributing and booking inventory as a Booking.com partnerRead-only extraction and analysis of public listing data
Value scoringNot provided โ€” you receive raw availability and pricingvalueScore, valueRank, valueRatio computed across the whole result set
Discount visibilityRate plans as contracted, in partner termsbasePrice vs discountedPrice and a derived discountPct as shown publicly on the page
Trader / legal identityNot the API's concerntraderInfo, licenseInfo, hostInfo as published on the listing
Setup timePartner onboarding and reviewFill the input form, press Start
Output shapePartner-contract JSON schema40 stable top-level keys per property, identical on every run

Use the Demand API when you are an approved partner and your goal is to sell inventory โ€” it is the only lawful path to real booking capability. Use this Actor when your goal is analysis: rating against price, published discounts and taxes, or a structured accommodation dataset built from pages that are already public.


Why do developers and teams scrape Booking.com?

Booking.com is the largest single public catalogue of accommodation pricing, and almost every price the platform shows is decorated with a discount claim, a tax note and a board type that only exist inside the rendered page. Four groups get real leverage from turning that into rows.

For AI engineers and agent builders

A travel agent answering "find me a well-rated place in Lisbon under โ‚ฌ150 that includes breakfast" needs structure, not a screenshot. Run the Actor with location, stayCheckIn, stayCheckOut and priceBand, then hand the dataset to the model: valueScore is a ready ranking signal, rateBreakdown[].boardType answers the breakfast constraint without string matching, and rateBreakdown[].refundable answers the cancellation constraint. Every field is typed JSON, so there is no HTML parsing inside the agent loop โ€” the most common reliability failure in tool-using agents.

For travel marketers and deal sites

Deal editors need the properties whose rating is disproportionate to their price, not the cheapest ones. Set sortResultsBy to bestValue and the report arrives pre-ranked: valueRank: 1 is the strongest rating-for-price in the set, and valueRatio exposes the raw number for re-weighting. discountPct on each rate option separates a real markdown from a strikethrough that never moved, and ratingLabel gives the exact wording Booking shows guests in the language set by resultLanguage.

For researchers and market analysts

Everything returned is drawn from pages Booking.com serves to anonymous visitors โ€” no login, no member rate, no reviewer personal data, no booking-flow content. That matters for pricing studies, tourism-economics work and accommodation-supply research where sample provenance is part of the method. A typical study sweeps one location per city with fixed stayCheckIn/stayCheckOut and maxProperties, then compares price, stars, rating and taxesAndFees across markets. timeOfScrapeISO timestamps every observation, so repeated runs assemble into a panel.

For developers building data products

The Actor is a scheduled job with a stable output contract, which is what a pipeline needs. Point a daily Apify schedule at a saved input, read defaultDatasetId from the run object, and upsert on hotelId plus checkInDate. Field names and types do not change between runs, so a warehouse table defined once keeps loading. Pay-per-event pricing means a run's cost tracks the rows you actually got, which makes per-customer cost modelling straightforward.


๐Ÿš€ How to scrape Booking.com hotel rates (step by step)

The Actor runs on the Apify platform only. There is no separate signup, no API key of its own, and no credit system โ€” you start it from the Apify Console or call it with the Apify API using your Apify token.

  1. Open the Actor on its Apify Store listing and press Try for free, or open it in the Apify Console if it is already in your account.
  2. Give it a target. No field is required by the schema, but the run does nothing without one of the two entry points: a city, region or landmark in location, or Booking.com URLs in stayUrls (hotel detail pages and searchresults pages are both accepted).
  3. Set the stay dates. Fill stayCheckIn and stayCheckOut (YYYY-MM-DD, or a relative form like 2 weeks). This unlocks rateBreakdown, taxesAndFees and correct currency handling โ€” without both dates Booking serves no priced availability blocks.
  4. Choose filters and ordering. maxProperties caps the sample, sortResultsBy sets the output order (bestValue by default), and stayCategory, starRating, minGuestRating, priceBand and searchOrder narrow the comparison set before ranking.
  5. Start the run, then export. Open the Output tab โ€” the default ๐Ÿ’Ž Value-ranked hotels view leads with the ranking columns โ€” and export to JSON, CSV, Excel, XML or HTML, or pull the dataset over the Apify API.

What to do when Booking.com changes its structure

Nothing on your side. The Actor reads four independent layers of the page โ€” Apollo store, JSON-LD, inline window.booking variables, DOM selectors โ€” and merges whatever each yields, so a change to one layer degrades single fields rather than breaking the run. Output key names and types are the integration contract and are kept stable while the scraper is maintained against Booking's markup.


What changed in Booking.com scraping recently?

The largest structural shift is regulatory rather than technical: the European Commission designated Booking.com a gatekeeper under the Digital Markets Act on 13 May 2024, with compliance obligations applying from 13 November 2024, which reshaped how search results and ranking disclosures render in the EU.

  • Trader-identity blocks became routine. The EU Digital Services Act's trader-traceability obligation has applied to online marketplaces since 17 February 2024, and Booking now publishes legal-entity, registration and contact data on many listings. That is exactly the block this Actor reads into traderInfo, licenseInfo and hostInfo โ€” data that simply was not on the page for most listings before.
  • Two rendering paths coexist. Priced availability appears in the React/Apollo store on some responses and in the legacy server-rendered #hprt-table rooms table on others. A scraper that knows only one of them returns an empty rateBreakdown on the other. This Actor tries the Apollo path first and falls back to the DOM table.
  • Anti-bot on the search-results page is aggressive. A competing Booking.com Actor's own listing states plainly that "Booking.com blocks shared Apify proxies" (plowdata/booking-com-review-scraper, checked on the Apify Store 2026-07-25 โ€” not measured here). This Actor defaults to Apify RESIDENTIAL proxies and steps down a ladder on repeated blocks.
  • What is still public: search results, hotel detail pages, published rates, guest scores, facilities, house rules and trader blocks. What is not: member and loyalty-gated rates, anything behind login, and the booking or payment flow.

Maintenance continues against whichever rendering path Booking ships next; the layered extractor is what makes that survivable.


โฌ‡๏ธ Input

Every parameter below comes from .actor/actor.json. Nothing is required โ€” but a run with neither location nor stayUrls logs "No destination and no Stay URLs. Nothing to do." and exits without pushing rows. Each field also accepts the base Booking scraper's key name as an alias, so an input written for the base Actor runs here unchanged.

ParameterRequiredTypeDescriptionExample Value
locationNostringDestination to value-rank โ€” city, region or landmark. Ignored when stayUrls is non-empty. Default "", editor textfield. Alias: search."Amsterdam"
stayUrlsNoarrayBooking.com URLs โ€” hotel detail pages and/or searchresults pages. Non-Booking URLs are dropped. Default [], editor stringList. Alias: startUrls (also accepts urls); string or {"url": โ€ฆ} entries.["https://www.booking.com/hotel/nl/example.html"]
maxPropertiesNointegerHow many properties to scrape and rank per destination / URL. Default 10, minimum 1, maximum 20000. Alias: maxItems.15
sortResultsByNostringOutput ordering. Enum: bestValue, lowestPrice, highestRating, asFound. Default bestValue, editor select. Any unrecognized value falls through to bestValue."bestValue"
includeRateBreakdownNobooleanParse per-room rate options from Booking's room blocks. Default true. Needs both stay dates to return anything.true
includePropertyDetailsNobooleanInclude rooms, facilities, highlights, images, room photos, breadcrumbs, chain, licence, host and trader info. Default true. Alias: scrapeAdditionalHotelData.true
stayCheckInNostringCheck-in. Absolute YYYY-MM-DD or relative (2 weeks). Default "", editor datepicker, dateType: absoluteOrRelative, pattern ^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$|^(\d+)\s*(day|week|month|year)s?$|^$. Alias: checkIn."2026-09-12"
stayCheckOutNostringCheck-out. Same format, same pattern, same editor as stayCheckIn. Default "". Alias: checkOut."2026-09-15"
dateFlexibilityNostringShift the stay dates by up to this many days. Enum: 0, 1, 2, 3, 7. Default "0", editor select. Sent as flex_window only when not "0". Alias: flexWindow."0"
guestsAdultsNointegerAdult guests the rate is quoted for. Default 2, minimum 1, maximum 30. Alias: adults.2
guestsChildrenNointegerChildren the rate is quoted for. Default 0, minimum 0, maximum 30. Alias: children.0
roomsNeededNointegerRooms for the stay. Default 1, minimum 1, maximum 9. Alias: rooms.1
stayCategoryNostringAccommodation type filter. Enum: none, hotels, apartments, hostels, guest houses, homestays, bed and breakfasts, holiday homes, boats, villas, motels, resorts, holiday parks, campsites, luxury tents. Default "none", editor select. Applied only on destination searches. Alias: propertyType."hotels"
searchOrderNostringBooking's server-side result order, applied before value re-ranking. Enum: distance_from_search, price, review_score_and_price, review_score, class. Default "review_score_and_price", editor select. Alias: sortBy."review_score_and_price"
minGuestRatingNostringGuest-rating floor, e.g. 8. Default "", editor textfield. Truncated to a whole number and sent as review_score=<nร—10>, so 8.5 behaves as 8. Alias: minimumRating."8"
starRatingNostringOfficial star class filter. Enum: any, 1, 2, 3, 4, 5. Default "any", editor select. Alias: starsCountFilter."any"
priceBandNostringNightly price band, min-max (60-250) or min+ (100+). Default "0-999999", editor textfield; the default is treated as "no filter". Malformed values are ignored silently. Alias: minMaxPrice."60-250"
resultCurrencyNostringCurrency for prices and the rate breakdown. Enum: USD, EUR, GBP, CAD, AUD, CHF, JPY, CNY, INR, BRL, MXN. Default "EUR", editor select. Alias: currency."EUR"
resultLanguageNostringBooking interface language, which also selects the searchresults.<lang>.html domain path. Enum: en-gb, en-us, de, fr, es, it, pt-br, nl, pl, ru, ja, zh. Default "en-gb", editor select. Alias: language."en-gb"
proxyConfigurationNoobjectApify proxy settings, editor proxy. Default {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}. apifyProxyCountry is carried through to the residential tier when set.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Example input

{
"location": "Amsterdam",
"stayUrls": [],
"maxProperties": 15,
"sortResultsBy": "bestValue",
"includeRateBreakdown": true,
"includePropertyDetails": true,
"stayCheckIn": "2026-09-12",
"stayCheckOut": "2026-09-15",
"dateFlexibility": "0",
"guestsAdults": 2,
"guestsChildren": 0,
"roomsNeeded": 1,
"stayCategory": "hotels",
"searchOrder": "review_score_and_price",
"minGuestRating": "8",
"starRating": "any",
"priceBand": "60-250",
"resultCurrency": "EUR",
"resultLanguage": "en-gb",
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

Most common input mistake: leaving stayCheckIn and stayCheckOut empty while expecting the rate breakdown. Booking only injects priced availability blocks for a specific stay, so a dateless run returns rateBreakdown: [], no taxesAndFees, and prices in Booking's own default currency rather than your resultCurrency โ€” because the currency parameter is only appended to detail-page URLs when both dates are present. The Actor logs a warning for exactly this case: "Rate breakdown requested but no check-in/out dates set."


โฌ†๏ธ Output

Results land in the Actor's default Apify dataset as typed, normalized JSON with a stable key set โ€” 40 top-level keys on every row, in the same order, whether or not each has a value. Export from the Console or the API as JSON, CSV, Excel (XLSX), XML, HTML or JSONL. The default view, ๐Ÿ’Ž Value-ranked hotels, surfaces all 40 keys with the ranking columns first; nothing is hidden behind it. Missing data is null (or [] / "") โ€” the extractor never substitutes a plausible-looking value for one it could not find.

Scraped property with value score and rate breakdown

{
"order": 1,
"valueRank": 1,
"valueScore": 100.0,
"valueRatio": 0.023133,
"name": "Canal House Amsterdam",
"price": 372.0,
"currency": "EUR",
"rating": 8.6,
"ratingLabel": "Fabulous",
"stars": 4,
"rateBreakdown": [
{
"roomId": "12345678_401234567_2_0_0",
"roomName": "Deluxe King Room with Canal View",
"boardType": "Breakfast included",
"basePrice": 465.0,
"discountedPrice": 372.0,
"discountPct": 20.0,
"taxesAndFees": 44.64,
"totalPrice": 416.64,
"currency": "EUR",
"refundable": true
},
{
"roomId": "12345678_401234567_2_1_0", "roomName": "Standard Twin Room",
"boardType": null, "basePrice": null, "discountedPrice": 318.0,
"discountPct": null, "taxesAndFees": 38.16, "totalPrice": 356.16,
"currency": "EUR", "refundable": false
}
],
"reviews": 1487,
"breakfast": "Available",
"url": "https://www.booking.com/hotel/nl/canal-house-amsterdam.en-gb.html",
"startUrlOrQuery": "Amsterdam",
"type": "hotel",
"description": "Set in a 17th-century canal house in the Jordaan district โ€ฆ",
"checkInDate": "2026-09-12",
"checkOutDate": "2026-09-15",
"checkIn": "From 15:00 to 23:00",
"checkOut": "From 07:00 to 11:00",
"location": { "lat": 52.374, "lng": 4.8897 },
"address": { "full": "Keizersgracht 148", "country": "NL", "city": "Amsterdam" },
"image": "https://cf.bstatic.com/xdata/images/hotel/max1024x768/000000001.jpg",
"rooms": [
{
"id": "12345678_401234567",
"name": "Deluxe King Room with Canal View",
"description": "This double room features a seating area, minibar and canal views.",
"size": 28,
"occupancy": 2,
"price": 372.0
}
],
"highlights": ["Free WiFi", "Canal view", "Very good breakfast"],
"finePrint": "The property is located in a listed building without a lift.",
"policies": ["Pets: not allowed", "Smoking: not allowed"],
"images": ["https://cf.bstatic.com/xdata/images/hotel/max1024x768/000000001.jpg"],
"roomImages": [
{ "roomId": "401234567", "images": ["https://cf.bstatic.com/โ€ฆ/000000011.jpg"] }
],
"categoryReviews": [
{ "name": "Cleanliness", "score": 8.9 },
{ "name": "Location", "score": 9.4 }
],
"hotelChain": null,
"licenseInfo": "0363 1234 5678 ABCD",
"hostInfo": null,
"traderInfo": {
"isBusiness": true, "email": "info@example-hotel.nl", "phone": "+31201234567",
"companyName": "Example Hospitality B.V.", "firstName": null, "middleName": null,
"lastName": null, "registrationNumber": "12345678",
"tradeRegisterName": "Kamer van Koophandel",
"address": {
"street": "Keizersgracht 148", "street2": null, "postalCode": "1015 CX",
"city": "Amsterdam", "countryCode": "NL", "state": null
}
},
"breadcrumbs": [
{ "name": "Netherlands", "url": "https://www.booking.com/country/nl.html" },
{ "name": "Amsterdam", "url": "https://www.booking.com/city/nl/amsterdam.html" }
],
"hotelId": "12345678",
"facilities": [{ "name": "Free WiFi", "id": 107 }, { "name": "Bar", "id": 11 }],
"timeOfScrapeISO": "2026-07-25T09:41:12.883410+00:00",
"source_url": "https://www.booking.com/hotel/nl/canal-house-amsterdam.html"
}

Every output key, and what fills it

FieldTypeNotes
orderinteger1-based position in the final report, rewritten after re-ranking so it always matches sortResultsBy.
valueRankinteger | null1 = highest rating รท price in the run. null without a rating or a price.
valueScorenumber | nullPercentile of rating รท price across scored rows, 0โ€“100, one decimal. Lowest scored row is 0.0, highest 100.0; a lone scoreable row gets 100.0.
valueRationumber | nullRaw rating รท price, 6 decimals.
namestringProperty name. A page yielding no name is retried then skipped, so this is never null on a pushed row.
pricenumber | nullLowest priced option on the page โ€” minimum of rooms[].price and rateBreakdown[].discountedPrice, falling back to Booking's inline b_cheapest_price_that_fits_search_eur and then the DOM price element.
currencystringThe property's b_hotel_currencycode, defaulting to "USD". Its listing currency โ€” not necessarily the currency price is quoted in (see caveats).
ratingnumber | nullGuest score out of 10, from ReviewScoreSummary or JSON-LD aggregateRating.ratingValue.
ratingLabelstring | nullBooking's localized label; if absent, derived from the score โ€” Superb โ‰ฅ 9, Very good โ‰ฅ 8, Good โ‰ฅ 7, Pleasant โ‰ฅ 6, else null.
starsnumber | nullOfficial star class from the StarRating object.
rateBreakdownarrayPer-room rate options. [] when includeRateBreakdown is false, when no stay dates are set, or when no priced block exists. Never fabricated.
rateBreakdown[].roomIdstring | nullApollo room id, or the DOM table's data-block-id.
rateBreakdown[].roomNamestring | nullRoom type name as displayed.
rateBreakdown[].boardTypestring | nullBooking's meal-plan value, or normalized from the meal-plan cell into All inclusive, Full board, Half board, Breakfast included, Breakfast available, Dinner included, Lunch included.
rateBreakdown[].basePricenumber | nullPre-discount / strikethrough price, when published.
rateBreakdown[].discountedPricenumber | nullThe price actually charged for this option.
rateBreakdown[].discountPctnumber | null(basePrice โˆ’ discountedPrice) / basePrice ร— 100, one decimal. Only when both exist and basePrice โ‰ฅ discountedPrice.
rateBreakdown[].taxesAndFeesnumber | nullExcluded charges as published under the price.
rateBreakdown[].totalPricenumber | nulldiscountedPrice + taxesAndFees, 2 decimals. null when taxes are absent or published as zero.
rateBreakdown[].currencystring | nullFrom Booking's price object; on the DOM path it is backfilled with your resultCurrency.
rateBreakdown[].refundableboolean | nulltrue on free cancellation, false on non-refundable, null when neither is stated.
reviewsintegerReview count; 0 when unavailable.
breakfaststring | null"Available" when breakfast appears in the facility/highlight set or in Booking's "breakfast is available/included" copy, else null.
urlstringCanonical URL from JSON-LD, falling back to the fetched URL.
startUrlOrQuerystringYour location value, or the URL on a URL-driven run โ€” the grouping key for multi-destination datasets.
typestringBooking's b_hotel_type_anchor, defaulting to "hotel".
descriptionstring | nullJSON-LD description, falling back to the Apollo description. Not gated by includePropertyDetails.
checkInDate / checkOutDatestring | nullThe resolved stay dates for this run, YYYY-MM-DD. null on a dateless run.
checkIn / checkOutstring | nullThe property's house-rule time windows, e.g. "From 15:00 to 23:00". A one-sided window renders as "From 15:00" or "Until 11:00" โ€” never a half-formed "to None".
locationobject{ "lat": number|null, "lng": number|null }, from Apollo data or an inline-variable regex fallback.
addressobject{ "full", "country", "city" }; country upper-cased. Empty strings, not null, when unknown.
imagestringFirst gallery image, or "".
roomsarray{ id, name, description, size, occupancy, price } per room type. [] when includePropertyDetails is false.
highlightsarrayBooking's facility-highlight titles. [] when includePropertyDetails is false.
finePrintstringFine-print paragraphs joined with spaces; "" when none. Not gated by includePropertyDetails.
policiesarray | nullReal house-rule lines only ("Pets: not allowed"), from house-rule objects and boolean rule flags. null when none are published โ€” never invented from marketing copy.
imagesarrayDeduplicated gallery URLs, capped at 50. [] when includePropertyDetails is false.
roomImagesarray{ roomId, images[] } per room photo set. [] when includePropertyDetails is false.
categoryReviewsarray{ name, score } subscores such as Cleanliness, Location, Staff; the hotel_ prefix is stripped and the name title-cased. Not gated by includePropertyDetails.
hotelChainstring | nullChain name when the property belongs to one. null when includePropertyDetails is false.
licenseInfostring | nullFirst published licence/registration number only.
hostInfostring | nullHost profile name; falls back to the trader's first + last name when no company name is published.
traderInfoobject | nullisBusiness, email, phone, companyName, firstName, middleName, lastName, registrationNumber, tradeRegisterName, plus nested address with street, street2, postalCode, city, countryCode, state. null when includePropertyDetails is false.
breadcrumbsarray{ name, url } for Booking's country/region/city trail. [] when includePropertyDetails is false.
hotelIdstring | nullBooking's numeric hotel id, as a string.
facilitiesarray{ name, id } per facility; the DOM fallback returns id: null. [] when includePropertyDetails is false.
timeOfScrapeISOstringUTC ISO-8601 timestamp of the extraction.
source_urlstringThe URL as queued, before availability parameters were appended โ€” the stable join key across runs.

Scraped rate option (standalone shape)

rateBreakdown is the second entity in the report and has its own schema, identical whether it came from the Apollo store or the legacy rooms table:

{
"roomId": "12345678_401234567_2_0_0",
"roomName": "Deluxe King Room with Canal View",
"boardType": "Breakfast included",
"basePrice": 465.0,
"discountedPrice": 372.0,
"discountPct": 20.0,
"taxesAndFees": 44.64,
"totalPrice": 416.64,
"currency": "EUR",
"refundable": true
}

Limits, caveats and known behaviour

These are the things worth knowing before you build on the output. All of them are visible in the source.

  • price follows the stay, not the night. On a multi-night search Booking renders the price for the whole stay in the rooms table, and price is the minimum of those figures โ€” so on a 3-night run price is the 3-night total for the cheapest option, even though the dataset view labels the column Nightly Price. Within one run every row uses the same stay length, so the ranking is internally consistent; keep the stay length fixed when comparing valueRatio across runs.
  • currency and the price currency can disagree. currency comes from the property's own b_hotel_currencycode and defaults to "USD". Your resultCurrency reaches Booking as selected_currency, which is appended to detail URLs only when both stay dates are set. On a dateless run, prices come back in whatever currency Booking picks for the locale. Set both dates when currency matters.
  • The whole result set is buffered before anything is pushed. Value scoring is a percentile across the run, so every property is scraped and held in memory first; rows appear in the dataset only after the last one finishes. A run aborted mid-way writes nothing.
  • Properties are fetched sequentially. No concurrency โ€” hotel pages go one after another, with a 1-second pause between search pages. maxProperties accepts up to 20000, but wall-clock time scales with it and the run timeout is the practical ceiling.
  • Filters apply to destination searches only. stayCategory, starRating, minGuestRating, priceBand and searchOrder are encoded into the search URL. Hotel detail URLs in stayUrls are fetched as given, unfiltered.
  • minGuestRating is truncated. 8.5 becomes 8 before conversion to Booking's review_score=80 filter.
  • Relative dates are approximate. 1 month resolves to 30 days from today, 1 year to 365. A date matching neither the absolute nor the relative form resolves to empty โ€” silently turning the run into a dateless scan.
  • basePrice from the legacy rooms table is a heuristic. On the DOM path the pre-discount price is inferred as the largest other plausible figure in the price cell (bounded to under 10ร— the charged price), so an unrelated number printed there can distort basePrice and discountPct. The Apollo path reads a named field and is unaffected.
  • valueScore is relative, not absolute. It ranks your result set against itself, not against all of Booking.com โ€” scrape 5 hotels and one is 100 and one is 0 by construction. Raise maxProperties for a meaningful spread.

How does this compare to other Booking.com scrapers?

FeatureBooking.com Scraper โ€” Rate & Value ReportGeneric Booking.com scraper
Output format40 typed top-level keys per property, identical every run; JSON, CSV, XLSX, XML, HTMLRaw rows, shape varies by Actor
Entity coverageProperty record + per-room rate options + derived value ranking in one itemUsually one entity โ€” either listings or reviews
Rate detailbasePrice, discountedPrice, discountPct, taxesAndFees, totalPrice, boardType, refundable per room optionA single nightly price field
Input flexibilityDestination search or hotel URLs or search-results URLs, plus 8 filters and base-Actor key aliasesUsually URL-only or destination-only
Anti-bot handlingChrome-TLS fetch with a headless Chromium fallback, on a RESIDENTIAL โ†’ DATACENTER โ†’ NO_PROXY ladder with 3 retries per tierVaries; some listings require you to bring your own proxies
Missing data policynull / [], never a fabricated rate or an invented policy lineVaries

The closest Apify Store listings were checked on 2026-07-25. plowdata/booking-com-review-scraper targets a different entity โ€” guest reviews and per-category scores across two datasets โ€” and its listing advertises "~30-50 reviews/sec" while instructing users to supply their own proxies because "Booking.com blocks shared Apify proxies" (its listing text, checked 2026-07-25 โ€” not measured here). ryanclinton/booking-scraper is the closest positional competitor: its listing advertises a stateful rate-intelligence engine with six modes, saved rate history across runs, and "up to 240 hotels per destination" (checked 2026-07-25 โ€” not measured here). This Actor is stateless by comparison, keeps no history between runs, and does one thing: a single-run value ranking with the published rate components underneath it.

If you are building an AI agent or a RAG pipeline, the output-format row is the decision-maker: parsing HTML inside an agent loop is a reliability failure mode, not a feature. Check the rate-detail row too โ€” a single price field cannot tell your model whether a discount is real or whether taxes are already included.


How many hotels can you scrape in one run?

maxProperties sets the ceiling: default 10, minimum 1, maximum 20000. There is no other hard cap inside the Actor โ€” no separate item limit, no internal page limit.

Pagination is offset-based: the Actor builds a Booking search URL, extracts every property-card link, then re-requests the same URL with &offset= incremented by 25 until it has maxProperties links or a page returns no new cards, pausing one second between pages. Detail pages are then fetched one at a time, each with up to 9 attempts across the proxy ladder. Any searchresults URL you pass in stayUrls is expanded the same way, and the combined list is truncated to maxProperties.

Two practical limits sit above the schema. Booking's own search paging depth bounds how many distinct properties one destination query can surface, so a large maxProperties on a small city stops early with "No more hotels found at offset N." And because the Actor is sequential and buffers the full result set before writing, the run timeout โ€” not maxProperties โ€” usually decides how large a run can be. No benchmark timing is quoted here because none has been measured.

What a run costs

Pricing is pay-per-event, declared in .actor/actor.json: $0.005 once per run for actor-start, and $0.01 per hotel row successfully pushed (row_result). Properties that fail every retry are skipped and never charged, and once your event limit is reached the Actor stops pushing and exits with "User spending limit reached". Rows that came back thin โ€” a property with no price, so valueScore: null โ€” are still pushed and still charged, because they are still a successfully scraped property record. Apify platform compute and proxy usage are billed separately by Apify under your plan.


๐Ÿ”Œ Integrate the Rate & Value Report into your workflow

The Actor runs on Apify, so it works with any language or tool that can send an HTTP request to the Apify API โ€” authentication is your Apify API token, and there is no separate account or key for the Actor itself.

REST API integration

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_API_TOKEN>")
run_input = {
"location": "Amsterdam",
"maxProperties": 15,
"sortResultsBy": "bestValue",
"stayCheckIn": "2026-09-12",
"stayCheckOut": "2026-09-15",
"resultCurrency": "EUR",
}
run = client.actor("<YOUR_USERNAME>/booking-scraper-rate-value-report").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
rate = (item.get("rateBreakdown") or [{}])[0]
print(item["valueRank"], item["name"], item["price"], item["currency"],
item["valueScore"], rate.get("boardType"), rate.get("discountPct"))

Works in Python, Node.js, Go, Ruby, cURL โ€” the same run-and-read-dataset pattern applies to every Apify API client.

Automation platforms (n8n, Make, LangChain)

n8n ships an official Apify node: choose the Run Actor operation, select this Actor, paste the JSON input, and pipe the dataset items onward โ€” a Postgres insert, a Slack digest of the top three valueRank rows, or a Google Sheets append.

Make has an Apify app with Run an Actor and Get Dataset Items modules. A common build is a scheduled scenario that runs the Actor each morning and filters to rows where rateBreakdown[].discountPct crosses a threshold before creating a record or firing an alert.

LangChain connects through the Apify integration: ApifyWrapper / ApifyDatasetLoader calls the Actor and maps each dataset item to a Document, so a value-ranked accommodation dataset can be embedded into a vector store and queried by a travel agent with no HTML in the pipeline.

Apify webhooks (fire on run success, POST the dataset id to your endpoint) and Apify schedules cover recurring monitoring.


Scraping publicly accessible Booking.com pages is generally lawful in most jurisdictions, and this Actor collects only such pages โ€” it does not log in, does not access member-only or loyalty-gated rates, does not bypass authentication, and never touches the booking or payment flow.

Most of what it returns is business and product data: listings, published rates, discounts, taxes, facilities and house rules. That category is governed by Booking.com's terms of service and, in the EU, by database rights โ€” review both before commercial redistribution and keep request volumes reasonable.

One part needs different handling. traderInfo, hostInfo and licenseInfo can contain a natural person's name, email, phone and postal address when the property is let by an individual rather than a company. That subset is personal data under GDPR and CCPA and requires a lawful basis to store or process. No reviewer identities and no guest data are collected. Consult legal counsel for commercial use cases involving bulk personal data.


โ“ Frequently asked questions

Does the Rate & Value Report work without a Booking.com account?

Yes. The Actor never logs in and holds no Booking.com credentials โ€” it fetches public search and hotel pages anonymously through Apify Proxy. The only credential involved anywhere is your Apify API token, and only if you start runs over the API rather than from the Console.

How often is the scraped data updated?

Every run is a live fetch. Nothing is cached between runs and no rate history is stored, so each row reflects the page as Booking served it at timeOfScrapeISO. For a time series, schedule the Actor and join successive datasets on source_url (or hotelId) plus checkInDate.

Why is rateBreakdown empty for some properties?

Almost always because the run had no stay dates. Booking only injects priced availability blocks for a specific check-in/check-out pair, so a dateless run returns rateBreakdown: [] by design โ€” the Actor logs a warning rather than inventing a rate. It is also empty when includeRateBreakdown is false, when the property is sold out for your dates, or when Booking served a layout with neither Apollo room blocks nor the legacy rooms table. price can still be populated from Booking's inline cheapest-price value.

What happens when a property is delisted or a page returns nothing?

The property is retried through the full proxy ladder โ€” up to 9 attempts across RESIDENTIAL, DATACENTER and NO_PROXY โ€” and if no hotel name can be extracted, the Actor logs Giving up on <url> and skips it. No partial row is pushed and no row_result event is charged. If nothing could be extracted at all, the run finishes cleanly with "Nothing extracted from any hotel URL" and an empty dataset, so downstream jobs see zero rows rather than malformed ones.

Can I scrape login-gated or member-only Booking.com rates?

No. Only publicly accessible content is returned. Genius and other loyalty-gated prices, anything behind a Booking.com login, guest or reviewer personal details, and the booking/checkout flow are all outside what this Actor reads. If a rate is only visible to a signed-in member, it will not appear in rateBreakdown.

How do I try it before committing to a large run?

Start it from its Apify Store listing with the prefilled input โ€” location: "Amsterdam", maxProperties: 10 โ€” and add your own stay dates. Under pay-per-event a small run costs the actor-start event plus one row_result per property returned, so a 10-property test is easy to price before scaling maxProperties up.

Does it work for AI agent workflows and LLM pipelines?

Yes. The Actor is callable as an HTTP endpoint by any agent framework through the Apify API โ€” LangChain, LlamaIndex, a custom tool-calling loop, or an n8n/Make automation. Every response is typed JSON with stable keys, so nothing needs parsing between the scrape and the model's context window. valueScore, rateBreakdown[].boardType and rateBreakdown[].refundable map directly onto the constraints a travel agent is asked to satisfy.

How does the Actor handle Booking.com's anti-bot system?

With three layers, all in the source. Requests go out through an impit client that impersonates Chrome's TLS fingerprint, over an Apify Residential proxy by default. A response that looks blocked โ€” under 3000 characters, or containing captcha, robot check, are you a robot, px-captcha or access denied โ€” triggers a headless Chromium fallback with a real browser context, Chrome user agent, 1366ร—900 viewport and automation flags disabled. Repeated failures walk a proxy ladder: RESIDENTIAL, then DATACENTER, then no proxy, 3 retries per tier. Pin a proxy country with apifyProxyCountry in proxyConfiguration.

Does it return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with stable field names and consistent key ordering on every row โ€” no HTML, no selectors, no post-processing. Pass the dataset straight into an LLM context window, index it into a vector store, or expose it as an agent tool. Absent values are explicit null / [], which is what a model needs to reason about missing data instead of hallucinating around a blank string.

Can I use it without managing proxies?

Yes. proxyConfiguration defaults to Apify Proxy with the RESIDENTIAL group, and the fallback ladder, retry counts and browser fallback are handled inside the Actor. You do not supply proxy credentials, rotate sessions, or configure a browser. Change it only for a specific reason โ€” a country requirement, or your own proxy setup.

What happens when Booking.com changes its page structure?

The output schema stays put. The extractor reads four independent layers of every page โ€” Apollo store, JSON-LD, inline window.booking variables, DOM selectors โ€” and merges what each yields, so a markup change usually costs one field rather than the run. Key names and types do not change on your side, so warehouse tables and agent tools keep working while the scraper is updated behind them.


๐Ÿ’ฌ Your feedback

Found a bug, hit a Booking.com layout the extractor does not cover, or need a field that is on the page but not in the schema? We want to know. Open an issue on the Actor's Issues tab on its Apify Store listing โ€” include your input JSON and the run id, and the parsing path can be traced from the run log. Feature requests for new filters or extra rate components are welcome there too.