How to scrape Google Hotels prices
Google Hotels is the only place where every OTA prices the same property side by side, which makes it the cheapest rate-shopping surface there is. It also has a failure mode worth understanding before you schedule anything: it never tells you it did not understand your search.
The short version
- Give a place or an entity. A destination string ("hotels in Lisbon") pages Google's ranking 20 at a time; a Google Hotels entity id or URL prices one exact property with no resolution step.
- Set the stay, the market and the currency. Check-in and check-out, adults, children, currency and country. The market decides which sources appear and whether taxes are shown, so keep it fixed across a comparison.
- Ask for offers when you want the OTAs. The hotel row carries Google's headline price. Offer rows carry every source that priced the property, with its own nightly and total price and a deep link.
- Check spread_km before trusting a place run. The status row reports the median distance of returned hotels from their centre. Single digits is a city, tens a region, hundreds a country; thousands means Google matched something else.
Why Google Hotels rather than an OTA
Scrape Booking and you learn what Booking charges. Scrape Google Hotels and you learn what every booking site charges for the same room on the same night, including the hotel's own site, in one request. For rate shopping, parity checks and market pricing, that is a fundamentally better surface — and it is why one hotel row plus its offer rows costs less than pricing the same property on four OTAs separately.
The trade is that you get Google's view: the sources Google lists, the lowest offer each source shows for your occupancy, and Google's own display rules for taxes in that market. Room-level parity — the same room type on every channel — is a different question and needs room-level offers.
Working code
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/google-hotels-prices").call(run_input={
"queries": ["hotels in Lisbon"],
"checkIn": "2026-10-03", "checkOut": "2026-10-06",
"adults": 2, "currency": "USD", "maxHotels": 40,
})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
hotels = [r for r in rows if r["type"] == "hotel"]
offers = [r for r in rows if r["type"] == "offer"]
cheapest = min((h for h in hotels if h["nightly"]), key=lambda h: h["nightly"])
print(cheapest["name"], cheapest["nightly_display"], cheapest["total_display"])
for o in [o for o in offers if o["name"] == cheapest["name"]]:
print(" ", o["source"], o["nightly"], "official" if o["official"] else "")
For a scheduled run, use a relative check-in date so the job keeps meaning the same thing every morning:
{"hotels": ["ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE"],
"checkIn": "30 days", "nights": 2, "currency": "EUR", "country": "pt", "offerLevel": "sources"}
What the rows look like
The hotel row is Google's ranking position, the property and its headline price:
{ "type": "hotel", "query": "hotels in Lisbon", "check_in": "2026-10-03", "check_out": "2026-10-06",
"nights": 3, "adults": 2, "currency": "USD", "rank": 1,
"name": "The Central House Lisbon Baixa", "entity_id": "ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE",
"stars": 2, "rating": 4.3, "reviews": 727, "lat": 38.7131, "lng": -9.1368,
"nightly": 81.81, "total": 245, "deal": "19% less than usual",
"website": "https://www.thecentralhousegroup.com/", "total_results": 15000 }
The offer rows are the interesting ones — one per source that priced the property:
{ "type": "offer", "name": "Hyatt Regency Lisbon", "source": "Booking.com", "official": false,
"nightly": 569.35, "total": 1708.05, "lowest": true,
"partner_url": "https://www.booking.com/hotel/pt/hyatt-regency-lisbon.html?checkin=2026-10-03&checkout=2026-10-06" }
official marks the hotel's own booking engine, which is what makes a parity check a one-line comparison. partner_url is the deep link with your dates already in it, so a row is verifiable by hand in one click.
The failure mode nobody warns you about
Google never fails a place search.
Feed it a misspelt city, a nonsense string, an internal project codename — it will silently match something and answer with real hotels, real prices and real coordinates. There is no error, no empty result and no confidence field. A per-row scraper will deliver those rows and bill you for them, and the data will look completely normal in your warehouse until someone notices the "Lisbon" prices are for a town in Ohio.
It cannot be reliably detected, either. The obvious guard — measure how spread out the returned hotels are and refuse a run when the cluster is too wide — was tested and does not work: a garbage string can return a tighter cluster than a legitimate broad search like "Portugal" or "California", so any threshold that catches the nonsense also breaks the legitimate wide searches.
What can be done honestly is report the number. Each place status row carries spread_km, the median distance of returned hotels from their centre: single digits for a city, tens for a region, a few hundred for a country. Anything in the thousands almost certainly did not resolve, and above 500 km a warning is logged.
The practical rule: for anything scheduled, use entity ids, not place strings. Resolve the property once by name, check the coordinates by hand, then run on the id forever. Ids do not drift and cannot be fuzzy-matched.
Cost shape
A hotel row is $0.004 and an offer row $0.002, and sold-out hotels — those with no bookable rate — are delivered free. So pricing a 40-hotel comp set with every source costs roughly $0.16 plus the offers you asked for, and a market scan that finds half the properties sold out pays for half of them.
The related actors trade differently: Hotel Price Tracker bills one rate row per property per date (a date window in one run), and Hotel Rate Parity bills one row per property per date with every source pre-compared inside it — cheaper than reading the offers yourself when the comparison is all you want.
The honest limitations
- Place searches page 20 at a time up to a cap. For full city coverage, run neighbourhood or landmark queries as well.
- Sources per hotel vary by market and by day. Typically 5-25 with US settings; a property may show one source on a quiet date.
- Taxes follow Google's rules for the market, so cross-market comparisons are not like for like.
- Google changes its internal formats occasionally. A daily canary run is the only honest answer to that, together with status rows so a break is visible rather than a silent zero.
Run it
Google Hotels Prices Scraper — $0.004 per hotel row, $0.002 per offer row, sold-out properties and status rows free. Next: how to check hotel rate parity across OTAs if the comparison is what you are after, and how to track competitor prices daily with n8n for the scheduled version.
FAQ
Is there a Google Hotels API?
Not a public one for prices. Google's Hotel Center and its partner APIs are for hotels and OTAs with a commercial relationship. Everything here is read from the same public results a visitor sees, with no key on Google's side.
Why did my misspelt destination still return hotels?
Because Google never fails a place search. An unrecognised string is silently matched to somewhere else and answered with real hotels, which are then real rows that a per-row scraper will bill you for. There is no field in the response that says "I did not find that". A nonsense string can even return a tighter geographic cluster than a legitimate broad search like "Portugal", so no spread threshold can separate them reliably — which is why the honest design reports the spread rather than pretending to detect the failure.
How many sources price one hotel?
Typically 5 to 25 with US market settings, varying by market and by day. A property with a rate from only one source still gets a row; a property with none is a free status row rather than a silent gap.
Do the prices include taxes?
It follows Google's display rules for the market you chose. Within one market the numbers are comparable; across markets they are not, which matters if you are comparing a US and an EU view of the same hotel.
Run Google Hotels Prices Scraper on Apify →
Other actors used on this page: apify.com/kestrel/hotel-rate-parity, apify.com/kestrel/hotel-price-tracker.