kestrel actors › Hotel & flight prices
Google Hotels Prices Scraper — Price Tracking & Rate Parity
Google Hotels prices scraper for exact dates and guests: the lowest nightly rate and the stay total for every hotel in a search, then every booking site's rate for each hotel — Booking.com, Expedia, Agoda, Hotels.com, Trip.com and the hotel's official site — with free-cancellation flags and deep links. Run it once for a price check, on a schedule for hotel price tracking and rate parity, or with a date sweep to build a price calendar and your own hotel price history. No Google API key, no login, no browser: it reads the same public Google Hotels results any visitor sees, through Apify's proxies, and returns clean JSON.
Run Google Hotels Prices Scraper on Apify →
Google hotels prices scraper pricing: what one row costs
| Event | What it is | Price | Charged for |
|---|---|---|---|
hotel | Hotel price row | $0.004 | One hotel with its lowest nightly rate and stay total for your dates. Unpriced (sold-out) hotels are free. |
offer | Source rate row | $0.002 | One rate from one source (OTA or official site) for one room and stay, with free-cancellation flag and deep link. |
Pay per event, only for priced rows: a hotel row (a hotel with a price for your dates) and an offer row (one source's rate). Sold-out hotels, empty searches and status rows are free, and a hotel and stay is billed once per run even when overlapping searches return it twice. A city search of 60 hotels with source-level offers is a few hundred rows; a daily parity check of ten properties with seven check-in days is about 70 hotel rows plus their offers. Apify's platform usage for a run of that size is a fraction of a cent — the runs above cost $0.001–$0.003 in compute. The exact per-row prices are on this page's pricing tab, and maxOffersPerHotel caps the offer rows when you only need the cheapest few sources.
Google hotels prices scraper input reference
Every field of Google Hotels Prices — input, straight from the actor's input schema.
| Field | Type | Default | What it does |
|---|---|---|---|
queries | array | — | Free-text place searches, exactly as you would type them into Google Hotels: "hotels in Lisbon", "hotels near Times Square", "5-star hotels Bangkok". Google's results are paged 20 at a time; maxHotels sets how many distinct hotels to collect per place. Google always answers a place search: an unrecognised or misspelt place is silently matched to somewhere else rather than failing, and those hotels are delivered and billed. Pass an exact place name or a Google Hotels entity URL, and check the status row's spread_km (and each hotel's lat/lng) before trusting the results. |
hotels | array | [] | Track exact properties without a search: Google Hotels URLs (https://www.google.com/travel/hotels/entity/ChkI…) or bare entity tokens, e.g. from an earlier run's google_url/entity_id. One offers call per hotel and stay; the hotel row comes from that page. |
checkIn | string | required | Check-in date: absolute YYYY-MM-DD or relative to the run date (e.g. 2026-10-03, or relative for schedules: "40 days", "6 weeks", "+40d"). Relative dates keep a scheduled run valid forever. |
checkOut | string | required | Check-out date: absolute YYYY-MM-DD or relative to the run date (e.g. 2026-10-06 or "42 days" — must be after check-in; nights = the difference). Relative dates keep a scheduled run valid forever. |
sweepDays | integer | 0 | 0 = only the stay above. N = also fetch the same length of stay starting on each of the next N days (a 3-night stay with sweepDays 6 = 7 stays, one per check-in day). Each stay is a separate list call. Range 0-60. |
adults | integer | 2 | Adults per room (prices scale with occupancy exactly as on Google). Range 1-6. |
childrenAges | array | [] | Ages of children sharing the room, e.g. [4, 9]. Leave empty for none. |
currency | string | "USD" | 3-letter currency code for every price (USD, EUR, GBP, JPY, AED, …). All rows are in this one currency. |
language | string | "en" | Google UI language (hl): affects labels such as room names and deal notes. |
country | string | "us" | Google market (gl): which country's Google Hotels answers. Prices and available sources differ by market. |
maxHotels | integer | 20 | Distinct hotels to collect per place and stay. Google returns 20 per page; the actor pages on (one list call per 20) until this many or the results end. Range 1-200. |
includeOffers | boolean | true | For each priced hotel also fetch its offers page (one extra call per hotel): the rates from every source — Booking.com, Expedia, Agoda, the official site… — with free-cancellation flag, stay total and deep link. |
offerLevel | string (enum) | "sources" | sources = one row per source with its lowest rate (rate parity; ~5–15 rows per hotel). rooms = every source × room × rate, with room names and cancellation deadlines (~50–100 rows per hotel). One of: sources, rooms. |
maxOffersPerHotel | integer | 0 | 0 = every source. N = keep only the N cheapest rows per hotel (cost control; the official site row is kept only if it is among them). Range 0-500. |
proxyConfiguration | object | {"useApifyProxy": true} | Google accepts Apify datacenter proxies for these calls; keep the default. Residential is unnecessary. |
sessions | integer | 4 | Concurrent egress IPs; each is paced at perIp requests per second. Range 1-16. |
perIp | number | 0.5 | Pace per proxy session. 0.5 is safe; raise carefully. Range 0.1-2. |
Example input
{ "queries": ["hotels in Lisbon"], "checkIn": "2026-10-03", "checkOut": "2026-10-06", "adults": 2, "currency": "USD", "maxHotels": 60 }
{ "hotels": ["https://www.google.com/travel/hotels/entity/ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE"], "checkIn": "30 days", "checkOut": "32 days", "sweepDays": 6, "currency": "EUR", "offerLevel": "sources" }
Google hotels prices scraper output fields
Three row types share one dataset: hotel (lowest nightly rate and stay total per hotel), offer (one booking source's rate for a hotel and stay), status (per place or property and stay; never charged).
Dataset views in the Apify Console: Overview, Hotels, Offers by source, Run status.
| Field | Type | What it holds |
|---|---|---|
type | string | Row type: hotel, offer or status. |
query | string | The place search this row came from (null in property mode). |
hotel | string | Status rows in property mode: the hotel URL or entity token that was priced. |
check_in | string | Check-in date, YYYY-MM-DD. |
check_out | string | Check-out date, YYYY-MM-DD. |
nights | integer | Nights in the stay. |
adults | integer | Adults per room used for pricing. |
children | integer | Children per room used for pricing. |
currency | string | Currency of every price in the row (ISO code). |
rank | integer | Position in Google's results for the place search (null in property mode). |
name | string | Hotel name. |
hotel_id | string | Google's numeric hotel id. |
entity_id | string | Google Hotels entity token; reuse it in the hotels input to price this property directly. |
cid | string | Google Maps place id (CID). |
stars | integer | Hotel class, 1–5. |
class_label | string | Google's class label, e.g. 4-star hotel. |
rating | number | Google rating, 1–5. |
reviews | integer | Number of Google reviews. |
lat | number | |
lng | number | |
description | string | Google's one-line description of the hotel. |
website | string | Hotel website. |
types | array | Google place types, e.g. hostel, boutique_hotel. |
checkin_time | string | |
checkout_time | string | |
thumbnail | string | Thumbnail image URL. |
nightly | number | Lowest nightly rate (hotel rows) or this source's nightly rate (offer rows). Null = no price for the stay (sold out). |
nightly_display | string | Nightly rate as Google displays it, e.g. $82. |
total | number | Total for the whole stay. |
total_display | string | Stay total as Google displays it. |
deal | string | Google's deal note, e.g. 19% less than usual. |
source | string | Booking source: an OTA name, or the hotel's own name when official is true. On hotel rows: the source of the lowest rate when Google names it. |
source_id | integer | Google's id for the source — the key of a source (a name can appear twice: official listing and marketplace rate). |
official | boolean | True when the source is the hotel's official site. |
room | string | Room name (offerLevel rooms only). |
max_guests | integer | Guests the rate covers (offerLevel rooms only). |
free_cancel | boolean | Free cancellation available. |
free_cancel_until | string | Free-cancellation deadline as Google shows it, e.g. Oct 1. |
link | string | Google's click-through link for the source. |
partner_url | string | The source's own URL with the dates filled in (official sites: the booking engine). |
lowest | boolean | True for a source's lowest rate row. |
maps_url | string | Google Maps link for the hotel. |
google_url | string | Google Hotels page of the hotel. |
total_results | integer | Google's result count for the place search (capped at 15000). |
hotels | integer | Status rows: hotel rows produced. |
priced | integer | Status rows: hotels with a price (the only ones charged). |
offers | integer | Status rows: offer rows produced. |
duplicates | integer | Status rows: hotels skipped because another search already delivered them for this stay. |
offer_errors | integer | Status rows: hotels whose offers page failed. |
error | string | Status rows: error message, null when the job succeeded. |
fetched_at | string | UTC timestamp of the fetch. |
spread_km | number | Status rows, place mode only: median distance in km of the returned hotels from their centre. A city is single digits, a region tens. A large value means Google may not have resolved your place and answered with scattered unrelated hotels — those rows are still billed, so check this before trusting a run. |
Example row from a real run
Taken from the actor's README — this is the shape your dataset holds.
{ "type": "hotel", "query": "hotels in Lisbon", "check_in": "2026-10-03", "check_out": "2026-10-06", "nights": 3, "adults": 2, "children": 0, "currency": "USD",
"rank": 1, "name": "The Central House Lisbon Baixa", "hotel_id": "10088532857338925820", "entity_id": "ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE",
"stars": 2, "class_label": "2-star hotel", "rating": 4.3, "reviews": 727, "lat": 38.7131, "lng": -9.1368, "types": ["hostel"],
"nightly": 81.81, "nightly_display": "$82", "total": 245, "total_display": "$245", "deal": "19% less than usual", "source": null,
"website": "https://www.thecentralhousegroup.com/", "maps_url": "https://maps.google.com/?cid=3848859359889388291", "thumbnail": "https://lh3.googleusercontent.com/gps-cs-s/…",
"google_url": "https://www.google.com/travel/hotels/entity/ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE", "total_results": 15000, "fetched_at": "2026-08-25T14:03:11+00:00" }
{ "type": "offer", "name": "Hyatt Regency Lisbon", "entity_id": "ChoI4cjBy-KGurGyARoNL2cvMTFtdnFfdncydhAB", "check_in": "2026-10-03", "check_out": "2026-10-06", "nights": 3, "adults": 2, "children": 0, "currency": "USD",
"source": "Booking.com", "source_id": 184, "official": false, "room": null, "nightly": 569.35, "total": 1708.05, "max_guests": null,
"free_cancel": null, "free_cancel_until": null, "link": "https://www.google.com/travel/lodging/clk?pc=…", "partner_url": "https://www.booking.com/hotel/pt/hyatt-regency-lisbon.html?checkin=2026-10-03&checkout=2026-10-06…", "lowest": true, "fetched_at": "2026-08-25T14:03:14+00:00" }
Google hotels prices scraper in Python, JavaScript and curl
Python
from apify_client import ApifyClient
client = ApifyClient("<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"])
JavaScript (Node)
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<TOKEN>' });
const run = await client.actor('kestrel/google-hotels-prices').call({ hotels: ['https://www.google.com/travel/hotels/entity/ChkIhLCQwvjO2IYWGg0vZy8xMXE0bTZieDkyEAE'], checkIn: '30 days', checkOut: '32 days', currency: 'EUR' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const official = Math.min(...items.filter(x => x.type === 'offer' && x.official).map(x => x.nightly));
const parityBreaches = items.filter(o => o.type === 'offer' && !o.official && o.nightly < official);
console.log(parityBreaches.map(o => [o.name, o.source, o.nightly]));
curl
curl -X POST "https://api.apify.com/v2/acts/kestrel~google-hotels-prices/run-sync-get-dataset-items?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"queries": ["hotels in Lisbon"], "checkIn": "2026-10-03", "checkOut": "2026-10-06", "maxHotels": 20, "includeOffers": false}'
The same three calls are runnable files in the examples repository: examples/google-hotels-prices.py|.js|.sh.
Limits and honest notes
- A place search pages through Google's ranking 20 at a time up to
maxHotels(200); neighbourhood or landmark queries add coverage beyond that. - Prices are what Google Hotels shows for the market (
country) and currency you choose; whether taxes and fees are included follows Google's display rules for that market. Thetotalis the stay total Google shows. - Sources per hotel vary by market and day (typically 5–25 with US settings). Sold-out hotels appear as
hotelrows without a price and are not charged. - Google changes its internal formats occasionally; a daily canary run watches for that, the actor is rebuilt within days, and every run reports
statusrows so a failure is visible rather than a silent zero. - Google never fails a place search. An unrecognised or misspelt place is silently matched to somewhere else and answered with real hotels, which are delivered and billed like any other row. There is no signal in Google's response that says "I did not find that place", and a fuzzy-matched nonsense string can return a cluster as tight as a genuine region, so this cannot be detected reliably enough to refuse the run without also breaking legitimate broad searches like
"Portugal"or"California". Each placestatusrow therefore carriesspread_km— the median distance of the returned hotels from their centre. A city is single digits, a region tens, a country a few hundred; anything in the thousands almost certainly did not resolve. Above 500km the run logs a warning. Pass an exact place name or a Google Hotels entity URL when it matters, and checkspread_kmand each hotel'slat/lngbefore trusting a run.
FAQ
Does it need a Google Hotels API key or a Google account?
No. There is no public Google Hotels API for reading prices; the scraper uses the public results page through Apify's proxies. No login, cookies or API key.
Can I get hotel price history and price alerts from Google Hotels?
Google's own tracker keeps history and alerts inside Google. Schedule this scraper (daily, with relative dates) and every run appends dated rows to your dataset — that is your price history — and an Apify integration (n8n, Make, Zapier, webhook) turns a drop into an alert.
How is this different from Google's "Track hotel prices" feature?
Google's feature emails you about the listings it chooses to watch and does not let you pick sources or export data. This scraper returns every source's rate for the exact dates, occupancy and currency you set, as rows you own.
Which booking sites are covered?
Whatever Google Hotels lists for that property and market: Booking.com, Expedia, Hotels.com, Agoda, Trip.com, Priceline, Kayak, eDreams, Vio.com, the official site and dozens of regional OTAs. The source name and source_id come straight from Google.
Does it include taxes and fees?
It returns the nightly price and the stay total Google Hotels displays for your market. In some markets Google shows all-in prices, in others taxes are added later; set country to the market you sell in and compare like with like.
How many hotels can one run return?
Up to 200 per place search, or as many properties as you list in hotels, times the number of stays (sweepDays). A run stops cleanly when it reaches the spending limit you set on the run.
Why do prices differ from what I see in my browser?
Google prices depend on market, currency, occupancy, dates and sometimes the device. Match country, currency, adults, childrenAges and the dates, and compare the total for the stay rather than a headline nightly figure.
Can I use it as a hotel price scraper for Booking.com or Expedia specifically?
Yes for prices: each offer row names its source, so filtering source == "Booking.com" gives you Booking.com's rate per hotel and stay, and likewise for Expedia, Agoda or Hotels.com — without scraping each site separately. For reviews or room descriptions use a Booking.com scraper or Expedia scraper built for that site.
Is there a free tier?
Yes: Apify's free plan covers small runs, and this scraper never charges for a run that returns no priced rows.
Last verified working: 2026-08-29.
How to use Google Hotels Prices Scraper
Ready-made runs you can open and start
Each one is a saved input on the Apify Store — open it, press Start, and it runs with the fields already filled in. All 163 across the suite.
| Ready-made run | What it does |
|---|---|
| Hotel Price History by Date (Google Hotels) | Price one Barcelona hotel set across 14 consecutive check-in dates in a single run and build real hotel price history from Google Hotels. |
| OTA Rate Comparison: Booking vs Expedia | Compare Booking.com, Expedia, Agoda and direct rates for the same New York hotels and night as flat rows from Google Hotels. |
| Hotel Price Data by Room Type and Source | Get every source, room and rate with room names and cancellation deadlines from Google Hotels: room-level hotel price data for five hotels. |
| Scrape Google Hotels Prices for a City | One search, 100 priced Rome hotels with star rating, guest score, location and the total for a three-night stay. A whole-city price snapshot. |
| Hotel Price Monitoring for a Comp Set | Track named properties, not a search: two Google Hotels entity URLs priced for seven consecutive check-in dates, six cents a run on a daily schedule. |
| Google Hotels API Alternative, No SERP Key | A ten-hotel Amsterdam run with every source rate: what a Google Hotels API would return, without a SERP key or a per-search fee. |
| Scrape Hotel Prices for a Family Room | Two adults plus children aged 4 and 9 on a four-night Orlando stay: Google prices by occupancy, so a family room quote needs the ages set. |
| Hotel Price Tracking: Weekend vs Weekday | Seven consecutive one-night check-ins across 25 Chicago hotels isolate the weekend premium: same hotels, same stay length, only the weekday changes. |
| Hotel Rates API for the German Market | Munich hotels priced on the German Google market in EUR with German labels: the sources and rates a booker in Germany actually sees. |
| Hotel Price Intelligence: 5-Star Dubai | Search Google Hotels the way a guest would - "5-star hotels in Dubai" - and get 30 priced luxury properties in AED for a three-night stay. |
| Scrape Hotel Prices Near a Landmark | "Hotels near Times Square" returns the walkable set, not all of New York: 40 priced properties with coordinates for a one-night stay. |
| Hotel Price Scraper for 7-Night Stays | Long-stay pricing from Google Hotels: 30 Miami Beach hotels quoted for a full week, where weekly rates and minimum-stay rules change the answer. |
| Hotel Price Comparison API for Group Rates | Six adults in one room across 20 Madrid hotels, with every source rate: which OTA handles high occupancy and which quietly drops the property. |
| Web Scraping Hotel Prices in Tokyo (JPY) | Tokyo hotels priced in JPY on the Japanese Google market with Japanese labels: 40 properties, three nights, one currency across every row. |
Related scrapers
Run Google Hotels Prices Scraper
The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/google-hotels-prices