How to Scrape Hotel Prices with Python (and When You Shouldn't)

Denis Gramm, StayAPI
By Denis Gramm, StayAPI · · 11 min read

I hope you enjoy reading this blog post. If you want our team to help you with hotel data integration, click here.

Quick summary

You need hotel prices in your code, and the booking sites don't hand them out. This tutorial walks through the real Python approach: finding where prices load, requesting them, parsing them, and keeping the whole thing alive. Then it puts honest numbers on the maintenance so you know exactly when a data API becomes the better call.

How to Scrape Hotel Prices with Python (and When You Shouldn't) — StayAPI

You need to scrape hotel prices because your code needs them: for a price tracker, a comp-set monitor, a research project.
And no OTA hands that data out.
This is a real tutorial.
You'll find where prices actually load, pull them with requests, fall back to Playwright when that fails, and normalize what comes back.
We sell the alternative (a data API), and this tutorial still teaches the real thing.
The build-vs-buy decision only makes sense once you know what the build costs.

Key Takeaways

  • Hotel prices arrive via JavaScript after page load. The initial HTML is empty of them, so the first skill is DevTools network inspection.
  • Go requests-first, Playwright second. The lightweight path works until consent walls and rotating tokens kill it; a headless browser is 10 to 50x slower and heavier.
  • Parsing is where scrapers silently lie. Taxes in or out, per-night vs total stay, ambiguous currency symbols: two scrapes of the same page can disagree.
  • Maintenance is the real cost. Markup churn, anti-bot escalation, and proxy bills put a multi-OTA scraper at a chunk of an engineer's month, every month.
  • Scraping is a fine teacher; production feeds want an API. If the prices feed a decision someone is paid to make, buy the pipe.

Before You Scrape: Ground Rules and Targets

A few practical rules keep your scraper (and your conscience) clean.
Throttle hard: one request every few seconds, not ten per second.
Treat robots.txt as a statement of intent, don't scrape anything behind a login, and don't store personal data from reviewer profiles.
This article also stays in one lane: nightly rates for specific hotels and dates.
If you're after names, amenities, and photos across hundreds of properties, that's a catalog job, and we cover it in the hotel listings scraping tutorial.
Know your target before you write a line, because difficulty varies wildly by OTA.

Target Difficulty What you're up against
Google Hotels Moderate Prices render via JavaScript; obfuscated markup, mild blocking
Expedia Hard Active bot detection, plus member rates muddying what you parse
Booking.com Hardest The strongest anti-bot stack in the vertical: CAPTCHAs, fingerprinting, IP bans

Booking.com deserves its reputation; our Booking.com API guide covers why scraping it degrades so fast.
The tutorial below uses Google Hotels, the least hostile major target and the one people most often want to scrape with Python.

The Tutorial: Scrape Hotel Prices with Python

The Tutorial: Scrape Hotel Prices with Python — StayAPI

The worked example is Google Hotels, so this doubles as a guide to scrape Google Hotels with Python.
The method transfers to any OTA; only the pain level changes.

Step 1: Find where the prices actually load

Open a Google Hotels search in Chrome, then open DevTools → Network → filter by Fetch/XHR.
Reload and watch the requests as prices appear.
Somewhere in that list is a response carrying the price payload.
Click through the responses and search for a price you can see on the page.
Learn this inspection method rather than memorizing any specific endpoint.
Endpoints churn constantly; the method survives every redesign.
Prices on Google Hotels come from partner bidding across booking sites, which is why the same room shows several prices (our Google Hotels API guide explains that auction).

Step 2: The requests-first attempt

If the XHR you found works without a browser context, requests is the cheap path.

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36",
    "Accept-Language": "en-US,en;q=0.9",
}

# Paste the XHR URL you found in DevTools. It encodes the query,
# dates, and usually a session token that expires.
url = "https://www.google.com/travel/search?q=hotels%20in%20lisbon&..."

resp = requests.get(url, headers=headers, timeout=15)
print(resp.status_code)

When it works, you get the payload for the cost of one HTTP call.
The typical failure modes: a 403, a redirect to a consent page instead of data, or a token in the URL that rotates every session.
Any of those means the lightweight path is closed, and you escalate.

Step 3: The Playwright fallback

A headless browser executes the JavaScript, so you scrape what a human would see.

import time
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(
        "https://www.google.com/travel/hotels?q=hotels%20in%20lisbon",
        wait_until="networkidle",   # wait for the XHR traffic to settle
    )

    # Class names are obfuscated and churn weekly.
    # Accessibility labels churn far less, so anchor on those.
    nodes = page.locator("[aria-label*='per night']")
    for i in range(nodes.count()):
        print(nodes.nth(i).get_attribute("aria-label"))

    time.sleep(3)   # throttle between pages, always
    browser.close()

About 15 lines, and it will genuinely print prices.
The cost: a full browser per request is 10 to 50x slower than requests, and it eats RAM accordingly.
At 5 hotels checked daily that's irrelevant.
At 500 it's an infrastructure project.

Step 4: Parsing and normalizing

This step decides whether your data can be trusted, and it's where most scrapers quietly fail.

Trap Example What goes wrong
Taxes in or out $189 shown, $214 charged Same hotel differs by region and OTA settings
Per night vs total stay "$378" for 2 nights Logged as a doubling that never happened
Currency symbols "$" US$, CA$, AU$, MX$ all render as "$"
Member and mobile rates $169 member price You log a price most buyers never see

The parsing itself is short:

import re

def parse_price(raw: str) -> float:
    # "$1,204" -> 1204.0
    return float(re.sub(r"[^\d.]", "", raw))

The judgment calls around it (which price, which basis, which currency) are the actual work.
Write down your normalization rules before you collect anything, or two scrapes of the same page will disagree and you won't know which one lied.

Step 5: Proxies and scale

Past toy volume, requests from one IP get blocked no matter how polite you are.
The standard fix is rotating residential proxies, billed per gigabyte of traffic, typically a few dollars per GB.
Headless browsers make that bill worse, because every page load pulls the full page weight through the proxy.
Budget for it as permanent infrastructure: proxies plus browser compute is the baseline cost of any scraper that runs on a schedule, before you count the engineering time.

The Wall: What Breaks and What It Costs

The Wall: What Breaks and What It Costs — StayAPI

Everything above works on the day you build it.
The question that decides build-vs-buy is what happens in the following 6 months.

What breaks How often What it looks like
Markup changes Weekly to monthly, per OTA Selectors return nothing; parsers throw
Anti-bot escalation Continuous, worst on Booking.com 403s, CAPTCHAs, bans spreading through your proxy pool
Consent and region walls Changes quietly by region The scraper sees a cookie banner, no prices
Silent data corruption The one you won't notice The run succeeds, the numbers are wrong

Silent corruption is the worst failure mode on the list.
A crashed scraper gets fixed; a scraper that logs member rates as public rates, or totals as nightly rates, feeds bad numbers into real decisions for weeks.
Now the math, stated as the estimate it is.
A 3-OTA hotel price scraper at portfolio scale realistically consumes 5 to 10 engineer hours a week once markup churn and proxy babysitting are counted.
That lands at $60,000 to $100,000+ a year in developer time and infrastructure, and our hotel data API guide breaks that figure down line by line.
The same trap at parity scale has its own writeup: monitoring your rates across OTAs is the job hotel rate parity monitoring exists for.
And if you'd rather buy an off-the-shelf scraper than build or subscribe, we compared the real hotel scraper tools honestly too.
The decision rule is simple.
If the prices feed a decision someone is paid to make (pricing, parity, portfolio monitoring), buy the pipe.
If it's an experiment or a one-off research pull, scraping is a fine teacher, and you just learned it.

The Graduation Path: Same Data, No Scraper

The Graduation Path: Same Data, No Scraper — StayAPI

Hotel data APIs sell the part of this article you'd rather not own: the extraction, the anti-bot arms race, and the normalization, maintained continuously by someone whose whole product depends on it.
StayAPI is one of them, with live price endpoints for Booking.com, Expedia, TripAdvisor, and Google Hotels, plus chain direct sites like Marriott and IHG.
The request replaces your entire stack from steps 2 through 5.
Here's the Booking.com prices endpoint:

curl -G "https://api.stayapi.com/v1/booking/hotel/prices" \
  -H "x-api-key: YOUR_API_KEY" \
  -d "hotel_id=346648" \
  -d "check_in=2026-09-18" \
  -d "check_out=2026-09-19" \
  -d "currency=USD"

The response, truncated to the fields the scraper was chasing:

{
  "success": true,
  "data": {
    "hotel": { "id": "346648", "name": "Le Grand Hotel Paris" },
    "rooms": [
      {
        "room_name": "Superior Double Room",
        "price_per_night_value": 189.0,
        "currency": "USD",
        "cancellation_policy": "Free cancellation"
      }
    ],
    "available_rooms_count": 4
  }
}

Normalization is already done: per-night basis, explicit currency, room-level granularity.
For Google Hotels, the equivalent endpoint returns each partner's offer as its own row, including the hotel's direct-site rate (the partner auction from step 1, pre-parsed).
The migration is mostly deletion.
A working Playwright scraper with proxy rotation and a parser runs 200 to 400 lines plus configuration; the replacement is one GET per property per day, about 10 lines with logging.

Criterion Your scraper A data API (StayAPI)
Markup churn You fix it, on the OTA's schedule Vendor's problem
Blocks and proxies Your bill, your on-call None; it's an API with no rate limits
Normalization Hand-written per OTA One schema: room, rate, currency, availability
Cost shape Proxies + compute + engineer hours Request-metered subscription
Price history Yours if you log it Yours if you log it (same job either way)

That last row is the honest one: an API returns current prices on request, so history is still something you build by logging.
For a truly one-off research pull, a scraper you already wrote may also genuinely be the cheaper move.
Getting started takes minutes:
1. Sign up at stayapi.com: 50 free requests, no credit card.
2. Preview the data without writing code using the free Google hotel info tool.
3. Point the curl above at a hotel you were about to scrape and diff the output against your parser's.
4. Schedule the daily pull in cron, or in N8N or Make.com if you'd rather keep it no-code.
For a team walkthrough, book a demo.

FAQ

What's the best Python library for scraping hotel prices?

requests when the price XHR is reachable directly, Playwright when prices render via JavaScript, and BeautifulSoup or lxml for parsing HTML.
Scrapy earns its complexity only at multi-site crawl scale.
For hotel prices specifically, the JavaScript rendering means Playwright ends up doing the heavy lifting more often than people expect.

Can you scrape Booking.com prices?

People try constantly, and Booking.com runs the strongest anti-bot stack among OTAs: CAPTCHAs, fingerprinting, aggressive IP bans.
Scrapers against it work in cycles (break, patch, break), so production reliability is poor.
For dependable Booking.com price data, a maintained data API is the realistic route.

How do I scrape Google Hotels prices?

Through the rendered page with a headless browser, or through the price XHR when it's stable; the tutorial above uses Google Hotels as its worked example.
Anchor selectors on aria-labels rather than obfuscated class names, and throttle between pages.
The same data is also available as JSON through hotel data APIs, partner-level prices included.

How often do hotel price scrapers break?

Expect markup-driven breakage weekly to monthly per OTA, with anti-bot escalation layered on top at unpredictable intervals.
The failures that hurt most are silent: the scraper runs green while logging wrong prices.
Any scraper feeding real decisions needs its own monitoring, which is more code to maintain.

How much does it cost to run a hotel price scraper?

Rough production math: residential proxy bandwidth billed per GB, compute for headless browsers, and the dominant line item, engineering time for breakage (5 to 10 hours a week at multi-OTA scale).
That's how a "free" scraper reaches $60,000+ a year.
For ongoing monitoring, maintenance alone usually exceeds a data API subscription within months.

Ship in 3 days

Ready to simplify your hotel data?

Join other developers using StayAPI to build the next generation of travel applications. Get started for free today.