# Google Hotels Rate Comparison Scraper (`fanndev/google-hotels-rate-comparison-scraper`) Actor

Compare what every booking site charges for the same hotel room-night: 17-25 OTA quotes per hotel with provider, price, taxes, cancellation terms and deep links, plus Google's own price, rating, star class and deal flag. No login.

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

## Pricing

from $0.40 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Rate Comparison Scraper

Find out what **every booking site** charges for the same hotel room-night. For each hotel you
get 17–25 quotes side by side — Booking.com, Agoda, Expedia, Trip.com, Klook, the hotel's own
site and more — with the price, whether taxes are included, whether cancellation is free, and
the direct booking link for each one.

### Why use this actor

- **Rate parity in one row.** The cheapest quote, the dearest quote and the spread between
  them are calculated for you, so you can see instantly which site is undercutting the others.
- **Every booking site at once**, instead of running one scraper per travel site.
- **Google's own price verdict** — "GREAT PRICE", or "18% less than usual" — alongside the
  rating, review count, star class and amenities.
- **Direct booking links** for each quote, so a price can be verified or handed to a customer.
- **No account, no login and no API key.** Nothing to manage and no browsers to run.
- **Automatic retries** and clear failure records: if a hotel can't be read, you get a row
  saying so rather than a silently missing hotel.

### How it works

1. You give the actor a destination query (`hotels in Singapore`) or the IDs of hotels you
   already care about.
2. The actor reads Google's public hotel pages and collects each hotel's details plus every
   booking-site quote shown for it.
3. Results stream into your dataset — ready to download as JSON, CSV or Excel, or to load
   straight into a database.

Run it on a schedule and you have a daily rate-parity feed for your own hotels or your
competitors'.

### Modes

| Mode | What it does | Requests |
| --- | --- | --- |
| `search` | Find hotels for a query and return their cards (price, rating, star class, deal flag) | 1 per query, ~21 hotels |
| `hotel` | Full detail for specific hotels, with all booking-site quotes nested under `rates` | 1 per hotel |
| `rates` | The same quotes flattened to one record each — the shape you want for analysis | 1 per hotel |
| `sponsored` | The paid placements on a hotel's page (quotes for *other* hotels) | 1 per hotel |
| `reference` | Check the source is live and report what it returns, including your currency | 1 |

### Input

```json
{
  "mode": "hotel",
  "hotelIds": [
    "ChUI17vV0u_fprYCGgkvbS8wZGQ5MDMQAQ",
    "https://www.google.com/travel/hotels/entity/ChcIyo2FjenOuZ8xGgsvZy8xdGYyMTV2aBAB"
  ],
  "languageCode": "en",
  "countryCode": "us",
  "maxItems": 200,
  "maxConcurrency": 3,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `mode` | string | `search` (default), `hotel`, `rates`, `sponsored` or `reference`. |
| `searchQuery` | string | A destination or hotel name, e.g. `hotels in Singapore`. |
| `searchQueries` | array | Several queries in one run, one request each. |
| `fetchRates` | boolean | In `search` mode, also pull every hotel's full quote table. Costs one extra request per hotel. Default `false`. |
| `hotelId` | string | A hotel ID, or the `google.com/travel/hotels/entity/<id>` URL it came from. |
| `hotelIds` | array | Several hotels in one run. |
| `languageCode` | string | Language of labels such as "Free cancellation". Default `en`. |
| `countryCode` | string | Influences which booking sites are listed. Default `us`. |
| `maxItems` | integer | Maximum records per run. Default 200. |
| `maxConcurrency` | integer | Hotels fetched in parallel. Default 3. |
| `proxyConfiguration` | object | Residential proxy, on by default. **The proxy country also decides the currency** — see Limits. |

### Output

Real output from `mode: "hotel"` (quote list truncated after 2 of 17):

```json
{
  "entityId": "ChUI17vV0u_fprYCGgkvbS8wZGQ5MDMQAQ",
  "name": "Marina Bay Sands Singapore",
  "hotelUrl": "https://www.google.com/travel/hotels/entity/ChUI17vV0u_fprYCGgkvbS8wZGQ5MDMQAQ",
  "address": "10 Bayfront Ave, Singapore 018956",
  "phone": "+65 6688 8868",
  "rating": 4.7,
  "reviewCount": 64684,
  "starClass": 5,
  "stayWindow": "Sep 27 – 28",
  "currency": "IDR",
  "rateCount": 17,
  "lowestRate": 21583872.0,
  "highestRate": 24962420.0,
  "rateSpread": 3378548.0,
  "cheapestProvider": "Expedia.co.id",
  "rates": [
    {
      "provider": "Marina Bay Sands",
      "providerHost": "linkcenterus.derbysoftca.com",
      "partnerId": "121",
      "isOrganic": true,
      "priceRaw": "IDR 21,691,972",
      "price": 21691972.0,
      "currency": "IDR",
      "rateType": "Total per night",
      "includesTaxes": true,
      "freeCancellation": true,
      "bookingUrl": "https://linkcenterus.derbysoftca.com/dplatform-linkcenter/booking.htm?hotelCode=SANDS-74224&checkInDate=2026-09-27..."
    },
    {
      "provider": "Expedia.co.id",
      "providerHost": "www.expedia.co.id",
      "partnerId": "89",
      "isOrganic": true,
      "priceRaw": "IDR 21,583,872",
      "price": 21583872.0,
      "currency": "IDR",
      "rateType": "Total per night",
      "includesTaxes": true,
      "freeCancellation": true,
      "bookingUrl": "https://www.expedia.co.id/Hotel-Search?selected=3231105&startDate=2026-09-27..."
    }
    // ... 15 more
  ],
  "recordType": "HOTEL",
  "_input": "ChUI17vV0u_fprYCGgkvbS8wZGQ5MDMQAQ",
  "_source": "travel-entity-prices-ssr",
  "_scrapedAt": "2026-09-18T04:21:11Z"
}
```

Real output from `mode: "search"`:

```json
{
  "entityId": "ChkIh46ls6ORhMkNGg0vZy8xMWZsZ3c1MnByEAE",
  "name": "ORIENTAL HOTEL KYOTO ROKUJO",
  "hotelUrl": "https://www.google.com/travel/hotels/entity/ChkIh46ls6ORhMkNGg0vZy8xMWZsZ3c1MnByEAE",
  "searchQuery": "hotels in Kyoto",
  "priceRaw": "IDR 1,018,048",
  "currency": "IDR",
  "price": 1018048.0,
  "priceIsTotal": true,
  "priceIncludesTaxes": true,
  "nights": 1,
  "stayWindow": "Sep 27 – 28",
  "dealFlag": null,
  "dealPercent": null,
  "rating": 4.2,
  "reviewCount": 1086,
  "starClass": 3,
  "propertyType": null,
  "amenities": ["Free Wi-Fi", "Parking", "Restaurant", "Accessible"],
  "reviewThemes": ["Property", "Breakfast", "Service"],
  "_source": "travel-search-ssr",
  "_input": "hotels in Kyoto",
  "_scrapedAt": "2026-09-18T05:08:49Z",
  "recordType": "HOTEL"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `entityId` | string | Google's ID for the hotel. Stable day to day, which is what makes rate tracking possible. |
| `name` / `address` / `phone` | string | Hotel identity, read from the hotel's own header block. |
| `rating` / `reviewCount` / `starClass` | number | Guest rating out of 5, number of reviews, official star class. |
| `priceRaw` / `price` / `currency` | string / number | Google's headline price, as published and as a number. |
| `priceIsTotal` / `priceIncludesTaxes` / `nights` | boolean / integer | What the price actually covers. |
| `stayWindow` | string | The dates the price applies to. See Limits. |
| `dealFlag` / `dealPercent` | string / integer | Google's verdict: "GREAT PRICE" or "18% less than usual". |
| `amenities` / `reviewThemes` | array | Amenity list, and Google's "People often mention" themes. |
| `rateCount` | integer | How many booking sites quoted this hotel. |
| `lowestRate` / `highestRate` / `rateSpread` | number | Cheapest, dearest, and the difference — the rate-parity figure. |
| `cheapestProvider` | string | Which booking site holds the lowest quote. |
| `rates` | array | Every quote: `provider`, `providerHost`, `price`, `rateType`, `includesTaxes`, `freeCancellation`, `bookingUrl`. |
| `recordType` | string | `HOTEL`, `RATE`, `SPONSORED`, `SURFACE` or `ERROR`. |
| `error` / `errorDetail` | string | Present only on failure rows, so a problem is never a silently missing hotel. |

### Limits worth knowing before you run it

These are properties of the source, not of the actor, and each one is reported in the data
rather than hidden:

- **The currency follows your proxy country, and cannot be requested.** Google ignores every
  currency parameter on these pages. Choose a proxy country matching the currency you want;
  every record tells you the currency it actually came back in.
- **The stay window is Google's default (tonight, one night) and cannot be chosen.** Every
  record carries `stayWindow` so you always know which dates a price refers to. For a rate
  history, run the actor on a daily schedule — that is what this data shape is for.
- **A search returns about 21 hotels and has no further pages.** To cover a city, run several
  narrower queries (by district or by hotel name) rather than expecting deeper pagination.
- **Not every hotel has many quotes.** A large property returns 17–25; a small one may return
  one or none, in which case you get an explicit record saying so.
- **Paid placements are kept separate.** The ads on a hotel's page are quotes for *other*
  hotels, so they only ever appear under `mode: "sponsored"` and never inside `rates` —
  mixing them in would quietly corrupt any parity analysis.

# Actor input Schema

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

search = find hotels for a query and get their cards (price, rating, star class, deal flag). hotel = full detail for specific hotels, with every OTA quote nested under 'rates'. rates = the same quotes flattened to one record each, which is the shape you want for rate-parity analysis. sponsored = the paid placements shown on a hotel's page, which are quotes for OTHER hotels. reference = check the source and report what it returns, including the winning TLS profile and the currency your proxy produces.

## `searchQuery` (type: `string`):

A destination or hotel name, e.g. 'hotels in Singapore' or 'hotels near Ubud Bali'. Google returns about 21 hotels per query and offers no deeper pages, so use several narrower queries to cover a city rather than expecting pagination.

## `searchQueries` (type: `array`):

Several queries in one run. One request each.

## `fetchRates` (type: `boolean`):

Search cards already carry Google's own price, rating, star class and deal flag. Turn this on to also pull each hotel's full cross-OTA rate table - one extra request per hotel, so roughly 21x the requests of a bare search.

## `hotelId` (type: `string`):

A Google Hotels entity ID, or the https://www.google.com/travel/hotels/entity/<id> URL it came from. Run mode 'search' first to collect IDs.

## `hotelIds` (type: `array`):

Several hotels in one run. One request each.

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

Google's 'hl' parameter. Controls the language of labels such as 'Free cancellation' and '5-star hotel'. It does NOT control the currency.

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

Google's 'gl' parameter. Influences which OTAs Google lists. It does NOT control the currency - see the proxy note.

## `maxItems` (type: `integer`):

Maximum records per run. A search query yields about 21 hotels; a single hotel yields up to about 25 OTA quotes in mode 'rates'.

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

How many hotel pages to fetch in parallel. Google tolerates far less parallelism than a typical site; 3 is a deliberate default.

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

Residential proxy is on by default and is effectively required: Google gates on the exit IP as well as the TLS fingerprint, and datacenter addresses get challenges instead of pages. The proxy country also decides the CURRENCY of every price, because Google ignores 'curr', 'currency' and 'gl' on this surface - so pick a proxy country matching the currency you want to report in.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "hotels in Singapore",
  "searchQueries": [
    "hotels in Singapore",
    "hotels in Bali"
  ],
  "fetchRates": false,
  "languageCode": "en",
  "countryCode": "us",
  "maxItems": 200,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All hotel, rate, sponsored, reference and error records produced by 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 = {
    "searchQuery": "hotels in Singapore",
    "searchQueries": [
        "hotels in Singapore",
        "hotels in Bali"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/google-hotels-rate-comparison-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 = {
    "searchQuery": "hotels in Singapore",
    "searchQueries": [
        "hotels in Singapore",
        "hotels in Bali",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/google-hotels-rate-comparison-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 '{
  "searchQuery": "hotels in Singapore",
  "searchQueries": [
    "hotels in Singapore",
    "hotels in Bali"
  ]
}' |
apify call fanndev/google-hotels-rate-comparison-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/google-hotels-rate-comparison-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/f2jb9GgzbukwXpvgN/builds/pEmYikQo2HA3VJCeB/openapi.json
