Scraping Booking.com means programmatically pulling the hotel listings a search returns — names, prices, review scores, locations — instead of copying them by hand. It's one of the most requested travel-data tasks: hotel owners tracking competitor rates, analysts building price indexes, and agencies monitoring availability all want the same thing. This tutorial shows a short, modern script to extract a hotel list, then covers selectors, pagination, anti-blocking, and the legal boundaries you should know before you run it at scale.
A decade ago you could drive Booking.com with a dozen lines of Selenium and no defenses to speak of. That code no longer works — both the Selenium API and Booking's front end have changed, and the site now runs serious anti-bot protection. Here's how to do it properly in 2026.
The plan
Booking.com is a JavaScript-heavy site: the search results are rendered client-side, so a plain HTTP fetch of the search URL won't contain the hotel cards. You have two realistic routes:
- Drive a headless browser (Playwright or Selenium 4) so the page fully renders, then read the DOM.
- Hit the underlying data — Booking builds results from JSON embedded in the page and from XHR/GraphQL calls. Parsing that JSON is faster and less brittle than scraping rendered HTML, when you can find it.
We'll use the browser approach first because it's the most approachable, then point at the JSON route for scale.
Building the search URL
You don't have to fill out the search form by clicking through autocomplete anymore. Booking encodes the whole search in the URL query string, so you can construct it directly:
https://www.booking.com/searchresults.html?ss=Berlin&checkin=2026-08-01&checkout=2026-08-03&group_adults=2&no_rooms=1&offset=0
Key parameters:
ss— the search string (destination).checkin/checkout— ISO dates.group_adults,no_rooms,group_children— occupancy.offset— pagination cursor (0, 25, 50, …); this is how you page through results.
Constructing the URL beats automating the destination autocomplete, which was the fiddly part of the old approach.
A modern Playwright script (Python)
Playwright is the current tool of choice for browser automation — faster and more reliable than legacy Selenium for this kind of job. It renders the page, so you get the client-side hotel cards.
from playwright.sync_api import sync_playwright
from urllib.parse import urlencode
def search_url(city, checkin, checkout, offset=0):
params = {
"ss": city, "checkin": checkin, "checkout": checkout,
"group_adults": 2, "no_rooms": 1, "offset": offset,
}
return "https://www.booking.com/searchresults.html?" + urlencode(params)
def scrape_hotels(city, checkin, checkout, max_pages=3):
results = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(locale="en-US")
for i in range(max_pages):
page.goto(search_url(city, checkin, checkout, offset=i * 25),
wait_until="domcontentloaded")
page.wait_for_selector('[data-testid="property-card"]', timeout=15000)
cards = page.query_selector_all('[data-testid="property-card"]')
if not cards:
break
for card in cards:
name = card.query_selector('[data-testid="title"]')
price = card.query_selector('[data-testid="price-and-discounted-price"]')
score = card.query_selector('[data-testid="review-score"]')
results.append({
"name": name.inner_text().strip() if name else None,
"price": price.inner_text().strip() if price else None,
"score": score.inner_text().split("\n")[0] if score else None,
})
browser.close()
return results
for hotel in scrape_hotels("Berlin", "2026-08-01", "2026-08-03"):
print(hotel)
A few things to note:
- Selectors use
data-testid. Booking's class names are obfuscated and change constantly, but thedata-testidattributes (property-card,title,price-and-discounted-price,review-score) are more stable because the site's own tests rely on them. Still, treat every selector as temporary — always re-inspect the live page in DevTools before trusting a scraper you haven't run in a while. wait_for_selectorreplaces the old manualWebDriverWaiton autocomplete. You wait for the results to render, not for a dropdown.- Pagination is just incrementing
offsetby 25 (the page size) until no cards come back.
The Selenium 4 equivalent
If you're maintaining an existing Selenium codebase, note that the old driver.find_element_by_css_selector(...) API was removed in Selenium 4. The modern form is:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
cards = WebDriverWait(driver, 15).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, '[data-testid="property-card"]')
)
)
for card in cards:
name = card.find_element(By.CSS_SELECTOR, '[data-testid="title"]').text
print(name)
Same idea, current syntax. For new projects, prefer Playwright.
Finding selectors yourself
Because Booking rewrites its markup often, you'll re-derive selectors regularly. Open the results page, press F12 (or Ctrl+Shift+I) to open DevTools, use the element picker to click a hotel name, and read off a stable attribute — prefer data-testid, id, or an ARIA role over a random hashed class like .a78ca197d0. The general skill is covered in our guide to CSS selectors for scraping.
The faster route: parse the embedded JSON
Rendering a browser for every page is slow and resource-heavy. Booking ships much of the result data as JSON inside the HTML (look for a <script type="application/json"> block or a window.__INITIAL_STATE__-style object) and fetches more via GraphQL XHR calls. If you can locate that payload, you can:
- Fetch the search HTML (sometimes with a browser to pass anti-bot checks), extract the JSON blob, and read structured fields directly — no fragile HTML selectors.
- Or replay the GraphQL/XHR request the page makes, with the right headers, and parse the JSON response.
This is the API-style scraping approach, and it's what you want at scale: cleaner data, fewer breakages, far less CPU. The catch is that these internal endpoints are undocumented and change without notice.
Anti-blocking: the real difficulty
The script above will work for a handful of requests and then start hitting captchas, "unusual traffic" pages, or outright blocks. Booking.com uses browser fingerprinting, rate analysis, and challenge pages. To scrape reliably you need the standard anti-blocking toolkit:
- Residential rotating proxies. Datacenter IPs get flagged fast on travel sites. Rotate residential proxies so requests come from many real-looking addresses, and use sticky sessions where a search spans several pages.
- Realistic browser fingerprint. Use a genuine headless Chromium (Playwright's), set a real
User-Agentandlocale, and consider stealth plugins that mask automation signals likenavigator.webdriver. - Human-like pacing. Add randomized delays, don't hammer 25 pages in two seconds, and cap concurrency per IP.
- Captcha handling. When a challenge appears, route it through a captcha-solving service or back off and retry later from a fresh IP.
- Cache and dedupe. Don't re-fetch pages you already have; store results in a database keyed by hotel ID and date so a rerun only pulls what changed.
Realistically, a robust Booking.com scraper is 20% extraction logic and 80% staying unblocked. Budget accordingly.
Other travel sites: Expedia, Hotels.com, Airbnb
The same pattern generalizes to the big online travel agencies (OTAs). A small hotel owner who wants to track a few competitors' nightly rates on Expedia or Hotels.com has the same three options as anyone facing an aggregator:
- Custom script — the Playwright/JSON approach above, tuned to that site's markup and search parameters, plus a proxy service and a database to store daily snapshots. Most control, most maintenance.
- Off-the-shelf scraping tools or a service — no-code scrapers and hosted APIs that handle rendering and proxies for you. Good when you don't want to run code and just need a recurring export.
- Browser extensions — point-and-click scrapers that grab data on demand while you browse. Fine for pulling "the current price of a few specific hotels" occasionally, but not for large or scheduled jobs.
For a daily rate-tracking need — exactly the "current room price of some hotels, every day" ask — option 1 or 2 makes sense because the data changes constantly and has to be re-fetched on a schedule. Aggregators are also the most aggressive about anti-scraping defenses, so plan for proxies from the start. If you'd rather skip the plumbing entirely, scraping.pro runs hotel and OTA price feeds as a done-for-you web scraping service, delivering clean daily datasets to your schema.
Is scraping Booking.com legal?
A practical, non-lawyer summary:
- Public, factual data — hotel names, publicly displayed prices, star ratings — is the lower-risk category. Facts aren't copyrightable, and courts (e.g. the hiQ v. LinkedIn line of cases in the US) have generally treated scraping publicly accessible data more leniently than sites would like.
- Terms of Service — Booking.com's ToS prohibit automated access. Breaching ToS is a contract matter, not automatically a criminal one, but it's a real risk, especially if you have an account you agreed to those terms under.
- Personal data — reviewer names and any personal information fall under GDPR/CCPA. Avoid collecting personal data unless you have a lawful basis.
- Copyright and load — don't republish photos or descriptive copy wholesale, and don't hammer the servers; excessive request volume can cross into "damage" territory.
Rules of thumb: scrape only public factual fields, respect robots.txt and rate limits, don't collect personal data, cache to minimize load, and get proper legal advice before a commercial launch. None of this is legal advice — jurisdictions differ.
The bottom line
The old ten-line Selenium snippet is a museum piece: the API changed, the markup is obfuscated, and Booking.com now fights bots hard. The modern recipe is a headless browser (Playwright) with stable data-testid selectors and URL-encoded searches for a quick start, the embedded-JSON/GraphQL route for scale, and a serious anti-blocking layer — rotating residential proxies, realistic fingerprints, human pacing, and captcha handling — to keep it running. Get those pieces right and extracting a hotel list is the easy part.