Booking.com Hotels, Prices and Reviews Scraper
Pricing
from $1.20 / 1,000 properties
Booking.com Hotels, Prices and Reviews Scraper
Scrape booking.com hotels, apartments, villas and hostels. Search any destination with the site's own filters (type, stars, review score, budget, meals, cancellation, amenities) or paste links. Live prices in 36 currencies, room-level rates, facilities, house rules and guest reviews.
Booking.com Scraper: Hotel Prices, Rooms and Guest Reviews
Booking.com Scraper turns booking.com into structured accommodation data. Search a destination and a stay, or paste links you already built on the site, and get back one row per property with a live price in the currency you choose, the guest review score, the exact location, the matched room configuration and, when you turn it on, the whole property page plus its guest reviews. It covers hotels, apartments, villas, hostels, guesthouses, bed and breakfasts, campgrounds and every other kind of place the site lists. Export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- Two ways to find data. Name a destination and let the actor search, or paste links you already built (a search results page or one property) and get exactly what you saw.
- Real prices for real dates. Every price is for the stay you asked for, converted into one of 36 currencies, with the property's own currency returned alongside it so nothing is hidden behind an exchange rate.
- The site's own filters, plus three it does not enforce itself. Type, star rating, review score, budget, meals, cancellation policy, amenities, bed type and travel group are all sent as the site's own filter tokens. Minimum review count, sold out status and actual availability for your dates are checked by the actor afterward, because the site's own tokens for those do not narrow anything.
- Property depth on demand. Turn on the property's own page for facilities, house rules, the room catalogue, policies and photos, and turn on guest reviews separately. The two share one per-property surcharge, billed only for returned properties where the extra read returned data. Reviews are on by default (20 per property); set them to
0to skip the surcharge. - Built for monitoring. Incremental mode returns only new and changed properties on a schedule, and resume continues an interrupted run without re-collecting what it already has.
- Honest about the site's own limits. A destination bigger than the site will serve anonymously is reported as such in the log, not silently truncated with no explanation.
Use Cases
- Travel search and metasearch: build a comparison feed of live prices, availability and review scores across many destinations.
- Revenue management and competitor pricing: track a named set of properties for price and availability changes on a recurring schedule.
- Review and reputation analysis: pull guest review text, category scores and traveller types to summarize what guests actually say about a property.
- Booking assistants and AI agents: look up a destination or a specific property on demand and pipe structured results into your own tool or MCP client.
- Market research: profile the accommodation mix (property types, star ratings, amenities) of a destination.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
propertyId / propertyName | "0000000" / "Example Harbour Hotel" |
propertyUrl / pageName | link to the property page / "example-harbour-hotel" |
accommodationTypeId / hostType | 999 (a type code) / "PROFESSIONAL" (the host label the site shows, when it shows one) |
starRating / starRatingSymbol | 4 / "STARS" |
reviewScore / reviewScoreLabel / reviewCount | 9.2 / "Wonderful" / 1348 |
secondaryReviewScore / externalReviewScore | a second, source specific score / a score from an outside review site (when present) |
address / city / countryCode | "1 Example Street" / "Sydney" / "au" |
latitude / longitude | -33.9 / 151.1 |
displayLocation / distanceFromSearchCentre | "Sydney, Australia" / "1.2 km from centre" |
mainPhoto / thumbnailPhoto | property photo URLs |
price.perStay / price.perNight / price.currency | 310.0 / 155.0 / "USD" |
price.propertyCurrency / price.perStayInPropertyCurrency | "AUD" / 465.0 |
rooms[] | roomId, price, mealPlan, occupancy, freeCancellationUntil, urgencyMessage |
matchedUnit | bedrooms, bathrooms, beds, kitchens, living rooms, floor area |
freeCancellation / noPrepayment / petsAllowedFreeOfCharge | true / false / false |
isPreferredPartner / hasGeniusRate / isSustainable | false / true / false |
isSoldOut / isAvailableForDates | false / true |
checkIn / checkOut / nights / adults / children / roomsRequested | the stay actually used for this row |
facilityGroups / topFacilities / houseRules / roomTypes | the property's own page (only with fetchDetails on) |
childrenAndBeds / petsPolicy / damagePolicy / finePrint / photos | more property page fields (only with fetchDetails on) |
reviews[] | score, title, what the guest liked and did not, language, the stay, the traveller type, the property's own reply, and the reviewer's public display name and country (only with maxReviewsPerProperty above 0) |
changeType / changedFields / firstSeenAt / lastSeenAt | incremental mode only: "UPDATED" / ["price"] / timestamps |
Every row is one property. Guest reviews live inside that same row as reviews[]; they are never a separate record.
How to Use
- Pick a mode:
search(name one or more destinations) orurl(paste booking.com links). - In search mode, name your destinations and set the stay (check-in, check-out, adults, rooms, currency), then narrow with the site's own filters.
- Turn on Read each property's own page and/or set Guest reviews per property if you want the property's full detail and its reviews.
- Set Max results to control run size and cost, then click Start.
Search a destination with filters:
{"mode": "search","destinations": ["Sydney", "Lisbon"],"checkIn": "2026-11-02","checkOut": "2026-11-04","adults": 2,"rooms": 1,"currency": "USD","starRatings": ["4", "5"],"minReviewScore": "8","maxPricePerNight": 350,"reservationPolicies": ["free_cancellation"],"amenities": ["swimming_pool", "free_wifi"],"sortBy": "rating_high_to_low","maxItems": 50}
Paste links (a search page and one property), with full detail and reviews:
{"mode": "url","urls": ["https://www.booking.com/searchresults.html?ss=Example+Town&checkin=2026-11-02&checkout=2026-11-04","https://www.booking.com/hotel/au/example-harbour-hotel.html"],"fetchDetails": true,"maxReviewsPerProperty": 50}
Monitor a destination on a schedule:
{"mode": "search","destinations": ["Sydney"],"checkIn": "2026-11-02","checkOut": "2026-11-04","incrementalMode": true,"stateKey": "sydney-watch","emitExpired": true,"maxItems": 0}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/booking-com-scraper").call(run_input={"mode": "search", "destinations": ["Sydney"],"checkIn": "2026-11-02", "checkOut": "2026-11-04"})for prop in client.dataset(run["defaultDatasetId"]).iterate_items():print(prop["propertyName"], prop["price"]["perNight"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/booking-com-scraper').call({mode: 'search', destinations: ['Sydney'],checkIn: '2026-11-02', checkOut: '2026-11-04',});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
How Max results shapes a run
With several destinations, Max results is split evenly across them so one destination cannot fill the whole run on its own. Each destination is then walked page by page, most relevant first unless you change Sort by, until its own share is filled or the source runs out. One search cannot go past 1000 properties; the site stops serving new results there regardless of the total it declares, and the run says so in the log. Set sortBy to price_low_to_high or rating_high_to_low when the first N matter, and split a very large destination by neighbourhood, property type, star rating or budget band to reach further into it.
Two things worth knowing about how Max results interacts with the other run modes:
- Max results is spent on properties read, not properties returned, whenever something drops rows after the read. Each destination stops reading pages once it has read its share, and only then are rows dropped: unchanged properties on an incremental run, and properties that fail the three checks the actor applies itself (Minimum number of reviews, Drop sold-out properties, Only properties available for these dates). So a run where most of the first page is unchanged, or fails one of those checks, can return well under Max results even though qualifying properties further down the result list were never reached. If a run returns fewer rows than expected, raise Max results well above what you expect to receive, or set it to
0. - With more than one destination or pasted search link in the same run, a property that appears in two overlapping searches can be silently lost from both. A destination reads a whole page (25 properties) before checking whether its own share is full, and every property on that page is recorded as handled, including the ones past the share that it then does not return. A later destination that finds those same properties skips them as already handled, even when it has room left, so they are returned by neither. If your destinations or links are likely to overlap (neighbouring searches, or the same city under two names), run them as separate actor runs instead of combining them in one.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every property that run already collected, so you are not charged for it twice. - Incremental mode (
incrementalMode) is for scheduled runs against the same search or links. Each property is classifiedNEW,UPDATED(with achangedFieldslist),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED.EXPIREDis only emitted (withemitExpiredon) after a run that reached the natural end of every destination or search link with nothing capped, resumed or refused, so a partial search never marks a live property as gone (see the exception for pasted property links below).stateKeynames or shares the stored state; leave it empty and the actor derives one from your search or links. With incremental mode off, output is exactly as before.
Three behaviors worth knowing before you rely on incremental mode:
- An
emitExpiredrun does not cap the number of EXPIRED rows at Max results. Once a run is complete enough to mark properties EXPIRED at all, every tracked property that was not found this time is returned, however many that is. On a large tracked destination where many properties disappeared at once, this can return and bill far more EXPIRED rows than the Max results you set for the run. - In
urlmode, a pasted property link that could not be read is treated as gone. Unlike a destination or a pasted search link, a refused property link does not stop the run from counting as complete, so withemitExpiredon that property is returned (and billed) as EXPIRED, then returned (and billed) again as REAPPEARED on the next run that reads it. The log still warns that some requests could not be read. If you track individual property links, leaveemitExpiredoff, or check that warning before acting on EXPIRED rows. - Starting an incremental campaign with Resume re-bills the resumed properties as UPDATED once, on the next ordinary run, even if nothing about them changed. Resume is only accepted on a campaign with no saved state yet. It seeds the properties it skips into the new baseline without a comparable fingerprint, purely so that run does not wrongly mark them EXPIRED. The next scheduled run that actually re-reads them cannot tell that placeholder apart from a real change, so it reports each of them as UPDATED with every field listed as changed. It settles after that one run.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each property, not the full JSON. Each property becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify โ Settings โ Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "MCP connectors" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where property pages should be created. - Use
maxNotifyPropertiesto cap how many properties are pushed per run; the dataset always keeps every one of them.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (name destinations) or url (paste booking.com links). |
destinations | array | sample | Destinations to search, one per line, exactly as typed into the site's own search box. |
propertyTypes | array | [] | Keep only these kinds of place. Several widen the search. Search mode only. |
starRatings | array | [] | Keep only these star ratings. Several widen the search. |
minReviewScore | string | any | Minimum guest review score out of 10. |
minPricePerNight | integer | (none) | Lower bound of the nightly budget, in the currency below. |
maxPricePerNight | integer | (none) | Upper bound of the nightly budget, in the currency below. |
mealPlans | array | [] | Keep only properties offering ALL of the picked meal arrangements. |
reservationPolicies | array | [] | Keep only properties matching ALL of the picked booking conditions. |
amenities | array | [] | Keep only properties with ALL of the picked property amenities. |
roomAmenities | array | [] | Keep only properties whose rooms have ALL of the picked amenities. |
bedPreferences | array | [] | Keep only properties offering these bed types. Several widen the search. |
travelGroups | array | [] | Keep only properties suited to ALL of the picked traveller types. |
sortBy | string | relevance | Result order, which decides WHICH properties you get when Max results stops the run early. Search mode only. |
urls | array | sample | One booking.com link per line: a search link or a property link. |
checkIn | string | (none) | YYYY-MM-DD. Left empty, prices a 2 night stay 30 days out and says so in the log. |
checkOut | string | (none) | YYYY-MM-DD, after check-in. Left empty, becomes 2 nights after check-in. |
adults | integer | 2 | Adults in the party. |
childrenAges | array | (none) | One age (0 to 17) per child, one per line. |
rooms | integer | 1 | Rooms the party needs. |
currency | string | USD | Currency for every price in the output and for the budget filters above. |
language | string | en-us | Language for names, room names, facility labels and review text where translated. |
availableOnly | boolean | false | Drop properties not actually bookable for your dates. Applied by the actor after the search. |
minReviewCount | integer | (none) | Drop properties with fewer reviews than this. Applied by the actor after the search. |
excludeSoldOut | boolean | false | Drop properties the site has explicitly flagged sold out. Applied by the actor after the search. |
fetchDetails | boolean | false | Read each property's own page: facilities, house rules, policies, room catalogue, photos. Adds the per-property surcharge. |
maxReviewsPerProperty | integer | 20 | Guest reviews to attach per property, newest first. 0 returns none. Adds the per-property surcharge. |
maxItems | integer | 20 | Stop after this many properties. 0 means no limit (up to 1000 per search). |
maxPages | integer | 0 | Stop each destination after this many result pages. 0 walks until results run out. |
resumeFromRunId | string | (none) | Continue one interrupted run, skipping what it already collected. |
incrementalMode | boolean | false | Return only NEW, UPDATED and REAPPEARED properties on a scheduled run over the same search. |
stateKey | string | (none) | Name this monitoring campaign. Leave empty to derive one from the search or links. |
emitUnchanged | boolean | false | Incremental mode only. Also return (and bill) properties that did not change. |
emitExpired | boolean | false | Incremental mode only. Also return (and bill) a row for each tracked property no longer found. |
proxy | object | Apify Proxy | Connection settings. |
mcpConnectors | array | (none) | Optional: send a summary of each property to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which property pages are created. |
maxNotifyProperties | integer | 50 | Cap on properties written to each connector per run. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"propertyId": "0000000","propertyName": "Example Harbour Hotel","propertyUrl": "https://www.booking.com/hotel/au/example-harbour-hotel.html","pageName": "example-harbour-hotel","accommodationTypeId": 999,"destinationId": -1,"starRating": 4,"starRatingSymbol": "STARS","reviewScore": 9.2,"reviewScoreLabel": "Wonderful","reviewCount": 1348,"address": "1 Example Street","city": "Sydney","countryCode": "au","latitude": -33.9,"longitude": 151.1,"displayLocation": "Sydney, Australia","price": {"currency": "USD","perStay": 310.0,"perNight": 155.0,"perStayFormatted": "$310.00","beforeDiscount": 360.0,"chargesInfo": "Includes taxes and fees","propertyCurrency": "AUD","perStayInPropertyCurrency": 465.0},"rooms": [{ "roomId": "0000010", "occupancy": 2, "price": 310.0, "currency": "USD","mealPlan": "Breakfast included", "freeCancellationUntil": "2026-10-30" }],"matchedUnit": { "bedrooms": 1, "bathrooms": 1, "beds": 1,"area": { "value": 30, "unit": "m2" } },"freeCancellation": true,"noPrepayment": false,"petsAllowedFreeOfCharge": false,"isSoldOut": false,"isAvailableForDates": true,"checkIn": "2026-11-02","checkOut": "2026-11-04","nights": 2,"adults": 2,"children": 0,"roomsRequested": 1,"houseRules": { "checkInFrom": "2:00 PM", "checkOutUntil": "10:00 AM" },"reviews": [{ "score": 8.0, "title": "Great stay","positiveText": "Comfortable room and helpful staff","negativeText": "Breakfast area was busy", "reviewerCountry": "Canada","travellerType": "FAMILIES", "nights": 2, "reviewedAt": "2026-09-01T00:00:00Z" }],"searchMode": "search","searchQuery": "Sydney","scrapedAt": "2026-09-10T12:00:00Z"}
Plan Requirement
The default proxy setting works out of the box, and the actor rotates connections automatically when one is refused. The Pricing tab on the actor page shows what a run costs.
FAQ
How much does it cost?
You pay a fixed actor start fee, then per property returned, and once more for each returned property whose extra pages were read and returned data. The property page and its reviews share ONE per-property surcharge, so turning on both costs the same surcharge as turning on one. Guest reviews are on by default (20 per property), so set Guest reviews per property to 0 if you only need the result-page fields. A pasted property link always carries the surcharge, because its own page is the whole record. The Pricing tab shows the current rates. Use Max results to cap the cost of any run.
Is it legal to scrape Booking.com?
This actor collects data that booking.com publishes to anyone, without logging in, and it does not collect personal contact details. You are responsible for how you use the results: follow booking.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Guest review text and public reviewer display names are user generated content published by the site.
Can I get only new or changed properties on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared properties, with a changedFields list, and unchanged ones are not billed unless you turn that on too.
Why did a run return fewer properties than Max results?
Three common reasons. A destination bigger than 1000 properties stops there regardless of what it declares as its total; narrow the search to reach further into it. Max results is spent on properties read from the source, not properties actually returned, so a run where many rows are dropped afterward (unchanged on an incremental run, or failing the minimum review count, sold-out or availability checks) can return well under Max results even though qualifying properties exist further down the list; raise Max results, or set it to 0. And with several overlapping destinations or search links in one run, a property one of them read past its own share can be skipped by the others; run overlapping searches separately.
Why did some properties come back as UPDATED right after I used Resume?
When Resume starts a new incremental campaign, it seeds the properties it skipped into the baseline without a real fingerprint, so the next ordinary scheduled run that actually re-reads them cannot tell that from a genuine change and reports each of them as UPDATED with every field listed as changed. It happens once, on that next run, and settles back to normal after that.
Why did my run fail instead of returning an empty dataset?
If the site refuses every request, the run stops with a clear message so "no properties matched" is never confused with "nothing could be read". Run it again in a few minutes. If only some destinations or links could be read, you still get what was found, with a warning that the results may be incomplete.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
๐ Want more travel data?
Pair this actor with these related scrapers from the same team:
| ๐จ Expedia Hotels Scraper From $1/1K. Scrape Expedia.com hotel listings and reviews for any destination. Extract... | ๐จ Trip.com Hotels, Rooms & Reviews Scraper Pull structured hotel listings from trip.com with prices, room types, amenities... |
| ๐จ Wego Scraper Scrape Wego hotels by city or search URL. Get rates, providers, booking links... | ๐จ Vrbo Scraper Extract vacation rental data from Vrbo.com by location, region, coordinates, property... |
| ๐จ Rakuten Travel Scraper Scrape Rakuten Travel, Japan's largest domestic hotel-booking site, by prefecture... | ๐จ Airbnb Scraper Scrape Airbnb stays by destination or link: nightly and total price, rating breakdown... |
๐ Browse all abotapi scrapers
๐ฌ Support & custom scrapers
- ๐ Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- ๐ ๏ธ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- โญ Enjoying it? A quick review on the actor page helps other users find it.