# Booking.com Search Scraper - Hotels & Prices (`thenetaji/booking-search-scraper`) Actor

Search Booking.com by destination and export every property returned: coordinates, address, star rating, guest review score, description, photo, and paid-placement markers. Add check-in/check-out dates for real per-stay prices.

- **URL**: https://apify.com/thenetaji/booking-search-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Travel, Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 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?

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 Search Scraper

The Actor searches Booking.com by destination and returns every property in the result set: coordinates, address, star rating, guest review score, description, photo, and paid-placement markers. Setting `checkin` and `checkout` adds a real, dated price to each row; without them, Booking prices a stay rather than a hotel, and no row carries one.

### Accepted input

| Field | Type | Default | Description |
|---|---|---|---|
| `destination` | string | required | Free text, exactly as typed into Booking's own search box: a city, region, neighbourhood, or country. |
| `checkin` | string (`YYYY-MM-DD`) | — | Arrival date. Must be set together with `checkout`. |
| `checkout` | string (`YYYY-MM-DD`) | — | Departure date. Must be after `checkin`. |
| `adults` | integer | `2` | Guests in the party, 1-30. |
| `rooms` | integer | `1` | Rooms wanted, 1-30. |
| `offset` | integer | `0` | Row offset to start pagination at, 0-10000, in steps of `rows_per_page`. |
| `rows_per_page` | integer | `25` | Properties Booking returns per request, 1-100. |
| `maxItems` | integer | `100` | Maximum properties saved across all pages. `0` removes the limit. |

```json
{
  "destination": "San Francisco",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "adults": 2,
  "rooms": 1,
  "maxItems": 50
}
```

### Response fields

| Field | Contents |
|---|---|
| `property_id` | Booking's internal property id, stable across dated and dateless searches |
| `slug`, `country_code` | Together, the input the Booking.com Hotel Scraper takes |
| `url`, `name`, `description` | The property's page, name, and one-paragraph blurb |
| `star_rating` / `star_rating_scale_max` | The property's star classification, out of five |
| `review_score` / `review_score_scale_max` | The guest score, out of ten |
| `review_count` | Exact review count the score is computed over |
| `external_review_score` / `external_review_count` | A score sourced from outside Booking's own guests, when one exists |
| `photo_url` | The property's lead photo |
| `destination_id` | Booking's internal id for the city or area |
| `accommodation_type_id` | Booking's own code for the kind of property; Booking publishes no label mapping for it |
| `is_closed`, `is_sold_out`, `is_sustainable` | Property status flags |
| `is_ad`, `native_ad_id` | Whether the row bought its position, and the placement id if so |
| `is_preferred`, `is_preferred_plus` | Booking's Preferred Partner tiers, a commercial relationship distinct from a paid ad slot |
| `location` | `address`, `city`, `country_code`, `latitude`, `longitude`, `display_location`, `distance_from_centre`, `public_transport_distance`, `is_centrally_located`, `is_within_best_location_score_area` |
| `price` | `null` unless `checkin`/`checkout` were set; otherwise `per_night_display`, `stay_total_display`, `stay_total_amount`, `currency` |

```json
{
  "slug": "club-quarters-san-francisco",
  "country_code": "us",
  "name": "Club Quarters Hotel",
  "star_rating": 4,
  "review_score": 8.2,
  "review_count": 5043,
  "is_ad": false,
  "price": { "per_night_display": "$193", "currency": "USD" }
}
```

### Behaviour on pagination

The Actor walks Booking's search API in pages of `rows_per_page` rows, starting at `offset`, and advances the offset itself; the run does not need to be started once per page. Each response states `has_more` and `exhausted`, computed from Booking's own reported total (`total_results`) rather than from whether the page came back full, so the last page of a 353-property city (three rows) is not mistaken for a full page one step early. The walk stops on whichever comes first: `has_more` turning false, an empty page, or `maxItems` being reached. This calls the same search API Booking's own frontend calls when scrolling, not its search page, which serves the identical first 25 results regardless of any offset passed to it.

### Frequently asked questions

**Why is `price` null on every row?**
No `checkin`/`checkout` was given. Booking prices a stay, not a hotel: without both dates, every row comes back with no price at all, not because the data is missing but because a price is not a property of a hotel by itself. Setting both dates adds a real, dated price to every row.

**Why do `star_rating` and `review_score` look like two different ratings?**
They are. `star_rating` is the property's star classification, out of five; `review_score` is the guest score, out of ten. `star_rating_scale_max` and `review_score_scale_max` state each row's own ceiling explicitly so the two are never read on the wrong scale.

**Should rows with `is_ad: true` be filtered out?**
Not necessarily; `is_ad` states a fact rather than making a recommendation. It marks a row that bought its position in the results Booking returned, which is a different thing from `is_preferred`, Booking's separate Preferred Partner programme. Both are stated on every row so a reader who wants only organic placements can filter for `is_ad: false` directly.

**Why did the run save fewer rows than `maxItems`?**
Either the destination's result set was exhausted before the limit was reached (a small town can hold fewer than 25 properties in total), or the search matched nothing Booking could place and it returned an empty page. Either way the run finishes normally rather than failing, since an empty result is a real answer, not an error.

**Can a run be resumed after being interrupted?**
Yes. A resumed run continues from the exact page and row it stopped on, using the offset of the page the interruption landed inside rather than re-fetching pages already saved.

### Related

[Booking.com Hotel Scraper](https://apify.com/thenetaji/booking-property-scraper) fetches one property's full record by slug: the complete subscore breakdown and up to ten featured guest reviews. Every row from this Actor carries the `slug` and `country_code` it needs, so a search result feeds directly into it.

# Actor input Schema

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

Where to search — free text, exactly as you would type it into Booking's own box. A city, region, neighbourhood or country all work.

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

Arrival date. Optional, but must be set together with Check-out — with both, results carry availability and prices for that stay; without either, every row's price is null.

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

Departure date. Must be set together with Check-in, and must be after it.

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

Guests in the party. Affects which properties can accommodate the search and what they cost.

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

Rooms wanted.

## `offset` (type: `integer`):

Row offset to start pagination at, in steps of Results Per Page. Leave at 0 to start from the beginning; a resumed run continues from where it left off regardless of this value.

## `rows_per_page` (type: `integer`):

How many properties Booking returns per request, 1-100. 25 is Booking's own frontend default; 100 is the largest value measured working and means fewer requests per run.

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

Maximum number of properties to save across all pages. Set 0 for as many as the destination has.

## Actor input object example

```json
{
  "destination": "San Francisco",
  "checkin": "2026-09-10",
  "checkout": "2026-09-12",
  "adults": 2,
  "rooms": 1,
  "offset": 0,
  "rows_per_page": 25,
  "maxItems": 25
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped 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 = {
    "destination": "San Francisco",
    "adults": 2,
    "rooms": 1,
    "rows_per_page": 25,
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/booking-search-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": "San Francisco",
    "adults": 2,
    "rooms": 1,
    "rows_per_page": 25,
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/booking-search-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": "San Francisco",
  "adults": 2,
  "rooms": 1,
  "rows_per_page": 25,
  "maxItems": 25
}' |
apify call thenetaji/booking-search-scraper --silent --output-dataset

```

## MCP server setup

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