# Google Hotels Scraper - Rate Calendar & Booking-Site Prices (`datalume/google-hotels-scraper`) Actor

Google Hotels scraper for hotel rate shopping: competitor prices for up to 60 check-in dates in one run (rate calendar), plus every booking site's price (Booking.com, Expedia, Agoda, official site) for rate parity & revenue management. Free cancellation, any currency, no proxies.

- **URL**: https://apify.com/datalume/google-hotels-scraper.md
- **Developed by:** [Greg](https://apify.com/datalume) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 hotel results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Hotels Scraper — Rate Calendar & Booking-Site Prices

Scrape **Google Hotels** for any city, neighborhood or landmark and get a **rate calendar**: every hotel's nightly price for a whole range of check-in dates in one run. Optionally add **prices from every booking site** Google shows (Booking.com, Expedia, Hotels.com, Agoda, Trip.com, the hotel's official site and dozens more) with free-cancellation and breakfast details.

No proxies, no browser, no login — fast and cheap.

**Built for hotel rate shopping:** competitor rate monitoring, rate parity checks across OTAs, and revenue management (RMS) inputs — a lightweight, pay-per-result alternative to hotel rate shopping tools.

### Rate shopping mode: your competitive set, every date, no gaps

Google ranks hotels differently for every check-in date, so a city search returns a different set of hotels each day — a competitor can be #2 on Monday and missing on Tuesday. For revenue management you need **the same hotels on every date**.

Enter your competitive set in **`hotels`** (hotel names, up to 50). The Actor locks each hotel to its Google Hotels ID on the first date and then finds **that exact hotel** on every check-in date:

```json
{
  "hotels": ["Keio Plaza Hotel Tokyo", "Hotel Gracery Shinjuku", "Shinjuku Granbell Hotel", "Mitsui Garden Hotel Jingugaien Tokyo PREMIER"],
  "location": "Tokyo",
  "dateRangeDays": 30,
  "includeOffers": true
}
```

You get one row per **hotel × date** — a complete rate calendar. If a hotel is sold out or not shown on a date, the row is kept with `priceStatus: "notListed"` (and **not charged**), so you can see the gap instead of silently missing data.

**Don't know your competitors yet?** Search your own hotel's name as `location` (e.g. `Park Hyatt Tokyo`) — the first result is your hotel and the next ones are its nearest competitors. Then copy the names into `hotels`.

### What makes it different

| | This Actor | Typical Google Hotels scrapers |
|---|---|---|
| City / area search | ✅ | ✅ |
| **Range of check-in dates in one run** (rate calendar) | ✅ up to 60 dates | ❌ one date per run |
| **Booking-site prices per hotel** (up to ~40 sites) | ✅ optional | ⚠️ some |
| Official-site price & member rates | ✅ | ⚠️ |
| Free cancellation deadline, breakfast | ✅ | ⚠️ |
| Adults, nights, any currency | ✅ verified on every page | ⚠️ |
| Wrong-date / wrong-currency pages are retried, never returned | ✅ | ❌ |
| **Fixed competitive set** — the same hotels on every date, no gaps | ✅ | ❌ |
| Your hotel + nearby competitors from a single hotel name | ✅ | ❌ |

### Use cases (hotel rate shopping, rate parity, market research)

- **Hotel revenue management** — track competitors' prices for the next 30–60 days.
- **Rate parity monitoring** — see which booking site undercuts your official rate.
- **Travel apps & price alerts** — cheapest site per hotel per date.
- **Market research** — price levels, deals and seasonality by city or neighborhood.

### Input

| Field | Description | Default |
|---|---|---|
| `location` | City, area or landmark, e.g. `Paris`, `Shibuya Tokyo`, `near Times Square`. Required unless `hotels` is set; with `hotels` it is added to each name to find the right hotel | — |
| `hotels` | **Rate shopping mode:** your competitive set (hotel names, max 50) — every hotel on every date | — |
| `checkIn` | First check-in date `YYYY-MM-DD` | today + 14 days |
| `dateRangeDays` | Number of consecutive check-in dates (1–60) | 1 |
| `nights` | Nights per stay | 1 |
| `adults` | Adults (1–6) | 2 |
| `currency` | ISO code (USD, EUR, GBP, JPY, KRW…) | USD |
| `maxHotels` | Hotels per check-in date (city search) | 50 |
| `maxResults` | Hard cap on total rows (0 = no limit) | 0 |
| `includeOffers` | Add booking-site prices for the top hotels | false |
| `offersMaxHotels` | Top hotels per date that get booking-site prices (city search; in rate shopping mode every hotel gets them) | 10 |
| `includeRooms` | Add room-level rates per booking site | false |

**Tip:** for `includeOffers`, run with **512 MB+ memory** — it is several times faster and not more expensive.

#### Example

```json
{
  "location": "Paris",
  "checkIn": "2026-12-01",
  "dateRangeDays": 30,
  "nights": 1,
  "adults": 2,
  "currency": "EUR",
  "maxHotels": 50,
  "includeOffers": true,
  "offersMaxHotels": 20
}
```

### Output

One row per **hotel × check-in date**:

```json
{
  "searchLocation": "Tokyo",
  "checkIn": "2026-11-09",
  "checkOut": "2026-11-10",
  "nights": 1,
  "adults": 2,
  "currency": "USD",
  "rank": 1,
  "name": "Koko Hotel Tsukiji Ginza",
  "hotelClass": "3-star hotel",
  "stars": 3,
  "rating": 3.9,
  "reviews": 735,
  "pricePerNight": 94.42,
  "pricePerNightDisplay": "$94",
  "totalPrice": 94,
  "totalPriceDisplay": "$94",
  "deal": "25% less than usual",
  "latitude": 35.6648,
  "longitude": 139.7732,
  "website": "https://koko-hotels.com/tsukiji_ginza",
  "country": "JP",
  "googleHotelsUrl": "https://www.google.com/travel/hotels/entity/...",
  "offersStatus": "ok",
  "offersCount": 24,
  "lowestOfferProvider": "klook",
  "lowestOfferPricePerNight": 94.42,
  "offers": [
    {
      "provider": "Hotels.com",
      "isOfficialSite": false,
      "isSponsored": true,
      "pricePerNight": 101.3,
      "totalPrice": 101.3,
      "originalPricePerNight": null,
      "freeCancellation": true,
      "freeCancellationUntil": "Nov 7",
      "breakfastIncluded": true,
      "roomOptions": 3
    }
  ]
}
```

- `pricePerNight` / `totalPrice` — nightly price and the total for the whole stay.
- `offers[].isOfficialSite` — the hotel's own website (often with member rates; `originalPricePerNight` shows the price before discount).
- `offers[].freeCancellation` — `true` / `false`, or `null` when the site does not state it (typical for meta-search sites).
- `priceStatus` (rate shopping mode) — `ok`, `notListed` (hotel not shown on Google for that date, e.g. sold out), `failed` (Google page could not be loaded after retries) or `hotelNotFound` (name not found — check spelling or add the city). Only `ok` rows are charged.
- `offersStatus` — `ok`, `none` (no booking-site prices for that date), `failed`, or `skipped` (hotel below `offersMaxHotels`).

### Pricing

Pay per event — you pay only for the rows you get, **no platform usage fees**:

- **Hotel row** — $1.5 per 1,000
- **Hotel row with booking-site prices** — $4 per 1,000 (charged instead of the plain row, and only when booking-site prices were actually found)

Rows with `offersStatus` `none`, `failed` or `skipped` are charged as plain hotel rows ($1.5 per 1,000).

Examples:

- 30-day calendar, 50 hotels, no booking-site prices → 1,500 rows ≈ **$2.25**
- 30-day calendar, 50 hotels, booking-site prices for the top 10 hotels per date (default) → 300 × $0.004 + 1,200 × $0.0015 ≈ **$3**
- 30-day calendar, 50 hotels, booking-site prices for all 50 (`offersMaxHotels: 50`) → 1,500 rows ≈ **$6**

In rate shopping mode, rows with `priceStatus` `notListed`, `failed` or `hotelNotFound` are **free**. Example: 5 competitors × 30 dates with booking-site prices → 150 rows ≈ **$0.60**.

Set a **maximum cost per run** in the run options — the Actor stops before exceeding it and does not fetch pages it cannot charge for. The exact number of charged rows of each type is saved in the run's key-value store as `RUN_STATS`.

### Reliability

Every results page is checked against your request (dates, nights, adults, currency) and retried if Google returns something else, so you never pay for wrong data. In our test of a 30-day New York calendar with booking-site prices (1,568 Google pages), **0 pages failed and 0 were blocked**.

### Daily monitoring

Use **Schedules** in Apify Console to run the same input every morning and get a fresh rate calendar each day. Results can be downloaded as JSON, CSV or Excel, or sent to Google Sheets, Slack, webhooks and other apps via Apify integrations. The `offers` array is nested — JSON keeps it intact; in CSV/Excel it is flattened into columns.

### Good to know

- Prices are a **snapshot of what Google shows at scrape time** (US point of sale). Google may refresh some booking-site prices a few seconds after the page loads, so a few prices can differ slightly from what you see later in a browser.
- Booking sites can show **different room types**: one site's lowest price may be for a standard room and another's (often the official site) for a larger room. For a strict like-for-like parity check, turn on `includeRooms` and compare the same room name.
- Large areas (e.g. "Tokyo") can include hotels on the outskirts. Use a neighborhood (`Shinjuku Tokyo`) for a tight competitive set.
- Only public price and hotel information is collected. No personal data, no reviews' text, no user accounts.

### FAQ

**Can I scrape a single hotel?** Yes — put its name in `hotels` (e.g. `["Park Hyatt Tokyo"]`) and set `dateRangeDays` for a full calendar.

**Why do city-search results change from date to date?** That is how Google ranks hotels (availability, price, ads). Use `hotels` to keep a fixed competitive set.

**Which point of sale / country are prices from?** United States (prices as shown on google.com in the US), converted to the currency you choose.

**Children?** Not yet supported — adults only.

**How fast is it?** At 512 MB: ~60 hotels with booking-site prices in ~25 s; a 30-day calendar of 50 hotels, all with booking-site prices (1,470 rows, ~34,000 site prices), in ~15 min.

**Can I use it for rate parity monitoring?** Yes — put your hotel in `hotels` and turn on `includeOffers`. Each row lists every booking site's price for that hotel and date, and `offers[].isOfficialSite` marks the hotel's own website, so you can compare the direct price with Booking.com, Expedia, Agoda and others for every check-in date (see the room-type note above for like-for-like checks).

***

*This Actor is not affiliated with, endorsed by or sponsored by Google. It only collects publicly available hotel and price information.*

# Actor input Schema

## `location` (type: `string`):

City, neighborhood or landmark, e.g. 'Paris', 'Shibuya Tokyo', 'Manhattan New York'. Required unless you enter competitor hotels below; with hotels, it is added to each hotel name to find the right one (e.g. 'Tokyo').

## `hotels` (type: `array`):

Optional. Enter your competitive set (hotel names, one per line, max 50). Every hotel gets a price for EVERY check-in date — no gaps from Google's changing city ranking. Hotels sold out on a date are kept as empty rows (priceStatus 'notListed', not charged). When used, 'Max hotels per date' and 'Hotels per date with offers' are ignored.

## `checkIn` (type: `string`):

YYYY-MM-DD (e.g. 2026-12-01). Leave empty for 14 days from today.

## `dateRangeDays` (type: `integer`):

Scrape consecutive check-in dates starting from the first one (1 = single date, 30 = a month-long rate calendar).

## `nights` (type: `integer`):

Length of each stay. Check-out = check-in + nights.

## `adults` (type: `integer`):

Number of adult guests.

## `currency` (type: `string`):

ISO code, e.g. USD, EUR, GBP, JPY, KRW.

## `maxHotels` (type: `integer`):

Maximum hotels to collect for each check-in date.

## `maxResults` (type: `integer`):

Hard cap on total rows across all dates - use it to control cost.

## `includeOffers` (type: `boolean`):

For the top hotels of each date, also collect prices from every booking site shown on Google (Booking.com, Expedia, Agoda, official site...) with free-cancellation info. Costs 1 extra request per hotel.

## `offersMaxHotels` (type: `integer`):

How many top-ranked hotels per check-in date get booking-site prices.

## `includeRooms` (type: `boolean`):

Add each booking site's room types with price, breakfast and free-cancellation details (bigger output).

## `concurrency` (type: `integer`):

How many check-in dates are scraped in parallel.

## `detailConcurrency` (type: `integer`):

How many hotel detail pages (booking-site prices) are fetched at the same time.

## `delaySeconds` (type: `string`):

e.g. 1 or 1.5

## Actor input object example

```json
{
  "location": "Paris",
  "hotels": [],
  "dateRangeDays": 1,
  "nights": 1,
  "adults": 2,
  "currency": "USD",
  "maxHotels": 50,
  "maxResults": 0,
  "includeOffers": false,
  "offersMaxHotels": 10,
  "includeRooms": false,
  "concurrency": 2,
  "detailConcurrency": 4,
  "delaySeconds": "1"
}
```

# Actor output Schema

## `rateCalendar` (type: `string`):

One row per hotel and check-in date with nightly and total price.

## `bookingSitePrices` (type: `string`):

Per hotel and date: number of booking sites, cheapest site and all booking-site offers.

## `runStats` (type: `string`):

Requests, retries, blocked/mismatched pages and charged events for this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "location": "Paris",
    "hotels": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("datalume/google-hotels-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "location": "Paris",
    "hotels": [],
}

# Run the Actor and wait for it to finish
run = client.actor("datalume/google-hotels-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "location": "Paris",
  "hotels": []
}' |
apify call datalume/google-hotels-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datalume/google-hotels-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BRO0ACyTMV8185ObH/builds/mm2hQdkeAsptaBXzx/openapi.json
