# Booking.com Hotel Listings & Reviews Scraper (`devilscrapes/booking-hotels-scraper`) Actor

Scrape Booking.com hotel search results and detail pages by destination or direct URL: name, address, coordinates, star rating, review score, price, room type, and optional guest reviews. Camoufox-powered to clear the AWS WAF challenge. Pay only per hotel scraped.

- **URL**: https://apify.com/devilscrapes/booking-hotels-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 50.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/platform/actors/running/actors-in-store#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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Booking.com Hotel Listings & Reviews Scraper

**💰 $2.00 / 1 000 hotels**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Give this Actor a destination and dates — or a list of direct hotel URLs — and get back structured Booking.com hotel data: name, address, coordinates, star rating, review score, price, room type, and optional guest reviews.

</div>

***

### 🎯 What this scrapes

For each run, this Actor works in one of two modes:

1. **Search mode** — pass a `destination` (plus check-in/check-out dates), and it opens Booking.com's search results for that window, capped at your `maxHotelsPerSearch`.
2. **Direct-URL mode** — pass a list of `hotelUrls`, and it visits each hotel's own detail page directly, independent of a destination search.

For every hotel it visits, the Actor emits one dataset row with name, address, city, country, coordinates, star rating, review score, review count, price, currency, room type, and thumbnail. When `scrapeReviews` is enabled, it also attaches up to `maxReviewsPerHotel` best-effort guest reviews per hotel.

### 🔥 Features

- **We clear the JS-execution gate Booking.com puts in front of raw HTTP requests** — a hardened browser layer renders the page like a real visitor instead of bouncing off an automated bot-control challenge screen.
- **We rotate proxy sessions on every block** — a fresh exit IP and session id whenever a challenge or mismatch is detected.
- **We verify the currency and locale actually match what you asked for** before trusting a price — a wrong-geo page never gets emitted as if it were correct.
- **We back off when the target pushes back.** Partial successes surface with a clear status message — we never silently return an empty dataset.
- **Per-hotel fault isolation** — one dead page, timeout, or parse error skips that hotel with a logged warning; it never fails the whole run.
- **Pydantic v2 input + output validation** — every row is a typed, schema-enforced record; nullable fields are always `null`, never silently dropped.
- **Pay-Per-Event pricing** — you're charged per hotel row written to your dataset, plus a small per-review charge only when you opt into reviews.

### 💡 Use cases

- **OTA price intelligence** — track how a set of hotels prices a given date range across markets.
- **Revenue-management benchmarking** — hotel revenue managers pull comparable listings to sanity-check their own pricing and positioning.
- **Review-signal analysis** — pull guest review scores and text at scale for sentiment or competitive analysis.
- **Travel-agtech and itinerary tools** — bulk-source structured hotel data (name, coordinates, star rating, price) to power a booking or comparison product.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Enter a `destination` with `checkin`/`checkout` dates (search mode), or paste one or more `hotelUrls` (direct-URL mode) — or both.
3. Optionally enable `scrapeReviews` to pull guest reviews alongside each hotel.
4. Click **Start**. Output streams into the run's dataset.
5. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `destination` | `string` | no\* | — | Free-text destination for search mode, e.g. `"Paris, France"`. |
| `checkin` | `string` | required if `destination` set | — | Check-in date (`YYYY-MM-DD`). |
| `checkout` | `string` | required if `destination` set | — | Check-out date (`YYYY-MM-DD`), must be after `checkin`. |
| `adults` | `integer` | no | `2` | 1-30 adults per room. |
| `rooms` | `integer` | no | `1` | 1-10 rooms. |
| `maxHotelsPerSearch` | `integer` | no | `20` | 1-200 hotels returned per search. |
| `hotelUrls` | `array` | no\* | `[]` | Direct hotel-detail URLs; alongside or instead of `destination`. |
| `scrapeReviews` | `boolean` | no | `false` | Also fetch guest reviews per hotel. |
| `maxReviewsPerHotel` | `integer` | no | `20` | 1-200 reviews per hotel when `scrapeReviews` is on. |
| `countryCode` | `string` | no | `"US"` | 2-letter ISO country code — proxy geo pin. |
| `currency` | `string` | no | `"USD"` | 3-letter ISO 4217 currency code. |
| `sortOrder` | `string` | no | — | Optional search sort: `popularity`, `price`, `review_score`, `class`. |
| `proxyConfiguration` | `object` | no | unset | Apify Proxy editor; not required to be RESIDENTIAL. |

`*` At least one of `destination` or `hotelUrls` must be set.

#### Example input

```json
{
  "destination": "Paris, France",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "adults": 2,
  "rooms": 1,
  "maxHotelsPerSearch": 20,
  "scrapeReviews": false,
  "countryCode": "US",
  "currency": "USD"
}
```

### 📤 Output

Every row is one dataset item — one Booking.com hotel.

| Field | Type | Notes |
|---|---|---|
| `hotel_name` | string | Hotel name. |
| `booking_url` | string | Canonical hotel detail URL. |
| `address` | string | null | Street address. |
| `city` | string | null | City. |
| `country` | string | null | Country. |
| `latitude` | float | null | Latitude, when available. |
| `longitude` | float | null | Longitude, when available. |
| `star_rating` | float | null | Official star rating. |
| `review_score` | float | null | Booking.com's own review score (0-10 scale). |
| `review_count` | integer | null | Number of reviews behind the score. |
| `price` | float | null | Price in the requested currency, numeric. |
| `currency` | string | Echoes the requested `currency`. |
| `room_type` | string | null | Room type. |
| `thumbnail_url` | string | null | Thumbnail image URL. |
| `checkin` / `checkout` | string | null | Echoes the requested dates. |
| `reviews` | array | null | Guest reviews, only when `scrapeReviews=true`; best-effort. |

#### Example output

```json
{
  "hotel_name": "Hôtel Eiffel Trocadéro",
  "booking_url": "https://www.booking.com/hotel/fr/eiffel-trocadero.html",
  "address": "35 Rue Benjamin Franklin, 75116 Paris",
  "city": "Paris",
  "country": "France",
  "latitude": 48.8635,
  "longitude": 2.287,
  "star_rating": 4.0,
  "review_score": 8.6,
  "review_count": 1204,
  "price": 189.0,
  "currency": "USD",
  "room_type": "Superior Double Room",
  "thumbnail_url": "https://cf.bstatic.com/xdata/images/hotel/square200/....jpg",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "reviews": null
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.02 | One-off warm-up charge per run. |
| `hotel-scraped` | $0.0020 | Per hotel row written to your dataset. |
| `reviews-scraped` | $0.0005 | Per review item emitted, only when `scrapeReviews=true`. |

Example: 1 000 hotel rows at the rates above ≈ **$2.00**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

Scaffold-stage build — hotels-only (no Booking.com Attractions, car rentals, or flights), one-shot scrape per run (no price-trend history), and read-only extraction (no booking/reservation actions). Review pagination shape may evolve as recon continues; reviews stay best-effort and never block a hotel's core row.

### ❓ FAQ

**Is this legal?**

We only fetch content Booking.com makes publicly available on its search and hotel pages. Respect Booking.com's terms of service before using output commercially.

**Why is a field `null`?**

Detail-page fields are `null` when Booking.com doesn't expose that field publicly for a given hotel, or when a currency/locale mismatch caused the Actor to skip trusting that page's price.

**Do I need my own proxy?**

No — a proxy is wired in by default; `proxyConfiguration` is optional if you want to bring your own.

**Why did I get fewer rows than `maxHotelsPerSearch`?**

Any hotel that hits a dead page, a challenge that doesn't clear, or a currency/locale mismatch is skipped rather than failing the run — check the run's status message for a breakdown.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

## `destination` (type: `string`):

Free-text destination (city/region) for search mode, e.g. "Paris, France". Leave blank if using Hotel URLs instead.

## `checkin` (type: `string`):

Check-in date (YYYY-MM-DD). Required when Destination is set.

## `checkout` (type: `string`):

Check-out date (YYYY-MM-DD), must be after Check-in date.

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

Adults per room.

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

Number of rooms.

## `maxHotelsPerSearch` (type: `integer`):

Cap on hotels returned per search.

## `hotelUrls` (type: `array`):

Direct hotel-detail mode — one or more booking.com/hotel/...html URLs. Used alongside or instead of Destination.

## `scrapeReviews` (type: `boolean`):

Also fetch guest reviews per hotel (best-effort).

## `maxReviewsPerHotel` (type: `integer`):

Cap on reviews per hotel when Scrape reviews is enabled.

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

2-letter ISO country code — proxy geo pin, passed to the proxy session.

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

3-letter ISO 4217 currency code.

## `sortOrder` (type: `string`):

Optional Booking.com search sort.

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

Apify Proxy — optional. Actor prefers WEBSHARE\_PROXY\_URL when set, falling back to Apify Proxy (RESIDENTIAL then BUYPROXIES94952).

## Actor input object example

```json
{
  "destination": "Paris, France",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "adults": 2,
  "rooms": 1,
  "maxHotelsPerSearch": 5,
  "hotelUrls": [],
  "scrapeReviews": false,
  "maxReviewsPerHotel": 20,
  "countryCode": "US",
  "currency": "USD"
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "destination": "Paris, France",
    "checkin": "2026-09-10",
    "checkout": "2026-09-12",
    "maxHotelsPerSearch": 5,
    "hotelUrls": [],
    "countryCode": "US",
    "currency": "USD"
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/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 = {
    "destination": "Paris, France",
    "checkin": "2026-09-10",
    "checkout": "2026-09-12",
    "maxHotelsPerSearch": 5,
    "hotelUrls": [],
    "countryCode": "US",
    "currency": "USD",
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/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 '{
  "destination": "Paris, France",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "maxHotelsPerSearch": 5,
  "hotelUrls": [],
  "countryCode": "US",
  "currency": "USD"
}' |
apify call devilscrapes/booking-hotels-scraper --silent --output-dataset

```

## MCP server setup

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