Google Hotels Price Scraper – Vendor Rates, Price History avatar

Google Hotels Price Scraper – Vendor Rates, Price History

Pricing

from $1.40 / 1,000 hotels

Go to Apify Store
Google Hotels Price Scraper – Vendor Rates, Price History

Google Hotels Price Scraper – Vendor Rates, Price History

Hotel prices from Google Hotels: per-vendor rates (OTA price ladder), rate comparison and price history for tracked stays. API and MCP-ready. Data-quality figures: data.yappman.com/data-quality

Pricing

from $1.40 / 1,000 hotels

Rating

0.0

(0)

Developer

Russell Yapp

Russell Yapp

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

9 hours ago

Last modified

Categories

Share

Google Hotels Scraper: Prices, Vendor Rates

Get Google Hotels prices for a destination (a city, area or landmark) and dates as structured data: one row per hotel with the nightly price, stay total, rating, star class (hotels only) and coordinates, where Google shows them.

A real row from a Lisbon search (the image URL is left out):

{
"hotelId": "ChkI7drbt9vwh-JTGg0vZy8xMWI2cGMwcjE4EAE",
"name": "InterContinental Lisbon",
"propertyType": "hotel",
"hotelClass": 5,
"rating": 4.6,
"reviewCount": 2998,
"address": null,
"latitude": 38.726817,
"longitude": -9.1552236,
"checkIn": "2026-10-23",
"checkOut": "2026-10-25",
"nights": 2,
"adults": 2,
"rooms": 1,
"leadPrice": {"perNight": 204.06, "total": 408, "currency": "USD", "vendor": null, "isSponsored": null},
"vendorRates": null,
"amenities": [],
"amenityCodes": [165, 29, 15, 2, 18, 8, 4, 23, 24, 1, 31, 27, 7, 11, 25],
"url": "https://www.google.com/travel/hotels/entity/ChkI7drbt9vwh-JTGg0vZy8xMWI2cGMwcjE4EAE",
"filtersApplied": {"google": [], "actor": []},
"history": [],
"scrapedAt": "2026-10-09T06:30:32.350585+00:00"
}

A default run (the prefilled input: Lisbon, 2 adults, a two-night stay, 5 hotels) costs up to about $0.015 on the free tier: 5 hotel rows at $0.002 each, plus the $0.005 Actor start fee. It costs less if fewer hotels come back, and a blocked run costs only the start fee. Hotel rows cost less on higher Apify plans; the start fee is flat.

Run it with the default input.

16-second demo on YouTube: https://youtu.be/sURf-8czh8g

What it does

Get hotel prices from Google Hotels as structured data. Search a destination and a pair of dates and get one row per hotel: the lead nightly price Google shows, the total for the stay, star class, guest rating, review count, amenities and coordinates. In search mode you can add the per-vendor rate ladder (the rate each booking vendor and OTA shows, such as Booking.com, Expedia and Hotels.com) to each row, for rate comparison and price comparison; hotel mode returns no vendor rates in v0.1. You can also look up specific hotels by Google hotel id or Google Travel URL for one pair of dates, and read weekly lead-price history for the hotels in the cities the Actor tracks, for price tracking over time. It needs no browser and returns no personal data, and AI agents can call it through Apify's MCP server.

It is for travel-analytics and rate-intelligence teams, metasearch builders, researchers pricing destinations, and anyone who needs Google's hotel prices as structured data on a stable schema.

Every price belongs to the searched dates, and every row carries those dates.

Modes

ModeStatusWhat it does
searchAvailableHotels for each destination and dates in searches.
hotelAvailableThe hotels in hotels, for the one pair of dates in dates. Returns no vendor rates in v0.1.
historyAvailableWeekly lead-price snapshots for hotels the Actor tracks, for the dates they are tracked on. See History mode.
trackedAvailable, freeLists every tracked hotel id with its dates. See Tracked mode.

Input

The default input (one search for Lisbon, two adults, a two-night stay starting 14 days after the run, 5 hotels in the prefilled input and 20 if maxItems is left out, no vendor rates) runs without changes.

FieldTypeDefaultLimits
modesearch, hotel, history, trackedsearchOne mode per run. tracked takes no other input and is free.
searchesarray of objectsone item: Lisbon, 2 adults, 1 roomsearch mode. Up to 20 items; at least 1 in search mode.
searches[].destinationstringrequired1 to 200 characters; a city, area or landmark. No control characters.
searches[].checkIn, searches[].checkOutYYYY-MM-DDsee belowcheckOut must be after checkIn. If left out, the stay is two nights starting 14 days after the run.
searches[].adultsinteger21 to 12.
searches[].roomsinteger1Must be 1. Google ignores the rooms field, so other values are rejected rather than charged for a setting that changes nothing.
hotelsarray of strings[]hotel and history modes: Google hotel ids or the hotel's Google Travel page URL. Up to 1,000 entries in hotel mode (the maxItems cap) and 100 in history mode, each up to 500 characters. Required in both modes. A history run stops after 20 untracked hotels in a row; run tracked first to see what's covered.
datesobject { "checkIn", "checkOut" }nonehotel and history modes: required, YYYY-MM-DD, checkOut after checkIn. Both modes use 2 adults and 1 room; there is no adults input for them.
includeVendorRatesbooleanfalseAdds the per-vendor rate ladder to each row. search mode only for now: in hotel, history and tracked modes it is rejected with an input error, and hotel mode returns no vendor rates in v0.1. One extra request per hotel. Charged as its own event (see Pricing).
filtersobject{}See Filters.
sortByrelevance, price, rating, reviewsrelevanceOrder of results as Google sorts them.
currencystringUSDISO 4217 code, three capital letters.
languagestringenGoogle's hl, e.g. en or en-GB.
countrystringusTwo lowercase letters; Google's gl.
maxItemsinteger201 to 1,000. Hotels returned per run, across all searches.

One search returns at most about 540 hotels (30 pages), whatever maxItems is.

There is no proxy input. The Actor manages its own network egress.

Filters

minPrice, maxPrice, minRating and hotelClass are applied by the Actor after the results are fetched, and each row's filtersApplied.actor lists the ones applied.

FilterMeaningNotes
minPriceMinimum lead price per night, 0 or moreHotels with no lead price are excluded when a price filter is set.
maxPriceMaximum lead price per night, 0 or moreAs above.
minRatingMinimum guest rating, 0 to 5Hotels with no rating are excluded.
hotelClassArray of star classes, each 1 to 5Hotels with no star class are excluded.
freeCancellationRejected for nowGoogle's results carry no cancellation terms the Actor can check.
amenitiesRejected for nowMost hotels carry Google's amenity codes without labels (see amenityCodes), so there is nothing reliable to match against.

Because filters are applied after fetch, a run with a narrow filter can fetch many hotels to return few. If every hotel is filtered out, the run ends as blocked with the reason "no hotel matched the filters" and charges no hotel/vendor-rates/history event (only the platform's own start fee applies).

Very narrow filters can stop a search early (after 6 pages with no match, or once far more hotels were checked than returned); sorting by price or rating pages toward matches faster. The run summary's stoppedEarly says when this happened.

Output

One row per hotel per search (search), per looked-up hotel (hotel), or per tracked hotel (history).

Prices (leadPrice and vendorRates) are Google's pre-tax figures, nightly and for the stay, rounded half-up to the currency's minor unit (2 decimals for USD, 0 for JPY).

FieldTypeNotes
hotelIdstringGoogle's property id, stable across searches.
namestring
propertyTypestring or nullhotel when Google shows a star class; the vacation-rental label Google shows when present; otherwise null.
hotelClassinteger or nullStar class, 1 to 5.
ratingnumber or nullGoogle's aggregate guest rating, rounded to one decimal (half-up).
reviewCountinteger or nullNumber of reviews behind the rating.
addressstring or nullFilled in hotel mode. Null in search mode (a search result does not carry it), and normally null in history mode (stored rows come from search results).
latitude, longitudenumber, each nullable
checkIn, checkOutdateThe searched itinerary, on every row.
nights, adults, roomsintegerThe searched itinerary.
leadPriceobject or nullThe price Google shows first: perNight, total, currency, vendor, isSponsored. Null when Google shows no price. Search results do not attribute the lead price to a vendor, so vendor and isSponsored are filled only when includeVendorRates is on and a vendor's offer matches the lead price; otherwise null.
vendorRatesarray or nullNull unless includeVendorRates is on. One entry per vendor, that vendor's cheapest total across Google's room groups: vendor, perNight, total, currency, guests, isSponsored, bookingUrl, bookingUrlType, vendorDomain, offerCount.
amenitiesarray of stringsGoogle's own labels, only where Google serves them (vacation rentals carry inline labels). Empty otherwise.
amenityCodesarray of integersGoogle's numeric amenity codes as served, for hotel-type records whose labels are not in the response. Empty otherwise. No labels are guessed.
imageUrlstring or nullOne thumbnail.
urlstringThe hotel's Google Travel page.
filtersAppliedobjectgoogle and actor: which filters each side applied. Today every filter is applied by the Actor.
historyarrayEmpty in search and hotel modes. In history mode: { readAt, leadPrice, deltaPerNight } per weekly snapshot, oldest first. deltaPerNight is that snapshot's leadPrice.perNight minus the previous one's, null for the first snapshot or when either price is missing.
scrapedAtISO 8601 date-time

Within vendorRates:

  • offerCount is how many of Google's room-group offers were collapsed into that vendor's cheapest entry.
  • bookingUrl and bookingUrlType are always null. Google's click links carry session identifiers, which the Actor never passes on.
  • vendorDomain is the vendor site's registrable domain only (for example booking.com, not www.booking.com or secure.booking.com), or null when Google's link does not carry one. To book, use vendorDomain together with the row's url (Google's hotel page).

Example search row without vendor rates (values illustrative):

{
"hotelId": "ChIJ…",
"name": "Hotel Example Lisboa",
"propertyType": "hotel",
"hotelClass": 4,
"rating": 4.6,
"reviewCount": 2310,
"address": "Rua Exemplo 1, Lisbon",
"latitude": 38.71,
"longitude": -9.14,
"checkIn": "2026-11-06",
"checkOut": "2026-11-08",
"nights": 2,
"adults": 2,
"rooms": 1,
"leadPrice": { "perNight": 142, "total": 284, "currency": "EUR", "vendor": null, "isSponsored": null },
"vendorRates": null,
"amenities": [],
"amenityCodes": [6, 29, 2, 4, 23, 1, 11],
"imageUrl": "https://…",
"url": "https://www.google.com/travel/hotels/entity/ChIJ…",
"filtersApplied": { "google": [], "actor": ["minRating"] },
"history": [],
"scrapedAt": "2026-09-28T10:00:00Z"
}

With includeVendorRates on, leadPrice.vendor and leadPrice.isSponsored are filled when a vendor's offer matches the lead price, and vendorRates holds entries like this one (illustrative):

{
"vendor": "Booking.com",
"perNight": 142,
"total": 284,
"currency": "EUR",
"guests": 2,
"isSponsored": false,
"bookingUrl": null,
"bookingUrlType": null,
"vendorDomain": "booking.com",
"offerCount": 3
}

History mode

history mode returns weekly lead-price snapshots for hotels the Actor tracks, for the dates they are tracked on. It makes no Google request: it reads stored snapshots.

  • Input: mode: "history", hotels (ids or Google Travel URLs) and dates. Adults are fixed at 2.
  • Tracked hotels currently come from a seed set of ten cities: New York, Los Angeles, Chicago, Miami, Las Vegas, London, Edinburgh, Lisbon, Paris and Barcelona. Stays are two nights, 2 adults, starting 2 and 6 weeks out, and are re-read weekly.
  • Rows are returned as tracked: one fixed series (USD, US locale). The currency, language and country inputs do not change them.
  • includeVendorRates is rejected in this mode, and history rows carry no vendor rates.
  • One row per tracked hotel, with history listing its snapshots oldest first.
  • A hotel and dates that are not tracked return no row and are not charged. If none of the requested hotels is tracked, the run fails with "none of the requested hotels are tracked for these dates".
  • A row with two or more snapshots is charged the history event; a row with one snapshot is free.

No coverage beyond the seed set is promised. Run tracked first to see which hotels and dates history covers.

Tracked mode

tracked mode is free and makes no Google request. It lists every hotel currently tracked, and it writes the list to the run's key-value store record TRACKED_HOTELS, not to the dataset.

  • Input: mode: "tracked". Nothing else is needed; includeVendorRates is rejected.
  • The record holds generatedAt, count and items. Each item has hotelId, checkIn, checkOut, adults and destination (the seed city), sorted by destination, then check-in, then hotel id.
  • Use an item's hotelId as hotels and its checkIn / checkOut as dates in a history run. history uses 2 adults.
  • This code charges no hotel/vendor-rates/history event for a tracked run - only the platform's own apify-actor-start launch fee applies, the same as any run.

Standby

On Apify Standby this Actor also answers two HTTP endpoints directly, instead of running as a single batch job:

  • GET /search?destination=&checkIn=&checkOut=&max= - the same validation as search mode's input (destination up to 200 characters, rooms must be 1 if given at all). Optional: adults, sortBy, currency, language, country, includeVendorRates, and the filters as query parameters (minPrice, maxPrice, minRating, hotelClass, repeated or comma-separated). max is 1 to 100 (narrower than batch's maxItems, since one call answers in a single response) and defaults to 20; capped at 20 when includeVendorRates is on (each vendor-rates lookup takes a few seconds and all of them must finish inside the request timeout).
  • GET /hotel?id=&checkIn=&checkOut= - the same validation as hotel mode's input. Optional: currency, language, country. includeVendorRates is rejected here, the same as in batch hotel mode.

Both use the same validation, normaliser, dedupe and filters as a batch run of this Actor, and return { "items": [...], "count", "blocked": null | "<reason>", "vendorRatesCharged": n, "chargeLimitReached": true | false } - items is exactly the Output row schema above; vendorRatesCharged is how many of the returned rows actually got the extra vendor-rates charge (normally equal to how many carry vendorRates, but can be lower if that one extra charge failed after the hotel charge already succeeded - the rows are still returned either way); chargeLimitReached is true when the run's own charge limit either trimmed max before fetching anything or was actually hit while charging, false otherwise. Nothing is pushed to this Actor's dataset from Standby; the response is the only place the rows go.

Only the parameters listed above are accepted per endpoint: an unknown query parameter, a single-value parameter repeated, or a value that does not parse in its documented format (a plain base-10 integer, a plain decimal, or true/false/1/0 for a boolean) is always rejected with 400, naming the parameter - never silently dropped or defaulted.

Each row returned is charged the hotel event (and vendor-rates too, for a row that ends up carrying vendor rates when includeVendorRates is on) before it is returned, so you are only ever billed for rows you actually got back. A request is charged before its response is written, so a dropped connection after that point is still charged. The platform's own apify-actor-start fee applies once per Standby run (the long-lived server process), not per request. In Standby mode you also pay the Apify platform usage of the run that serves your request - its compute units and any residential proxy traffic it uses - on top of these per-row events (Apify support, relayed 9 Oct 2026); a batch run instead absorbs that platform usage into the row price, as the Pricing section below describes. The platform sets each Standby run's own charge limit, and it can vary by caller - you cannot set or raise it yourself. This Actor reads the limit's remaining headroom from the run itself before fetching anything and never assumes a figure: a request can be capped below max, or refused outright, by that headroom, and chargeLimitReached says when it was. A request that found candidate rows but whose charge covered none of them gets 402 ("the run's charge limit allows no further rows"); a request that found nothing at all (or was blocked) gets 200 with items: [] and a blocked reason, and charges no hotel/vendor-rates event either way.

history and tracked mode are not available on Standby.

This server handles one request at a time: a second request made while one is already in progress gets 503 immediately (not queued), so you should not send concurrent requests to your own running instance. That run (including its platform usage above) stops after 60 idle seconds, so an idle endpoint costs you little. The first response from a run must arrive within 5 minutes including cold start (an Apify platform limit); this Actor's own request timeout shrinks accordingly for a run's very first request only, so that limit is always met.

See .actor/web_server_schema.json for the full OpenAPI description, including every parameter and error code.

Pricing

Pay per event. Prices below are the free-tier prices; paid Apify tiers are discounted: Bronze, Silver and Gold at 10%, 20% and 30% off the free-tier price; Platinum and Diamond at the Gold price.

Standby pays platform usage too. In Standby mode (see above) you also pay the Apify platform usage of the run that serves your request - its compute units and any residential proxy traffic it uses - on top of the events below (Apify support, relayed 9 Oct 2026). A batch run instead absorbs that platform usage into the row price; there is nothing extra to pay beyond the table below for a batch run.

EventCharged whenPrice
Actor start (apify-actor-start)Charged by Apify itself, at launch - not by this Actor's own code.$0.005 per GB of memory (1,024 MB default = one event)
hotelEach hotel row pushed to the dataset.$0.002 ($2.00 per 1,000)
vendor-ratesEach hotel row that carries vendor rates, and only when includeVendorRates is on. Never charged otherwise.$0.0025 ($2.50 per 1,000)
historyEach history row with two or more snapshots. A one-snapshot row is free.$0.01

tracked mode charges no event of this Actor's own. history mode does not charge hotel. Blocked or empty runs charge no hotel/vendor-rates/history event: no row stored means no row charge - only the platform's own start fee applies, the same as any run. Filtered-out hotels are not pushed and not charged.

What to expect

  • Blocked runs fail visibly. If Google refuses the requests or the responses no longer match the expected layout, the run ends with an error status ("Blocked:
  • Currency is as requested. In search and hotel modes prices are in the currency you set in currency, as Google returns them. The Actor never converts.
  • No booking links. bookingUrl is always null because Google's click links carry session identifiers, which the Actor never passes on and never follows. Use vendorDomain and the row's url.
  • Network egress is managed by the Actor. There is no proxy setting to configure.

FAQ

How do I get prices in another currency, language or country? Set currency (e.g. GBP), language (e.g. en-GB) and country (uk for the United Kingdom: it returned a normal Google results page when tested; gb has not been tested). They map to Google's curr, hl and gl, sent unchanged. Currency is as Google returns it; nothing is converted.

Why does a price differ from what I see on Google? Every price is specific to the itinerary searched (dates, adults) and to the request as an anonymous visitor. Prices you see in your own browser can differ if you are logged in, have a personalised profile or different location, or searched a different number of guests. Google also changes prices frequently, and rooms is not honoured by Google, so it does not affect results.

What are the limits? Up to 20 searches per run, up to 1,000 hotels per run (maxItems), and about 540 hotels per search. Requests are paced at one per two seconds, so large runs take time. As a guide, a 20-hotel search takes about 10 seconds, and 5 hotels with vendor rates take 20–30 seconds.

Can I filter by free cancellation or amenities? Not yet; see Filters.

Why did my search return fewer rows than maxItems? Very narrow filters can stop a search early (after 6 pages with no match, or once far more hotels were checked than returned); sorting by price or rating pages toward matches faster. The run summary's stoppedEarly says when this happened.

Can I read rate history? Yes, for tracked hotels only; see History mode.

Are prices before or after tax? Pre-tax, as Google's nightly figure is.

Read more

This Actor is not affiliated with, endorsed by or sponsored by Google. Google and Google Hotels are trademarks of Google LLC and are named here only to say where the data comes from. Google's terms restrict automated access to its services; you are responsible for how you use the data and for complying with applicable law and terms. This is not legal advice.

The Actor collects no personal data: no reviewer names, no review text, no user data. It returns what Google shows any visitor: hotel details, prices and vendor names. For history mode it keeps a list of tracked hotel ids and stay dates, and weekly price snapshots for them; it records nothing about who runs the Actor or what they search for.