# Google Hotels Scraper (`mlg14/google-hotels-scraper`) Actor

Find public hotel listings by destination and stay dates, with nightly and total prices, ratings, amenities, photos, nearby places, and property links.

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

## Pricing

$1.00 / 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.

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

Scrape Google Hotels listings for cities, neighborhoods, and landmarks with stay dates, nightly prices, total stay prices, guest ratings, amenities, photos, and nearby places. Export Google Hotels data to CSV, JSON, or a spreadsheet for accommodation research, rate tracking, and destination comparisons; this actor provides a structured alternative to manually reviewing Hotel Search pages.

### What data can you extract from Google Hotels?

Each dataset item describes one property found for one input location and stay. The search is public and does not require a guest account. A field can be empty when the listing does not show it.

| Field | Description | Example |
|---|---|---|
| `hotelId` | Public property identifier used for deduplication | `0x47e66e67ab78db57:0x4012cab02f7b7804` |
| `hotelName` | Name displayed on the listing | `Glint Paris` |
| `hotelUrl` | Link to the property's Hotel Search detail page | `https://www.google.com/travel/hotels/entity/...` |
| `location` | Input place that produced the result | `Paris` |
| `searchUrl` | Search page used to retrieve the result | `https://www.google.com/travel/search?...` |
| `propertyType` | Property category when identified in the listing | `hotel` |
| `hotelDescription` | Short description shown by the search page | `Modern rooms & suites...` |
| `gpsLatitude` | Property latitude | `48.8886187` |
| `gpsLongitude` | Property longitude | `2.3498362` |
| `hotelClass` | Displayed classification, if present | `4-star tourist hotel` |
| `starRating` | Numeric hotel classification, if present | `4` |
| `overallRating` | Public guest rating out of five | `4.5` |
| `reviewCount` | Count of public guest reviews | `3184` |
| `checkInTime` | Displayed arrival time | `3:00 PM` |
| `checkOutTime` | Displayed departure time | `11:00 AM` |
| `pricePerNight` | Numeric nightly price in `currency` | `66.27` |
| `pricePerNightDisplay` | Rounded nightly price as displayed | `$66` |
| `totalRate` | Numeric full-stay amount when shown | `157.65` |
| `totalRateDisplay` | Rounded full-stay price as displayed | `$158` |
| `currency` | Currency actually returned with the listing | `USD` |
| `amenityCodes` | Codes for amenities marked as present | `[29, 2, 18]` |
| `images` | Public property photo URLs, up to ten | `["https://lh3.googleusercontent.com/..."]` |
| `ratingsBreakdown` | Review distribution by star level | `[{"stars": 5, "percent": 71, "count": 2228}]` |
| `reviewsBreakdown` | Mention counts and sentiment by topic | `[{"name": "Service", "sentiment": 0.76}]` |
| `nearbyPlaces` | Named places and displayed travel times | `[{"name": "Château Rouge", "transportations": [...]}]` |
| `detailToken` | Public property route token | `ChcIhPDt-4LWsolAGgsvZy8xdjZsNnQ2bRAB` |
| `bookingSources` | Reserved for date-matched vendor quotes; empty in this version | `[]` |
| `checkInDate` | Arrival date actually applied to the result | `2026-11-12` |
| `checkOutDate` | Departure date actually applied to the result | `2026-11-14` |
| `scrapedAt` | UTC collection timestamp | `2026-09-27T07:51:56+00:00` |

The numeric and display price fields serve different purposes. The numeric amount can contain cents even when the page rounds the visible label to a whole unit. The total covers the selected stay when the page provides a total, while the nightly figure is a per-night amount. Use `currency` from each record when combining prices; a requested currency can be ignored by the search page.

The review distributions describe aggregated public ratings, not individual guest comments. Each `ratingsBreakdown` entry gives stars, percentage, and count. Each `reviewsBreakdown` entry gives a topic name, a sentiment value, the number of mentions, and positive and negative mention counts. The topic counts do not have to sum to the property's full review count.

### How to scrape Google Hotels

1. Enter one or more locations, such as a city or neighborhood.
2. Set check-in and check-out dates in `YYYY-MM-DD` format. The departure date must be later than the arrival date.
3. Choose a maximum number of results per location and, if useful, a total limit across all locations.
4. Run the actor. It verifies that each listing's applied stay dates match your request before saving prices.
5. Open the dataset to inspect property rows or download CSV, JSON, or spreadsheet output.

A location is a search phrase, not a geographic boundary. A search for a city can include nearby suburbs or airport properties if they appear in the public results. Use a more specific neighborhood or landmark when you need a narrower area. The `location` column keeps the original search term so rows from several searches remain distinguishable after export.

For recurring price checks, use the same locations, dates, language, and region each time. Compare records by `hotelId`, then by `checkInDate`, `checkOutDate`, and `currency`. Hotel prices change frequently, and some properties do not show an available rate for every stay.

### Input

| Parameter | Type | Default | Description |
|---|---|---|---|
| `locations` | string array | Required | Places to search; each entry produces a separate search. |
| `checkInDate` | string | Required | Requested arrival date in `YYYY-MM-DD` format. |
| `checkOutDate` | string | Required | Requested departure date in `YYYY-MM-DD` format. |
| `adults` | integer | `2` | Currently fixed at two adults because the public page did not apply other guest counts reliably. |
| `currency` | string | `USD` | Requested currency code. Check the returned `currency` field before comparing prices. |
| `languageCode` | string | `en` | Interface language for the search. |
| `countryCode` | string | `us` | Regional context for the search results. |
| `maxResults` | integer | `20` | Maximum unique hotels to store for each location, up to 1,000 requested. Availability of new results is controlled by the site. |
| `maxItems` | integer | `0` | Overall dataset cap; zero means no overall cap. |
| `proxyConfiguration` | object | Enabled | Connection settings for retrieving the public pages. |

Example for two cities:

```json
{
  "locations": ["Paris", "Tokyo"],
  "checkInDate": "2026-11-12",
  "checkOutDate": "2026-11-14",
  "adults": 2,
  "currency": "USD",
  "languageCode": "en",
  "countryCode": "us",
  "maxResults": 20,
  "maxItems": 40
}
```

The date range is an essential part of the search. A search page can silently fall back to its own default dates; this actor checks the dates in the structured listing data and discards the location when they do not match. A successful result therefore reports the dates used for its price rather than simply repeating the input.

`maxResults` applies independently to each location. For two locations at 20, the intended maximum is 40 rows. `maxItems` stops the entire run sooner if set below that sum. The limit is a ceiling, not a promise that a particular destination has that many public results.

### Output example

The following values come from a 2026-09-27 run for a two-night Paris stay. Long image URLs and array contents are shortened here; the dataset contains the full values.

```json
{
  "hotelId": "0x47e66e67ab78db57:0x4012cab02f7b7804",
  "hotelName": "Glint Paris",
  "hotelUrl": "https://www.google.com/travel/hotels/entity/ChcIhPDt-4LWsolAGgsvZy8xdjZsNnQ2bRAB?q=hotels+in+Paris+November+12+to+14+2026",
  "location": "Paris",
  "propertyType": "hotel",
  "gpsLatitude": 48.8886187,
  "gpsLongitude": 2.3498362,
  "overallRating": 4.5,
  "reviewCount": 3184,
  "pricePerNight": 66.27,
  "pricePerNightDisplay": "$66",
  "totalRate": 157.65,
  "totalRateDisplay": "$158",
  "currency": "USD",
  "amenityCodes": [165, 29, 2, 18, 4, 27, 7, 11, 25],
  "ratingsBreakdown": [
    {"stars": 5, "percent": 71, "count": 2228}
  ],
  "reviewsBreakdown": [
    {"name": "Service", "sentiment": 0.7564954, "totalMentioned": 252, "positive": 232, "negative": 17}
  ],
  "nearbyPlaces": [
    {"name": "Château Rouge", "transportations": [{"type": 2, "duration": "3 min"}]}
  ],
  "bookingSources": [],
  "checkInDate": "2026-11-12",
  "checkOutDate": "2026-11-14",
  "scrapedAt": "2026-09-27T07:51:56.122755+00:00"
}
```

This is a listing snapshot, not a reservation or a guarantee that a room can still be booked at that rate. Visit the property link to verify the current offer and any conditions before making a decision. A total can be absent when the listing does not provide one.

### Use cases

- **Destination research:** Collect hotel names, locations, ratings, and classes for several candidate cities, then compare the available mix of properties.
- **Rate monitoring:** Schedule the same stay search repeatedly and compare nightly and total rates for matching `hotelId` values over time.
- **Market mapping:** Use coordinates and `nearbyPlaces` to see where listed hotels cluster relative to stations, landmarks, or airports.
- **Quality screening:** Filter exported rows by rating, review count, and the distribution of star reviews before manual evaluation.
- **Accommodation catalogs:** Build a structured shortlist with descriptions, property links, image URLs, amenities, and stay-specific pricing.
- **Travel planning:** Compare the total displayed stay cost across locations, keeping dates and currency consistent so comparisons are meaningful.

### How much does it cost to scrape Google Hotels?

The configured result price is **$1 per 1,000 stored hotels**, or $0.001 per hotel. Platform usage is included in that result price. A run that stores 40 hotels costs approximately **$0.04**; 100 stored hotels cost approximately **$0.10**; 5,000 stored hotels across many runs cost approximately **$5.00**. Your actual bill follows the number of rows stored, subject to your account's run settings and budget limit.

A location may require more than one public search pass to reach its requested limit. Those passes do not create duplicate result charges: the actor deduplicates by the public property identifier and pushes each property at most once per location. A property found in two separately requested locations appears once for each location because each row retains its search context.

For a small recurring monitor, start with a few locations and a modest `maxResults`. Increase the limit only when the destination returns enough new properties. Repeating very broad searches may cost time without producing many additional unique rows.

### Tips for best results

Use recognizable place names. A city such as `Paris` or a distinct neighborhood is easier for the search page to interpret than an ambiguous abbreviation. If several places share a name, include a region or country in the location text. Use separate location entries when you want each area's output labeled independently.

Choose dates within the site's available booking horizon. The actor rejects an invalid date order, and it checks the dates in every retrieved listing. If the site substitutes a default stay, that location is skipped and the run log identifies the issue. This protects price comparisons from mixing different travel dates.

Keep `currency` consistent across repeated runs, then inspect the output currency. In observed searches, the page returned USD even when a different code was requested. The numeric price fields always represent the currency in the row, not an exchange conversion performed by this actor. Convert currencies separately only after confirming the source amounts.

Expect some nulls. A hotel may have a rating but no published class, a nightly price but no total, or an image with no description. Avoid treating a blank price as zero. The output contains keys consistently, making it straightforward to filter on available fields.

The actor retrieves datacenter pages first and changes connection route if a block occurs. For frequent monitoring, leave pauses between repeated runs and keep result limits proportionate to the question you are answering. Very large requested limits can stop early if public search passes cease to reveal new properties.

### Limits

The public listing response normally exposes roughly one screen of properties at a time. The actor makes additional public search passes for larger `maxResults` values and deduplicates overlaps. Those passes are not a guaranteed numbered pagination feed, so a requested 1,000 results can finish well below 1,000. The observed golden run retrieved 40 rows from two cities with a 20-row limit for each.

The current public page applied stay dates when they were included in the search text. Direct `checkin`, `checkout`, and `htichips` URL parameters did not apply the requested stay in observed probes. The actor verifies applied dates from the listing data rather than trusting the URL. Searches with unusual date phrases or dates beyond the site's supported window may yield no stored rows.

Guest count is limited to two adults. The page did not apply a three-adult request in the observed search. Alternate currency requests can also be ignored by the page; `currency` in each result reports the returned currency. Vendor-specific booking quotes are not included, so `bookingSources` is an empty array. Opening a property detail page without its original stay state showed default dates, which would make those quotes unsafe to attach to the requested stay.

There is no guarantee that every property shows a price, a hotel class, a description, or the same set of review topics. Rates, taxes, fees, and availability can change after collection. Results reflect the site's public search at the time of the run.

### Use with agents

The input and output fields are designed for structured requests. Example requests:

- “Find up to 20 hotels each in Paris and Tokyo for November 12–14, 2026. Return the hotel name, total rate, currency, rating, review count, and property URL.”
- “Compare two runs for the same stay by hotel ID. Show properties whose total rate changed, and keep rows with different currencies separate.”

When a request needs a strict price ceiling or a star threshold, filter the dataset after collection. This version does not claim to apply those filters inside the search page. Check `checkInDate` and `checkOutDate` alongside the price whenever you combine outputs from multiple runs.

### FAQ

#### Is this data public?

The actor reads publicly accessible hotel search listings. Use the data in line with the site's terms and applicable privacy and data-protection rules. Do not use any public information for personal-data misuse. The output contains aggregate review statistics, not individual reviewer profiles.

#### Do I need to configure a proxy?

The default connection setting is ready for ordinary runs. The actor starts with a datacenter route and can escalate after blocks. If a location fails, review the run log and try a smaller, focused search before increasing volume.

#### How fast is a run?

A small search depends on the site's response time and the number of public search passes needed to reach the result limit. The observed two-city golden run completed with 40 rows. Larger requests take longer and may stop before the requested ceiling when new unique properties are exhausted.

#### Can I schedule repeated price checks?

Yes. Use the same dates, locations, language, and region each time. Keep the returned currency with every snapshot, and join rows by `hotelId`. Save the `scrapedAt` value to distinguish collection times.

#### Can I export to a spreadsheet?

Yes. Download the dataset as CSV or a spreadsheet file, or use the dataset endpoint in an existing workflow. Arrays such as amenities and nearby places are best handled as structured JSON if you need their internal values.

#### Why is a field empty?

The listing may omit it, or the field may not apply to that property. For example, a property can have a guest rating without a formal hotel class. `bookingSources` remains empty because this version does not attach vendor quotes whose dates cannot be verified.

#### Why did I receive fewer hotels than `maxResults`?

The site may expose fewer unique listings for that query and stay. Additional passes can overlap heavily, and the actor stops after repeated passes without new properties. Try a broader location or several nearby areas if you need more coverage.

### Integrations

Use the dataset endpoint, webhooks, schedules, and exported files to move hotel rows into reporting, alerting, or spreadsheet workflows. The stable `hotelId`, location label, applied stay dates, actual currency, and collection time make repeat runs easier to compare. Keep the raw record when you need image lists, review distributions, or nearby-place travel times.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

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

Cities, neighborhoods, or landmarks to search; one hotel search is made for each location.

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

Arrival date in YYYY-MM-DD format. The stay dates are verified against each result.

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

Departure date in YYYY-MM-DD format, after check-in.

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

The public search currently returns prices for two adults only.

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

Currency code sent with the search, such as USD or EUR. The output currency records what the site actually returned; the site may ignore this setting.

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

Two-letter interface language for the search, such as en or fr.

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

Two-letter country code for regional search results, such as us or fr.

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

Maximum unique hotels to save for each location. Additional public search passes may find more; the site can stop returning new hotels before this limit.

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

Hard cap across all locations. Set to 0 for no total cap.

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

Proxy configuration for public Hotel Search pages. Datacenter is used first, with escalation on blocks.

## Actor input object example

```json
{
  "locations": [
    "Paris",
    "Tokyo"
  ],
  "checkInDate": "2026-11-12",
  "checkOutDate": "2026-11-14",
  "adults": 2,
  "currency": "USD",
  "languageCode": "en",
  "countryCode": "us",
  "maxResults": 20,
  "maxItems": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Hotel listings in the default dataset.

# 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": [
        "Paris",
        "Tokyo"
    ],
    "checkInDate": "2026-11-12",
    "checkOutDate": "2026-11-14"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/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 = {
    "locations": [
        "Paris",
        "Tokyo",
    ],
    "checkInDate": "2026-11-12",
    "checkOutDate": "2026-11-14",
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/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 '{
  "locations": [
    "Paris",
    "Tokyo"
  ],
  "checkInDate": "2026-11-12",
  "checkOutDate": "2026-11-14"
}' |
apify call mlg14/google-hotels-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mlg14/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/H4MOPgPdg9VBokRpr/builds/kZCfUsseZInlKwnU5/openapi.json
