# Google Hotels Prices Scraper — Provider Prices & Price Alerts (`niyomurinzi.elis/google-hotels-prices`) Actor

Scrape Google Hotels prices for any city or hotel: lowest price, every booking site's rate (Booking.com, Expedia, Agoda, official site…), free cancellation, rating, stars and amenities in USD. Built-in price-change alerts.

- **URL**: https://apify.com/niyomurinzi.elis/google-hotels-prices.md
- **Developed by:** [Samuel IZABAYO](https://apify.com/niyomurinzi.elis) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 – Prices, Ratings & OTA Rates

Scrape **Google Hotels** for any city or hotel and get, per hotel: the lowest nightly price, **every OTA's rate** (Booking.com, Expedia, Hotels.com, Agoda, Trip.com, Priceline, the hotel's official site and more), free-cancellation flags, direct booking links, **rating**, review count, star class, amenities, address and coordinates. Prices come back in **USD** by default, or in one of 11 other currencies (EUR, GBP, JPY, …).

It also has a built-in **price alert mode**: schedule it, and each run outputs only the hotels whose lowest price changed since the last run, ready for Slack, email, Google Sheets, Zapier, Make or a webhook.

**Pay only for results:** $1.00 per 1,000 hotels, plus $2.00 per 1,000 for the full OTA price list. Failed or empty hotels are never charged.

### Why this Google Hotels scraper?

- **Built to keep working.** It reads the structured data that Google Travel's own frontend uses instead of fragile HTML class names, so cosmetic redesigns don't break it.
- **Automatic retries and proxy fallback.** Blocked or empty responses are retried on a fresh IP, then through US residential proxies if needed. You don't configure anything.
- **You never pay for failures.** A hotel is charged only when it comes back with a price; the OTA price list is charged only when it was fetched and isn't empty.
- **Fast and light.** No headless browser. Pagination and OTA price lists use small requests, which keeps runs quick and cheap.
- **Prices in your currency, wherever the proxy is.** The currency is part of the request, so you never get a random local currency.
- **Accurate stays.** Exact check-in/check-out dates and adult count are sent to Google, and the OTA booking links carry the same dates and guests.
- **Price alerts built in.** No database or diffing script needed.

### What data do you get?

| Field | Example |
|---|---|
| `hotel` | `Ritz Paris` |
| `entityId` | `ChUImcGKtLfWgL9OGgkvbS8wNTZuODEQAQ` (stable Google hotel ID) |
| `location` | Your input query, e.g. `Paris` |
| `checkIn` / `checkOut` / `adults` | `2026-12-01` / `2026-12-04` / `2` |
| `lowestPrice` | `2806.1` (per night) |
| `totalPrice` | `8418` (whole stay) |
| `currency` | `USD` |
| `rating` / `reviews` | `4.6` / `4643` |
| `stars` | `5` |
| `amenities` | `["Free Wi-Fi", "Indoor pool", "Spa", "Fitness center", …]` |
| `address`, `latitude`, `longitude` | `15 Pl. Vendôme, 75001 Paris, France` |
| `providerPrices` | OTA rates, see below |
| `providerCount` | `12` |
| `googleUrl` | Link to the hotel on Google Hotels |
| `scrapedAt` | ISO timestamp |
| `previousLowestPrice`, `priceChange`, `changeType` | Alert mode only |
| `history` | Price history: 30/90-day min/median/max, percentile, lowest seen, sparkline (only with **Add price history**, see below) |

Each entry in `providerPrices` looks like this:

```json
{
  "provider": "Booking.com",
  "price": 129,
  "totalPrice": 258,
  "freeCancellation": true,
  "freeCancellationUntil": "Nov 9",
  "official": false,
  "url": "https://www.booking.com/searchresults.en.html?...checkin=2026-11-10&checkout=2026-11-12&group_adults=2..."
}
```

OTA offers are sorted from cheapest to most expensive, one entry per booking site. `official: true` marks the hotel's own website.

### How to use it

1. Enter one or more **Locations** (e.g. `New York`, `Shibuya, Tokyo`, `beach hotels in Cancun`) and/or **Hotel names** (e.g. `Bellagio Las Vegas`).
2. Pick **check-in and check-out dates** and the number of **adults**. If you leave the dates empty, the stay starts 14 days from today and lasts 2 nights.
3. Set **Max hotels per location**. Google returns about 20 hotels per page, and the scraper paginates automatically.
4. Keep **Include provider prices** on for the full OTA price comparison. Turn it off for a cheaper hotel list with the lowest price only.
5. Click **Start**, then download the results as JSON, CSV, Excel or HTML, or use the API.

### Examples

#### Hotel list with prices and ratings (cheapest)

```json
{
  "locations": ["Barcelona", "Lisbon"],
  "checkIn": "2026-12-01",
  "checkOut": "2026-12-04",
  "maxHotelsPerLocation": 100,
  "includeProviderPrices": false
}
```

200 hotels with lowest price, rating, stars and amenities: at most **$0.20**.

#### Full OTA price comparison

```json
{
  "locations": ["New York", "Paris", "Tokyo"],
  "checkIn": "2026-12-01",
  "checkOut": "2026-12-04",
  "adults": 2,
  "maxHotelsPerLocation": 50,
  "includeProviderPrices": true,
  "currency": "EUR"
}
```

150 hotels, each with every booking site's rate: at most **$0.45**.

#### Specific hotels

```json
{
  "hotelNames": ["Ritz Paris", "Bellagio Las Vegas", "Marina Bay Sands Singapore"],
  "checkIn": "2026-12-20",
  "checkOut": "2026-12-22"
}
```

One row per hotel, matched to Google's top result. Add the city to the name for best matching.

#### Daily price-drop alerts

```json
{
  "locations": ["Manhattan, New York"],
  "checkIn": "2026-12-28",
  "checkOut": "2027-01-02",
  "maxHotelsPerLocation": 60,
  "onlyChangedSinceLastRun": true,
  "stateKey": "nyc-new-year",
  "minPriceChangePercent": 5
}
```

Run it on a daily **Schedule**. After the first (baseline) run, only hotels whose price moved by 5 % or more are output — and charged.

#### From the API

```bash
curl -X POST "https://api.apify.com/v2/acts/niyomurinzi.elis~google-hotels-prices/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"locations": ["London"], "maxHotelsPerLocation": 20}'
```

### Migrating from other Google Hotels scrapers

Switching is meant to take minutes, not a rewrite. Common field names from other Google Hotels scrapers are accepted as aliases:

| Your existing field | Used as |
|---|---|
| `checkInDate` | `checkIn` |
| `checkOutDate` | `checkOut` |
| `maxResults` | `maxHotelsPerLocation` |
| `languageCode`, `countryCode`, `requestTimeoutSecs` | Accepted and ignored (results are in English; timeouts and retries are automatic) |

If both an alias and the native field are set, the native field wins. Other fields need to be mapped by hand — usually just your search terms into `locations` (cities or free-text queries) or `hotelNames` (specific hotels). Output fields may also be named differently; see [What data do you get?](#what-data-do-you-get) and use `entityId` as the stable hotel key.

Example of an input written for another scraper, adapted:

```json
{
  "locations": ["Rome"],
  "checkInDate": "2026-12-01",
  "checkOutDate": "2026-12-03",
  "maxResults": 40,
  "languageCode": "en"
}
```

### Hotel price alerts (monitoring mode)

Turn on **Price alerts: only output changed prices** and give your watch-list a **State key**, e.g. `nyc-december`.

- **First run.** Everything is output as a baseline, and the prices are saved.
- **Every later run.** Only hotels whose lowest price changed are output, with `previousLowestPrice`, `priceChange` and `changeType`.
- **Tuning.**
  - Use **Minimum price change (%)** to ignore small moves.
  - Turn on **Also alert on newly listed hotels** if you also want hotels that show up for the first time.
- **Scope of the saved prices.** They are stored per hotel, stay dates, adults and currency, in a named key-value store (`google-hotels-prices-state`), so they persist between runs.

To automate it, create a **Schedule**, for example daily at 08:00, and add an integration such as Slack, email, Google Sheets or a webhook that fires when the run succeeds. Since alert-mode runs only output changes, every row is a real price movement.

### Price history and lowest-price tracking

Turn on **Add price history** to see whether today's price is actually low. Each hotel gets a `history` object built from prices collected every day for the tracked hotels (the top hotels of 20 popular cities, 2-night stays 14, 30 and 60 days ahead, 2 adults, USD):

| Field | Meaning |
|---|---|
| `currentPrice` | Today's price compared against the history (live price when your stay is 2 nights / 2 adults, else the latest collected price) |
| `min30d` / `median30d` / `max30d` | Lowest, median and highest daily price over the last 30 days |
| `min90d` / `median90d` / `max90d` | Same over the last 90 days |
| `percentile` | Share of past observations cheaper than the current price: `0` = cheapest ever seen, `100` = most expensive |
| `lowestSeenPrice` / `lowestSeenDate` | The lowest price on record and when it was seen |
| `sparkline` | 30 daily lowest prices, oldest first (`null` for days without data), ready for a mini chart |
| `observations` / `historyDays` | How much history the numbers are based on |
| `status` / `charged` / `message` | `ok` (charged) or `tracking-started` / `not-tracked` / `unavailable` (not charged) |

```json
{
  "locations": ["Paris"],
  "checkIn": "2026-12-01",
  "checkOut": "2026-12-03",
  "adults": 2,
  "history": true
}
```

Already have hotel IDs? Put them in **Hotel entity IDs** (`entityIds`) together with `history: true` to get the history without a live price scrape.

- **New hotels and cities.** A hotel or city that isn't tracked yet is added to the tracked set. Its row says `tracking-started` with 0 days of history, and daily prices are collected from the next day.
- **Basis.** History is for 2-night stays, 2 adults, in USD. For other stays, the statistics still describe the hotel's price level, and the `message` says so.
- **Retention.** Up to 120 days of history are kept.

See [`samples/history_sample.json`](samples/history_sample.json) for an example output (synthetic prices).

### Pricing

This Actor uses **pay-per-event** pricing, with no start fee and no monthly rental:

| Event | Price | When it's charged |
|---|---|---|
| `hotel` | **$1.00 per 1,000** | Once per hotel row that has a price |
| `hotel-prices` | **$2.00 per 1,000** | Once per hotel row with a successfully fetched, non-empty OTA price list |
| `hotel-history` | **$0.003 per hotel** | Only with **Add price history**: once per hotel whose `history` has at least one observation |

- 1,000 hotels with full OTA prices: **$3.00**. Without OTA prices: **$1.00**.
- **Not charged:** history rows without data yet (`tracking-started`, `not-tracked`, `unavailable`), hotels that fail to load, hotels without any rates for your dates (output with `lowestPrice: null`), failed or empty OTA price lists, and hotels skipped as unchanged in alert mode.
- The Actor respects your **maximum cost per run** and stops cleanly when that limit is reached.
- Platform usage (compute and proxies, including residential fallback) is included in the event price.

### Reliability and proxies

- **Default proxy.** Apify **datacenter proxy (US)**.
- **Retries.** Any request that is blocked (captcha or HTTP 429), errors, or returns no data is retried on a new IP. If it still fails, it is retried through **US residential proxy**.
- **Persistent blocking.** If datacenter IPs are blocked consistently, the run switches to residential and re-checks the datacenter path from time to time.
- **Monitored daily.** A small multi-city test run checks the scraper every day so that changes on Google's side are caught early.
- **Run statistics.** Each run writes a `RUN_STATS` record: hotels found, saved and not charged, OTA price-list success rate, requests, blocks, residential fallbacks and bandwidth.
- **Your own proxy.** You can choose a different proxy in the input, for example your own residential group.

### FAQ

**Is scraping Google Hotels legal?**
This Actor collects publicly available price information, without logging in. You are responsible for using the data in line with applicable laws and Google's terms. Don't use it to collect personal data.

**Why do some hotels have no price?**
Some listings, often vacation rentals or small guesthouses, have no bookable rates for your dates. Google shows them without prices, and so does this scraper (`lowestPrice: null`, `providerPrices: []`). These rows are free.

**Is the price per night or for the whole stay?**
`lowestPrice` and `providerPrices[].price` are per night. `totalPrice` is for the whole stay, as shown by Google, before any taxes that Google doesn't display.

**Can I get prices in EUR or GBP?**
Yes. Set **Currency**. USD is the default.

**Why is the hotel order different between runs?**
Google personalizes and shuffles rankings slightly. Use `entityId` as the stable hotel key. Alert mode already does this.

**Can I scrape many dates for one hotel?**
Yes. Run the Actor once per date range, for example with a schedule or through the API, using **Hotel names**.

### Support

Found a problem or need a field that's missing? Open an issue on the Actor's **Issues** tab. Issues are usually answered within a day.

# Actor input Schema

## `locations` (type: `array`):

Cities, neighborhoods or free-text Google Hotels queries, e.g. <code>Paris</code>, <code>Manhattan, New York</code> or <code>beach hotels in Cancun</code>. Plain place names are searched as "hotels in \<place>".

## `hotelNames` (type: `array`):

Specific hotels to track, e.g. <code>Ritz Paris</code> or <code>Bellagio Las Vegas</code>. Add the city for best matching. One row per hotel.

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

Check-in date (YYYY-MM-DD). Defaults to 14 days from today.

## `checkOut` (type: `string`):

Check-out date (YYYY-MM-DD). Defaults to 2 nights after check-in. Max 30 nights.

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

Number of adult guests (1–12). Prices are for one room.

## `maxHotelsPerLocation` (type: `integer`):

How many hotels to collect per location. Google returns ~20 per page; the Actor paginates automatically.

## `includeProviderPrices` (type: `boolean`):

Fetch every booking site's price for each hotel (Booking.com, Expedia, Hotels.com, Agoda, Trip.com, official site…) with free-cancellation flags and booking links. Charged as an extra <code>hotel-prices</code> event per hotel.

## `onlyChangedSinceLastRun` (type: `boolean`):

Alert mode. Remembers each hotel's lowest price (per stay dates, adults and currency) under the <b>State key</b>. The first run outputs everything as a baseline; later runs output only hotels whose lowest price changed (with previous price and difference). Pair with a Schedule + Slack/email/webhook integration for price-drop alerts. You only pay for rows that are output.

## `stateKey` (type: `string`):

Name of the saved price snapshot used by alert mode. Use a different key per watch-list/schedule.

## `minPriceChangePercent` (type: `integer`):

Alert mode: ignore price moves smaller than this percentage. 0 = any change of at least one whole currency unit.

## `alertOnNewHotels` (type: `boolean`):

Alert mode: also output hotels that were not seen in earlier runs. Off by default because Google's ranking shuffles which hotels appear on the first pages.

## `history` (type: `boolean`):

Add a <code>history</code> object to every hotel: current price vs. its 30/90-day min/median/max, percentile of today's price (0 = cheapest seen), lowest-seen price and date, and a 30-day sparkline of daily lowest prices (2-night stays, 2 adults, USD, collected daily for the tracked hotels). Charged as an extra <code>hotel-history</code> event per hotel <b>only when history exists</b>; hotels without history yet are flagged (<code>tracking-started</code>) and not charged.

## `entityIds` (type: `array`):

With <b>Add price history</b> on: Google Hotels entity IDs (the <code>entityId</code> field of earlier results) to return history for directly, without a live price scrape. Only the <code>hotel-history</code> event applies to these rows. Ignored when price history is off.

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

Currency for all prices. USD by default, independent of proxy location.

## `maxConcurrency` (type: `integer`):

Parallel requests to Google. The default is tuned for reliability.

## `proxyConfiguration` (type: `object`):

Primary proxy. Defaults to Apify datacenter proxy in the US.

## `residentialFallback` (type: `boolean`):

If a request is blocked or comes back empty, retry it automatically through US residential proxies.

## `checkInDate` (type: `string`):

Alias of <code>checkIn</code> for inputs written for other Google Hotels scrapers. Ignored if <code>checkIn</code> is set.

## `checkOutDate` (type: `string`):

Alias of <code>checkOut</code>. Ignored if <code>checkOut</code> is set.

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

Alias of <code>maxHotelsPerLocation</code>. Ignored if <code>maxHotelsPerLocation</code> is set.

## `languageCode` (type: `string`):

Accepted for compatibility; has no effect. Results are always in English.

## `countryCode` (type: `string`):

Accepted for compatibility; has no effect. Use <b>Currency</b> and <b>Proxy configuration</b> instead.

## `requestTimeoutSecs` (type: `integer`):

Accepted for compatibility; has no effect. Timeouts and retries are managed automatically.

## Actor input object example

```json
{
  "locations": [
    "New York",
    "Paris"
  ],
  "adults": 2,
  "maxHotelsPerLocation": 20,
  "includeProviderPrices": true,
  "onlyChangedSinceLastRun": false,
  "stateKey": "default",
  "minPriceChangePercent": 0,
  "alertOnNewHotels": false,
  "history": false,
  "currency": "USD",
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "residentialFallback": true
}
```

# Actor output Schema

## `hotels` (type: `string`):

No description

## `hotelsFull` (type: `string`):

No description

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

No description

# 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 = {
    "locations": [
        "New York",
        "Paris"
    ],
    "maxHotelsPerLocation": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("niyomurinzi.elis/google-hotels-prices").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 = {
    "locations": [
        "New York",
        "Paris",
    ],
    "maxHotelsPerLocation": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("niyomurinzi.elis/google-hotels-prices").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 '{
  "locations": [
    "New York",
    "Paris"
  ],
  "maxHotelsPerLocation": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}' |
apify call niyomurinzi.elis/google-hotels-prices --silent --output-dataset

```

## MCP server setup

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

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/CVC7K9u1sAWmKd6oi/builds/4G9366Cjn6j62tylR/openapi.json
