# Google Hotels Room Offers Scraper (`monkey.d.scraping/google-hotels-room-offers`) Actor

Extract Google Hotels room offers in EUR with providers, stay totals, cancellation and breakfast labels. Compare two snapshots for observed price changes and export JSON/CSV. Simple hotel-name or URL input; partial coverage, no AI key required.

- **URL**: https://apify.com/monkey.d.scraping/google-hotels-room-offers.md
- **Developed by:** [Monkey D. Scraping](https://apify.com/monkey.d.scraping) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.05 / 1,000 observed room offers

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 Room Offers Scraper

Extract observed **Google Hotels room offers** with room names, booking providers, displayed nightly prices, stay totals, cancellation terms and breakfast labels. Export the results as JSON, CSV or Excel from Apify. No Google account, AI key or OpenRouter subscription is required.

**Price:** $0.003 per start + $0.05 per 1,000 returned room offers. Snapshot comparison and changes CSV have no extra result event. Start fees apply even when the source returns no usable offers.

### What you get

Each result is a room offer observed for your requested hotel, dates and adults. The Actor preserves the source text so you can check the evidence behind each condition. It does not infer amenities or calculate a stay total from rounded nightly prices.

Use it to collect booking-offer snapshots, inspect explicitly refundable offers, or feed your own hotel research workflow. Compare two runs by selecting a previous run’s snapshot storage or supplying exported JSON. The Actor does not maintain an automatic historical database or create schedules.

### Quick start

1. Enter a hotel name and city, such as **Hotel Berna Milano**, or paste a full Google Hotels property URL into the same list. Exact property links avoid ambiguous name searches.
2. Choose both check-in and check-out dates using the calendars. Leave both blank for a two-night stay starting 30 days from the run's UTC date.
3. Set the number of adults and run the Actor. Open **Room offers** for results and **Collection status** for counts or source errors.

```json
{"hotels":["Hotel Berna Milano"],"adults":2,"maxOffersPerHotel":20}
```

The collector requests **EUR, English labels, one room and an Italian market hint**. Google can still vary providers by execution location or session: the market hint does not guarantee Italian-market inventory. Children and multiple rooms are unsupported. Up to 10 hotels and 200 returned offers per hotel; stays of 1–28 nights with check-in in the next 330 days.

### Exact hotel links

For a known property, use its full Google Hotels URL containing `/travel/hotels/entity/`. Names and links can be mixed in the same list. Links are normalized and duplicate links to the same property are removed before collection. Only HTTPS `google.com` and `www.google.com` property URLs are accepted; shortened, Maps and external links are rejected.

```json
{"hotels":["https://www.google.com/travel/hotels/entity/ChcIrMPXnMPLhPV0GgsvZy8xdGQxdGIwNhAB"],"adults":2,"maxOffersPerHotel":20}
```

Dates, currency and guest settings embedded in pasted URLs are discarded. The form controls the requested stay. Before returning offers for an exact link, the Actor checks the source page's canonical property ID. Missing or different IDs cause an explicit error. This confirms property identity, not complete offer coverage or improved availability.

### Compare two observations

**Quick repeat:** after a complete collection where every hotel returns offers without truncation, open **Ready-to-use input for next comparison**. Copy its JSON into the next run’s **JSON** input tab and start. It already includes the exact stay, filters, hotels and baseline reference. This shortcut is not produced for empty, failed or truncated collections: review their coverage before choosing a baseline. Past stay dates still fail validation.

1. Run normally. Its key-value store contains **Snapshot for next comparison** (`SNAPSHOT`). Find its storage ID in the run’s Storage section.
2. Start a later run with the **same explicit check-in/check-out dates**, adults, filters and offer limit. Relative default dates change across days, so use the dates recorded in the snapshot.
3. In **Compare with an earlier run**, select that key-value store using **Previous run storage**. Alternatively, export and paste the SNAPSHOT JSON. Do not fill both options. Open **Observed changes** (`CHANGES`) after completion.

Download **Changes for Excel (CSV)** for a spreadsheet with one row per comparison event: before/after prices, numeric differences, stay context, source links and evidence. No events means a header-only CSV; check CHANGES JSON for baseline/unchanged counts. UTF-8 with BOM preserves accented names in Excel. Potential formula-like source text is prefixed with an apostrophe in CSV; JSON retains the original text. Numeric price differences remain numbers.

The normal dataset still contains current room offers only. Snapshot and comparison JSON are separate key-value records; no extra comparison event is billed. No AI key or second account is required. The storage picker requests READ access only to the selected storage; the Actor remains on limited permissions and never modifies the selected baseline. If storage has expired or is inaccessible, use an exported snapshot.

Comparison uses exact hotel ID, provider, room label, stay, currency, cancellation and breakfast terms. A unique match on each side can produce `observed_price_changed`, with before/after prices, difference and source evidence. Multiple matches are `ambiguous` and are not compared automatically. Even exact labels do not prove equivalence of the underlying bookable product.

`first_observed_in_comparison` means absent from the supplied sample, not newly launched. `not_observed_in_current_sample` means not seen this time, not removed or sold out. Changes in conditions create separate observations rather than an unsupported claim that one tariff changed. Review collection errors and truncation. Empty runs do not overwrite your supplied baseline; the output recommends keeping it for review. The supplied snapshot is not independently authenticated.

This is a user-selected two-snapshot comparison, not automatic latest-run discovery or continuous monitoring. Up to 2,000 baseline offers and a 4 MB snapshot are supported. Different stay settings or malformed snapshots fail before collection.

### Optional filters

- **Only free cancellation:** retain an explicit free-cancellation label. A deadline is returned only when displayed.
- **Only breakfast included:** retain an explicit included/free breakfast label. A room name mentioning breakfast or “breakfast available” is insufficient.
- **Maximum offers per hotel:** limit matching results, ordered by the displayed stay total. A lower limit does not reduce the single source request per hotel or guarantee the cheapest offer in the market.

Unknown conditions do not pass filters. If both filters are enabled, both must be satisfied by the same offer.

### Output fields

| Field | Meaning |
|---|---|
| `hotelName`, `hotelId` | Property resolved by Google. Check these for similarly named hotels. |
| `provider`, `roomName` | Booking provider and room-offer name as displayed. |
| `checkIn`, `checkOut`, `adults` | Requested stay, confirmed against source controls before emitting results. |
| `currency` | EUR. Unsupported source prices produce an explicit error, not a false empty result. |
| `nightlyDisplayed` | Nightly price shown in the source offer. Do not assume tax treatment or multiply it to reconstruct the total. |
| `stayTotalDisplayed` | Source-displayed total for the stay; read independently of the nightly price. |
| `freeCancellation`, `breakfastIncluded` | `true` or `false` only with explicit evidence; otherwise `null`. Blank cells in exports can mean unknown. |
| `cancellationDeadlineDisplayed` | Source wording, without an invented year, cutoff time or time zone. |
| `evidence` | Displayed offer text, including prices and conditions. |
| `sourceUrl`, `observedAt` | Google Hotels query for the stay and observation time in UTC. Reopening the URL may show different offers. |
| `offerId` | Hash of this offer snapshot. A price or condition change creates a different ID; not a permanent room identifier. |
| `coverage`, `taxTreatment` | Explicit limitations on completeness and tax verification. |

The default dataset contains deduplicated observed offers. Identical offers are deduplicated within each hotel lookup. Room names are not matched across providers and different rooms must not be treated as equivalent.

### Empty results and errors

`SUMMARY` in the default key-value store records every hotel's resolution, observed/matching/returned counts, truncation, collection time and errors.

- `offers_observed`: at least one offer returned.
- `no_matching_observed_offers`: offers were observed but none satisfied the filters.
- `no_detailed_offers_observed`: no parseable detailed offers were exposed in this response. **This does not mean sold out.**
- `error`: the request, property resolution, stay confirmation or price parsing failed. An unresolved hotel includes `resolvedNamesToVerify` where available; verify the property before using a suggested name.

A run fails when all hotel lookups fail. A mix of successful and failed hotels is marked `partial` in the summary. Storage failures fail the run. Resurrecting a run with existing dataset rows is rejected to prevent duplicate results and charges; start a fresh run instead. `complete` is always false because the Actor cannot establish complete market inventory.

### Coverage and price limitations

Google exposes a partial, rotating selection of detailed offers, often from featured booking partners. Identical requests can return different providers and offer counts. This Actor does not provide complete inventory, certified rate parity, guaranteed lowest prices or final checkout prices. Taxes and fees are not independently verified.

The HTML and encoded stay parameters are undocumented and may change. Property name matching is a guard against obvious mismatches, not a guarantee against similarly named properties. Use an exact Google Hotels property link where possible. Name searches still require checking the resolved property before relying on the output.

No reservations, checkout, AI analysis, automatic schedules, external change notifications or automatic historical database are included.

### Pricing and reliability

The price is **$0.003 per start + $0.05 per 1,000 returned offers** ($0.00005 each), with no per-hotel or filter event. A run returning 54 offers has an event price of $0.0057. No claim is made to be the cheapest alternative: some products charge per hotel and include provider offers.

Set **Maximum cost per run** in Apify's run options, not the input JSON. The minimum is $0.00305 (start plus one offer). The Actor computes its row allowance before fetching, truncates results to the affordable count, and skips remaining hotels when exhausted. `SUMMARY.stopReason` and per-hotel statuses distinguish budget/time stops. The collection stops starting new hotel lookups after 85 seconds; individual source requests have a 15-second timeout. A response that does not confirm the requested stay may be retried once before any offers are saved, if enough time remains. Persistent mismatch fails that lookup; SUMMARY.sourceAttempts is recorded per hotel.

Source coverage varies across runs and execution locations. In development checks, a property with zero detailed offers later exposed room offers, while other properties continued to expose no detailed rooms. This is why absent observations are never labelled sold out or removed. Check Collection status alongside your results.

### Frequently asked questions

**Does this monitor prices automatically?** No. Run a new observation and select a previous snapshot to compare. No schedules or notifications are created for you.

**Can I compare refundable and non-refundable rooms?** They are separate observed conditions. Price-change events require matching provider, room label and terms; ambiguous variants are not paired automatically.

**Does a missing offer mean sold out?** No. Google may expose a different subset or no detailed room cards. The output reports observations, not complete inventory.

**Which markets and currencies are supported?** EUR, English source labels, adults in one room, with an Italian market hint. The hint does not guarantee local inventory. Children, multiple rooms and other currencies are unsupported.

**Is this affiliated with Google?** No. This is an independent data extraction tool, not an official Google Hotels API.

### Related tools

- [Website SEO Audit & Contact Scraper](https://apify.com/monkey.d.scraping/website-opportunity-auditor): inspect public hotel or agency websites for SEO gaps and contact details.
- [Google Shopping Price Tracker & Seller Monitor](https://apify.com/monkey.d.scraping/google-shopping-price-change-seller-monitor): track product prices and sellers when your research concerns retail rather than accommodation.

# Actor input Schema

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

One hotel name with city or full https://www.google.com/travel/hotels/entity/... link per line, up to 10. Exact links avoid name ambiguity; dates and guests come from this form, not the pasted link. Shortened links are not supported.

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

YYYY-MM-DD. Leave both dates blank for check-in 30 days from the run date.

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

YYYY-MM-DD. With both dates blank, the stay lasts two nights.

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

Adults sharing one room. Children and multiple rooms are not supported.

## `onlyFreeCancellation` (type: `boolean`):

Keep only offers whose source explicitly states free cancellation. Unknown conditions are excluded.

## `onlyBreakfastIncluded` (type: `boolean`):

Keep only an explicit included/free breakfast label. Available breakfast or a room name mentioning breakfast does not qualify.

## `maxOffersPerHotel` (type: `integer`):

Return up to this many observed matching offers, sorted by displayed stay total. Collection is not a complete market inventory.

## `previousSnapshotStoreId` (type: `string`):

Select the key-value store containing SNAPSHOT from an earlier run of this Actor (0.1.10+). Read-only access; the baseline is never overwritten. Use the same explicit dates, adults, filters and offer limit.

## `previousSnapshot` (type: `object`):

Alternative to selecting previous storage. Do not fill both. Same dates, adults, filters and limit are required.

## Actor input object example

```json
{
  "hotels": [
    "Hotel Berna Milano"
  ],
  "adults": 2,
  "onlyFreeCancellation": false,
  "onlyBreakfastIncluded": false,
  "maxOffersPerHotel": 100
}
```

# Actor output Schema

## `offers` (type: `string`):

One unique observed room offer per row, with provider, displayed prices, nullable conditions and source evidence.

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

Per-hotel counts, resolution, errors and truncation. No observed offers does not mean sold out.

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

Conservative comparison against an optional supplied snapshot. Missing observations are not removed offers.

## `snapshot` (type: `string`):

Export this JSON to compare a later run for the same explicit dates and settings. Review coverage before replacing your baseline.

## `changesCsv` (type: `string`):

One row per comparison event, with before/after prices and evidence. Header-only when there are no events. Missing observations are not removals.

## `nextComparisonInput` (type: `string`):

Copy this JSON into the next run input JSON tab. Includes these exact dates, settings and baseline reference. Available only when all hotels return offers without truncation; review Collection status otherwise.

# 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 Berna Milano"
    ],
    "adults": 2,
    "onlyFreeCancellation": false,
    "onlyBreakfastIncluded": false,
    "maxOffersPerHotel": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("monkey.d.scraping/google-hotels-room-offers").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 Berna Milano"],
    "adults": 2,
    "onlyFreeCancellation": False,
    "onlyBreakfastIncluded": False,
    "maxOffersPerHotel": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("monkey.d.scraping/google-hotels-room-offers").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 Berna Milano"
  ],
  "adults": 2,
  "onlyFreeCancellation": false,
  "onlyBreakfastIncluded": false,
  "maxOffersPerHotel": 100
}' |
apify call monkey.d.scraping/google-hotels-room-offers --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,monkey.d.scraping/google-hotels-room-offers"
        }
    }
}
```

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/nnnwbNnuUv9qUEA8S/builds/n5Vx90fHyMhzHQeVR/openapi.json
