# Booking.com Hotels Scraper (`muhammad4hmed/booking-hotels-scraper`) Actor

Scrape Booking.com hotel search results with live prices in a currency you choose, guest review scores, star ratings, addresses and distance. Filter-slicing returns ~180 hotels per destination where a naive scraper gets 25.

- **URL**: https://apify.com/muhammad4hmed/booking-hotels-scraper.md
- **Developed by:** [Muhammad Ahmed](https://apify.com/muhammad4hmed) (community)
- **Categories:** Travel, E-commerce
- **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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Booking.com Hotels Scraper

Scrape Booking.com hotel search results — **live prices in a currency you pin**, guest review scores, star ratings, addresses and distance from the centre — for any destination, date range and occupancy.

### Why this one goes deeper

Booking hands a headless browser **25 properties per search** and offers no way to page further: no "Load more" button, no pagination links, and the `offset` parameter is ignored outright (offsets 0, 25 and 50 all return the same first property — verified).

This Actor gets past that by **slicing each search into disjoint filtered queries** — Booking's own ranking first, then five star classes, then five price bands — each of which returns its own 25. Duplicates are removed by hotel ID across slices.

Measured on Paris, one date range: **180 unique hotels in 51 seconds**, every one with a price. Against 25 for a naive scraper.

Slicing pays a second dividend: a hotel found in the `4-star` slice *is* 4-star. That matters more than it sounds — Booking omits star markup from its cards entirely when the request comes from a datacenter IP, so the class filters are the only place a star rating comes from on the default proxy. Star ratings are taken from the slice that establishes them, and are `null` — never guessed — where nothing does.

### What you get

- **Prices in a fixed currency.** Booking prices by the requesting IP's location; on a datacenter host that means results arrive in PKR or similar. This Actor pins `selected_currency`, so USD means USD wherever the run executes.
- **Guest scores and review counts** parsed to numbers (`8.1`, `1621`), not left as `"Scored 8.1"`.
- **Address and distance** — `"14th arr., Paris"`, `"3.7 km from downtown"`.
- **Full occupancy control** — check-in/out, adults, children, rooms, sort order, language.
- Requires a real browser; plain HTTP gets an HTTP 202 challenge.

### Input

| Field | Type | Description |
|---|---|---|
| `destinations` | array | City, region or property name as typed into Booking's search box |
| `checkIn` / `checkOut` | string | `YYYY-MM-DD`; defaults to a 2-night stay 30 days out |
| `adults` / `children` / `rooms` | integer | Occupancy — changes which rooms and prices appear |
| `currency` | string | Three-letter code. **Pin this** or prices follow the server's location |
| `language` | string | Booking locale, e.g. `en-us`, `de` |
| `sortBy` | string | Booking's sort key, or empty for its default ranking |
| `maxResultsPerDestination` | integer | Up to ~180 per destination via slicing (default `100`) |
| `maxItems` | integer | Total cap (`0` = no limit) |
| `proxyConfiguration` | object | **Required** — datacenter proxy by default |

#### Example

```json
{
  "destinations": ["Paris", "Tokyo"],
  "checkIn": "2026-10-01",
  "checkOut": "2026-10-03",
  "adults": 2,
  "currency": "USD",
  "maxResultsPerDestination": 150
}
```

### Output

```json
{
  "name": "Montparnasse Alésia",
  "url": "https://www.booking.com/hotel/fr/montparnasse-alesia.html",
  "hotelId": "montparnasse-alesia",
  "price": 440,
  "priceText": "US$440",
  "currency": "USD",
  "reviewScore": 8.1,
  "reviewCount": 1621,
  "stars": 3,
  "address": "14th arr., Paris",
  "distanceFromCentre": "3.7 km from downtown",
  "image": "https://cf.bstatic.com/xdata/images/hotel/…",
  "destination": "Paris",
  "checkIn": "2026-10-01",
  "checkOut": "2026-10-03"
}
```

A destination that returns nothing produces an item with `error` rather than vanishing.

### Notes and limits

- **`price` is the total for your date range**, as Booking displays it, not a per-night rate.
- **Star ratings are filled for most, not all, results** — about 43 of 60 on a typical run. After the requested number of hotels is reached the Actor keeps running the star-class filters purely to fill this column in, but each filter itself only returns 25 hotels, so a property ranked deep inside its class can go unmatched. Anything the filters do not establish stays `null` rather than being guessed from card markup, and self-catering places genuinely have no star rating at all.
- **Coverage is deep, not exhaustive.** Slicing reaches ~180 hotels per destination and date range, not every property in a city. Scrape more date ranges or narrower destinations for wider coverage.
- **Availability and price depend on your dates and occupancy**, so two runs with different inputs are not comparable — that is Booking's behaviour, not the Actor's.
- **A proxy is required and enabled by default.** Booking returns nothing to the bare container IP. Apify's datacenter proxy works and is the default. Residential costs roughly 9x more and is worth it for one reason only: Booking sends star markup to residential IPs, so every row gets a star rating directly instead of relying on the class filters.
- **Images are not downloaded.** Hotel photo URLs are still returned, but the files themselves are blocked — they are the bulk of the page weight and none of the data.

### Local development

```bash
npm install
npx playwright install chromium
npm run build
CHROME_PATH=/path/to/chrome node dist/main.js
```

`CHROME_PATH` is optional — it lets a self-hosted run reuse an existing Chromium. On Apify the Playwright image supplies the browser.

# Actor input Schema

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

City, region or property name as you would type it into Booking's search box, e.g. `Paris` or `Manhattan, New York`.

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

YYYY-MM-DD. Defaults to 30 days from the run date.

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

YYYY-MM-DD, must be after check-in. Defaults to a 2-night stay.

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

Number of adult guests; affects which rooms and prices are shown.

## `children` (type: `integer`):

Number of children in the party.

## `rooms` (type: `integer`):

Number of rooms required.

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

Three-letter code such as USD, EUR or GBP. Without this Booking prices in whatever currency it infers from the server's location, which makes runs incomparable.

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

Booking locale code, e.g. `en-us`, `de`, `fr`.

## `sortBy` (type: `string`):

Booking sort key. Leave empty for its default relevance ranking.

## `maxResultsPerDestination` (type: `integer`):

Booking pages 25 at a time; this caps how deep to go.

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

Stop after this many hotels in total. 0 means no limit.

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

Required. Booking blocks the bare container IP, so runs without a proxy return nothing. Apify's datacenter proxy (the default) works and is roughly 9x cheaper than residential; switch to RESIDENTIAL only if datacenter starts getting blocked.

## Actor input object example

```json
{
  "destinations": [
    "Paris"
  ],
  "adults": 2,
  "children": 0,
  "rooms": 1,
  "currency": "USD",
  "language": "en-us",
  "sortBy": "",
  "maxResultsPerDestination": 100,
  "maxItems": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped hotels.

# 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 = {
    "destinations": [
        "Paris"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("muhammad4hmed/booking-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 = { "destinations": ["Paris"] }

# Run the Actor and wait for it to finish
run = client.actor("muhammad4hmed/booking-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 '{
  "destinations": [
    "Paris"
  ]
}' |
apify call muhammad4hmed/booking-hotels-scraper --silent --output-dataset

```

## MCP server setup

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