# Google Hotels Scraper & Rate Monitor - Prices by Booking Site (`ivora/google-hotels-rate-monitor`) Actor

Hotel prices from Google Hotels for any dates: price per booking site (Booking.com, Expedia, Agoda, hotel's own site…), lowest price, rating, reviews and stars. Monitor mode returns only price changes since the last run. Competitor rate shopping for hotels.

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

## Pricing

from $3.00 / 1,000 hotel and date checkeds

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 Monitor: Prices by Booking Site

Get **hotel prices from Google Hotels for the exact dates you choose**, broken down **by booking site**: Booking.com, Expedia, Agoda, Trip.com, Hotels.com, Priceline, the hotel's own website and the rest. Each row also has the lowest price, rating, review count and star class. In **monitor mode** a daily run returns **only the prices that changed** since the last run, with old price, new price and delta per site.

It's built for **hotel revenue managers doing daily rate shopping**, small hotel groups and consultants watching a competitor set, and travel-deal sites.

- **Real dates, not Google's default.** Pick fixed stays (`2026-12-04/2026-12-06`) or **rolling dates** ("the next 14 nights from today", "every Friday for 8 weeks"). Every result is checked against the dates Google actually priced. A mismatch is never charged.
- **Any guests and currency:** adults, currency (USD, EUR, GBP…), country market and language.
- **Hotels by name, URL or destination.** Use `"Hotel Botanico Lisbon"`, a `google.com/travel/hotels/...` link, or `"hotels in Paris"` (top 1–20 hotels, pinned across runs).
- **Parity check:** the hotel's own-site price against the cheapest OTA (`parityGapPct`).
- No login, no API key, no browser. Plain HTTP through Apify datacenter proxy, about 1–3 seconds per hotel and date.

### Sample output

One `hotelDate` row from a real cloud run (Oct 2026). The price list is shortened to 3 of 20 sites, and links are cut:

```json
{"type": "hotelDate", "hotelName": "Hotel Botânico", "checkIn": "2026-10-23", "checkOut": "2026-10-25", "nights": 2, "adults": 2,
 "currency": "EUR", "stars": 3, "rating": 4.4, "reviewCount": 873,
 "lowestPrice": 103.94, "lowestPriceSite": "Vio.com", "lowestTotalPrice": 207.88,
 "officialSitePrice": 120.5, "cheapestOta": "Vio.com", "cheapestOtaPrice": 103.94, "parityGapPct": 15.93, "sitesCount": 20,
 "prices": [
   {"site": "Vio.com", "pricePerNight": 103.94, "totalPrice": 207.88, "isOfficialSite": false, "isSponsored": false, "link": "https://deals.vio.com/?sig=…"},
   {"site": "Trip.com", "pricePerNight": 103.98, "totalPrice": 207.97, "isOfficialSite": false, "isSponsored": false, "link": "https://us.trip.com/hotels/redirect?hotelid=3483974…"},
   {"site": "Hotel Botânico", "pricePerNight": 120.5, "totalPrice": 241, "isOfficialSite": true, "isSponsored": false, "link": "https://be.heytravel.net/…"}
 ],
 "googleHotelsUrl": "https://www.google.com/travel/hotels/entity/ChgIj5u_rIHe5tKxARoLL2cvMXRqN2c5eXcQAQ/prices?…"}
```

A `change` row from the next scheduled run (real, from the same test):

```json
{"type": "change", "changeType": "priceChange", "hotelName": "The Central House Lisbon Baixa", "checkIn": "2026-10-30", "nights": 2,
 "site": "Kiwi.com", "oldPrice": 136.25, "newPrice": 133.27, "delta": -2.98, "changePct": -2.19, "direction": "down", "currency": "EUR",
 "previousCheckAt": "2026-10-09T16:09:16Z", "detectedAt": "2026-10-09T16:10:41Z"}
```

### How it works

1. **Hotels.** Names are searched on Google Hotels, and the first hotel listed is used. The matched name is in every row (`hotelName`, `matchedFrom`), and the match is remembered for later runs. URLs and entity keys are used directly. For a **destination**, the top hotels on the first run are saved and checked again on every run, because Google reorders destination results even minutes apart. Turn on `refreshDestinationHotels` to pick them again.
2. **Dates.** Google ignores simple `checkin=` URL parameters. The Actor builds Google's own encoded travel parameter for your dates, guests and currency. It then verifies the stay and currency Google echoes back before saving or charging anything.
3. **Prices.** For each hotel and stay it reads the hotel's Google Hotels *Prices* page, which shows every booking site with price per night, total and booking link.
4. **Changes.** The last prices for each hotel, stay, adults, currency and country are kept in a named key-value store (`stateStoreName`). The next run compares against them:
   - `priceChange`: one booking site's nightly price moved (old, new, delta, %).
   - `lowestPriceChange`: the lowest price moved, or another site became the cheapest.
   - `soldOut` / `backAvailable`: the stay has no prices any more, or has prices again.
   - `newSite` / `siteRemoved` (off by default): a booking site appeared in the list or left it.

Rolling dates fit this design. A stay is remembered by its calendar date, so "next 14 nights" compares each night with the price seen for that night on earlier runs. A night that enters the window is a first check, and nights in the past are forgotten.

### Input example

```json
{
  "hotels": ["Hotel Botanico Lisbon", "https://www.google.com/travel/hotels/entity/ChkIg-b2ismUj7M1Gg0vZy8xMWg3MThreGg1EAE"],
  "destinations": ["hotels in Lisbon"],
  "maxHotelsPerDestination": 5,
  "daysAhead": 1,
  "rollingDates": 14,
  "nights": 1,
  "adults": 2,
  "currency": "EUR",
  "country": "gb",
  "mode": "changes",
  "minChangePct": 2,
  "stateStoreName": "lisbon-compset"
}
```

| Option | What it does |
|---|---|
| `hotels` | Names with city, Google Hotels URLs or entity keys |
| `destinations`, `maxHotelsPerDestination` | "hotels in …" searches. The top 1–20 hotels are pinned across runs (`refreshDestinationHotels` re-picks them) |
| `checkInDates` | Fixed stays: `YYYY-MM-DD` (uses `nights`) or `YYYY-MM-DD/YYYY-MM-DD` |
| `daysAhead`, `rollingDates`, `rollingStepDays` | Rolling check-ins: start N days from today, how many, days between them (7 = same weekday). Without any dates: one stay 7 days ahead |
| `nights`, `adults`, `currency`, `country`, `language` | The search itself |
| `mode` | `changes` (default): full rows the first time a hotel and date is checked, then only change rows. `allPrices`: a full row on every run, plus change rows |
| `changeTypes`, `minChangePct`, `minChangeAmount` | Which changes you want and how small is too small |
| `sitesFilter` | Track only some sites, e.g. `["booking", "expedia", "official"]` |
| `includeSponsored`, `includeLinks`, `includeVacationRentals` | Extra filters |

### Output

- **`hotelDate` rows**, one per hotel and stay. You get them on the first check, and on every run in `allPrices` mode. Each row has the hotel name, key, address, coordinates, stars, rating and reviews; the stay (check-in, check-out, nights, adults, currency); `available`; the lowest price and its site; total for the stay; own-site price; cheapest OTA and `parityGapPct`; `sitesCount`; `prices[]` (site, price per night, total, display strings, own site / sponsored flags, booking link); `changesSinceLastCheck`; and a Google Hotels link that opens the same search. When a stay has no prices, `noPricesSuggestedStay` shows the stay Google suggests instead, for example a minimum stay of 3 nights.
- **`change` rows**: `changeType`, `site`, `oldPrice`, `newPrice`, `delta`, `changePct`, `direction`, `oldCheapestSite`/`newCheapestSite`, plus the hotel and stay context.
- **One `summary` row per run** (free): hotels, dates, checks done or failed, sold-out stays, change counts by type, and the 5 cheapest stays.
- **`error` rows** (free, never charged): `bad_input`, `not_found`, `search_failed`, `blocked`, `parse_error`, `dates_not_applied`.

The dataset has ready-made views: **Prices by hotel and date**, **Price changes**, **Run summary** and **Errors**.

### Honest limits

- **Prices are what Google Hotels shows** for the chosen country. That is usually the cheapest standard room per night. Whether taxes and fees are included depends on the market: US results usually exclude them, while UK and EU results usually include them. Member, mobile-only and loyalty rates aren't visible.
- **The list of booking sites varies between runs.** Small meta-search sites appear and disappear, so `newSite`/`siteRemoved` are off by default. Big OTAs and the hotel's own site are stable.
- **Name matching takes the first hotel Google lists.** Check `hotelName` on the first run, or paste the hotel's URL to be exact.
- **Destination searches read Google's first results page,** so at most 20 hotels per destination. To track more, add hotels by name or URL.
- **Sold-out stays are charged as a check.** "No prices for these dates" is real information, and the row says so. Failed or mismatched pages are never charged.
- **Google can change its page format.** The Actor runs a daily self-test, and pages it cannot read become free error rows instead of wrong data.
- About 330 days ahead at most, up to 30 nights, 1–8 adults, and no children yet.

In our Apify cloud tests (Oct 2026): **116 of 116 hotel-date checks succeeded** across Lisbon, Paris, London and New York in USD, EUR and GBP, with **no captcha or blocked page**. About 5% of requests hit a slow proxy connection and were retried automatically. A 60-check run took 93 seconds.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.005 per run |
| Hotel and date checked (prices read and compared) | $0.003 |
| Price change row | $0.0005 |

`hotelDate` rows (first checks and `allPrices` mode), summary and error rows cost nothing extra.

Examples:

- **Competitor set of 10 hotels, next 14 nights, checked daily:** 140 checks plus about 30 changes a day = $0.005 + $0.42 + $0.015 = **$0.44 per day** (about $13 per month).
- **5 hotels on 4 key weekends, daily:** 20 checks = **about $0.07 per day**.
- **One-off price grid for 1 hotel × 30 nights:** $0.005 + $0.09 = **$0.095**.

### Daily alerts to Slack or email

1. Fill in the input and click **Save as a new task**. One task per competitor set or client works well.
2. In **Schedules**, create a schedule, for example every day at 07:00 in your time zone, and add the task.
3. In the task's **Integrations** tab, add the **Slack** or **Gmail** integration for a message when a run finishes. Or add a **webhook** on "Run succeeded" for Zapier, Make, n8n or your own endpoint, which can read `https://api.apify.com/v2/datasets/{defaultDatasetId}/items?view=changes`.

In monitor mode every scheduled run's dataset *is* your alert list: an empty changes view means no price moved. Use `minChangePct` to hear only about meaningful moves, and turn on Apify's run-failure notifications.

### Related actors

This Actor is part of a small **competitor-intelligence suite** from the same developer. They all work the same way: pay per event, failed items never charged, and monitors that return only what changed since the last run.

- [Google Ads Transparency Scraper & New Ads Monitor](https://apify.com/ivora/google-ads-transparency-monitor): what your competitors advertise on Google Search, Display and YouTube.
- [LinkedIn Ad Library Scraper & New Ads Monitor](https://apify.com/ivora/linkedin-ad-library-monitor): competitors' LinkedIn ads without login, including EU impressions and targeting.
- [Bing Ads Library Scraper - Microsoft Ads Monitor (EU)](https://apify.com/ivora/microsoft-ads-library-monitor): Bing ads from Microsoft's official Ad Library (EU/EEA).
- [Google Trends Scraper & API](https://apify.com/ivora/google-trends-api): interest over time, by region/city, top queries and Trending now.
- [ATS Jobs Scraper & Hiring Monitor](https://apify.com/ivora/company-hiring-monitor): new and closed jobs from Greenhouse, Lever, Ashby, Workday and 6 more job boards.
- [App Store & Google Play Scraper](https://apify.com/ivora/app-store-monitor): ratings, versions, chart and keyword ranks of iOS and Android apps.
- [Shopify Store Monitor](https://apify.com/ivora/shopify-store-monitor): competitor Shopify stores' new products, price changes and stock.

### FAQ

**Do I need a proxy?** No. The default Apify datacenter proxy worked for every check in our tests. A residential fallback is available but off by default.

**Can I get prices for one hotel over many dates?** Yes. Use `rollingDates` (up to 60 check-ins per run) or list the stays in `checkInDates`.

**Why did my destination always return the same hotels?** They are pinned on purpose, so the monitor compares like with like. Set `refreshDestinationHotels` to re-pick them, or use a new `stateStoreName`.

**Is it legal?** It reads publicly available prices from Google Hotels. You are responsible for how you use the data and for complying with applicable law and Google's terms.

# Actor input Schema

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

Hotel names with the city (e.g. 'Hotel Botanico Lisbon'), Google Hotels URLs (google.com/travel/hotels/entity/…) or entity keys. Names are matched to the first hotel Google lists; the matched name is in every row.

## `destinations` (type: `array`):

Searches such as 'hotels in Lisbon' or 'hotels near Times Square'. The top hotels Google lists on the first run are saved and tracked on later runs (see 'Hotels per destination').

## `maxHotelsPerDestination` (type: `integer`):

How many hotels from the top of each destination search are tracked (max 20, the first Google results page).

## `includeVacationRentals` (type: `boolean`):

Destination searches also list apartments and holiday homes. Off = hotels only.

## `refreshDestinationHotels` (type: `boolean`):

Google reorders destination results often (even minutes apart). By default the hotels picked on the first run are saved and re-checked on every run, so the monitor compares the same hotels. Turn on to re-pick the current top hotels.

## `checkInDates` (type: `array`):

Fixed stays as YYYY-MM-DD (uses 'Nights') or YYYY-MM-DD/YYYY-MM-DD for check-in/check-out. Leave empty to use rolling dates below.

## `daysAhead` (type: `integer`):

Rolling dates move with each run, ideal for daily schedules: 0 = tonight, 7 = one week from the run date. Used when 'Rolling: number of check-in dates' > 0, or when no fixed dates are given (then one date).

## `rollingDates` (type: `integer`):

How many consecutive check-in dates to check, starting 'N days from today'. Example: daysAhead 1 + 14 dates = the next two weeks of arrivals.

## `rollingStepDays` (type: `integer`):

1 = every day, 7 = same weekday each week.

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

Length of stay for rolling dates and for fixed dates given without a check-out.

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

Guests per room.

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

3-letter code: USD, EUR, GBP, CAD, AUD… Google converts the prices.

## `country` (type: `string`):

The market you see prices as (us, gb, de, fr…). It changes which booking sites appear and whether prices include taxes.

## `language` (type: `string`):

Interface language, e.g. en, de, es.

## `mode` (type: `string`):

changes = full price rows the first time a hotel + date is checked, then only change rows (old/new/delta per site). allPrices = a full row per hotel and date on every run, plus change rows.

## `changeTypes` (type: `array`):

Which change rows to output. All changes are counted in the free rows anyway. New / removed booking sites are off by default because small meta-search sites come and go.

## `minChangePct` (type: `integer`):

Ignore price moves smaller than this percentage.

## `minChangeAmount` (type: `integer`):

Ignore price moves smaller than this amount in your currency.

## `sitesFilter` (type: `array`):

Optional: track only sites whose name contains one of these words, e.g. booking.com, expedia, agoda, official (the hotel's own website). Empty = all sites.

## `includeSponsored` (type: `boolean`):

Google shows a few paid ('Sponsored') offers above the list. They are labelled isSponsored.

## `includeLinks` (type: `boolean`):

Add each site's booking link (deep link with your dates) to the price list.

## `stateStoreName` (type: `string`):

Named key-value store that remembers the last prices. Use a different name for each independent monitor (a-z, 0-9, -).

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

How many hotels are checked at the same time.

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

Apify datacenter proxy works for Google Hotels in our tests and is the default.

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

If Google keeps showing a captcha, switch to Apify residential proxy (slower; off by default).

## Actor input object example

```json
{
  "hotels": [
    "Hotel Botanico Lisbon"
  ],
  "maxHotelsPerDestination": 10,
  "includeVacationRentals": false,
  "refreshDestinationHotels": false,
  "daysAhead": 7,
  "rollingDates": 1,
  "rollingStepDays": 1,
  "nights": 1,
  "adults": 2,
  "currency": "USD",
  "country": "us",
  "language": "en",
  "mode": "changes",
  "changeTypes": [
    "priceChange",
    "lowestPriceChange",
    "soldOut",
    "backAvailable"
  ],
  "minChangePct": 0,
  "minChangeAmount": 0,
  "includeSponsored": true,
  "includeLinks": true,
  "stateStoreName": "google-hotels-rate-monitor-state",
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "residentialFallback": false
}
```

# Actor output Schema

## `prices` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `runSummary` (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 = {
    "hotels": [
        "Hotel Botanico Lisbon"
    ],
    "daysAhead": 7,
    "rollingDates": 1,
    "currency": "USD",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ivora/google-hotels-rate-monitor").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 = {
    "hotels": ["Hotel Botanico Lisbon"],
    "daysAhead": 7,
    "rollingDates": 1,
    "currency": "USD",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("ivora/google-hotels-rate-monitor").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 '{
  "hotels": [
    "Hotel Botanico Lisbon"
  ],
  "daysAhead": 7,
  "rollingDates": 1,
  "currency": "USD",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call ivora/google-hotels-rate-monitor --silent --output-dataset

```

## MCP server setup

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

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/Ry8lHg7Y79ZQpKrB9/builds/8yJN3VTYNzAaKfSak/openapi.json
