# Booking.com Scraper (`mlg14/booking-scraper`) Actor

Extract public accommodation listings from Booking.com search and city pages, including ratings, location, photos, available prices and room summaries.

- **URL**: https://apify.com/mlg14/booking-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 80.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

## Booking.com Scraper

Scrape Booking.com accommodation listings from public city pages or search-results URLs and export Booking.com property data to JSON, CSV, or spreadsheet formats. This Booking.com API alternative collects property names, page links, review scores, locations, images, and any public price and room information exposed by the selected page.

A city URL is a dependable starting point when you need a compact catalogue of properties. A dated destination search reads the public search data response and can provide prices and room summaries. Search-results URLs with website filters use the data embedded in their pages and may require visitor verification. Each dataset row represents one unique property, identified by its public property ID.

### What data can you extract from Booking.com?

The actor keeps the same columns for search and city records. Empty values mean the source page did not expose the field. A city page usually includes names, review scores, images, and location; a dated search page can include prices, room summaries, and more amenities. The `sourcePage` column tells you which page supplied a row.

| Field | Description | Example |
| --- | --- | --- |
| `order` | Position in this run's output | `1` |
| `propertyId` | Public property identifier used for deduplication | `7886254` |
| `url` | Direct public property page | `https://www.booking.com/hotel/nl/tribe-amsterdam-city.html` |
| `name` | Displayed accommodation name | `Tribe Amsterdam City` |
| `type` | Property type label, if exposed | `null` |
| `propertyTypeId` | Numeric accommodation category, when present | `204` |
| `description` | Short public property summary | `Located in Amsterdam...` |
| `stars` | Star count where the page marks the score as stars | `4` |
| `price` | Numeric displayed amount in `currency` | `103.84` |
| `pricePerNight` | Average nightly amount on a dated search card | `82.00` |
| `priceBasis` | `from` on a city card or `totalStay` on a dated search card | `from` |
| `currency` | Three-letter code attached to `price` | `USD` |
| `rating` | Guest score on the site's ten-point scale | `9` |
| `ratingLabel` | Displayed score label, when present | `Very Good` |
| `reviews` | Number of guest reviews shown on the card | `5811` |
| `breakfast` | Meal-plan inclusion indicated on a search card | `null` |
| `checkIn` | Property check-in time if the card exposes it | `null` |
| `checkOut` | Property check-out time if the card exposes it | `null` |
| `checkInDate` | Date applied to a search result | `2026-11-10` |
| `checkOutDate` | Departure date applied to a search result | `2026-11-12` |
| `latitude` | Property latitude where exposed | `52.402493` |
| `longitude` | Property longitude where exposed | `4.931046` |
| `address` | Street address on a search card, when available | `2 Middenweg` |
| `city` | City name | `Amsterdam` |
| `countryCode` | Two-letter country code | `nl` |
| `district` | District or displayed area | `Amsterdam Noord` |
| `image` | Main public property image URL | `https://cf.bstatic.com/xdata/images/hotel/...` |
| `features` | Amenity names shown on a search result | `["Free Wifi"]` |
| `rooms` | Room summaries available on a dated search result | `[{"roomType":"Original Room","persons":2}]` |
| `isSoldOut` | Availability marker on a search result | `false` |
| `sourcePage` | Source of the record | `city` |

The `rooms` array holds the room name, occupancy, displayed amount and currency, bed count, and bedroom count when the search card provides them. It is a summary of visible room information, not a full rate plan or reservation quote. The public city collections do not include room arrays, so `rooms` is empty for those rows. `features` is similarly an empty array when no amenity names appear. Prices and availability can change between page retrieval and a booking decision.

### How to scrape Booking.com

1. Copy a public city URL, such as `https://www.booking.com/city/nl/amsterdam.html`, into `startUrls`. You may provide several city URLs in one run. Alternatively, enter a destination in `search` or paste a search-results URL containing the filters you selected on the website.
2. Set `maxItems` to the number of unique properties you need. Add `checkIn` and `checkOut` when using search results and you want the page to expose stay pricing. Choose the number of adults and rooms for that search.
3. Select a preferred currency and language if needed. Existing URL query parameters take precedence over these inputs, so a filtered search URL retains the choices already present in it.
4. Run the actor and inspect the dataset. Download the rows in JSON, CSV, or a spreadsheet format. Check `sourcePage` and `priceBasis` before comparing prices from different inputs.

For a compact list of names and ratings, start with city pages. For comparison of prices for a particular stay, use a dated destination search. A dated search with no website-specific filters uses the public search data response; a URL containing additional filters uses its embedded page data. The actor does not open each hotel's detail page. A hotel-detail URL by itself is not an accepted input.

### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `search` | text | empty | Destination name used for a search-results request. |
| `startUrls` | URL list | empty | Public city or search-results URLs. Search URL filters remain in the request. |
| `maxItems` | integer | `100` | Maximum unique properties across all input URLs; `0` removes the actor-side cap. |
| `checkIn` | date | empty | Arrival date for a search, formatted `YYYY-MM-DD`. |
| `checkOut` | date | empty | Departure date after `checkIn`; both dates must be supplied together. |
| `adults` | integer | `2` | Adult guests for a search-results request. |
| `rooms` | integer | `1` | Rooms for a search-results request. |
| `currency` | text | `USD` | Preferred three-letter displayed price currency. |
| `language` | text | `en-us` | Preferred page language code. |
| `proxyConfiguration` | object | automatic routing | Optional proxy configuration. |

Example for a dependable catalogue run:

```json
{
  "startUrls": [
    { "url": "https://www.booking.com/city/nl/amsterdam.html" },
    { "url": "https://www.booking.com/city/fr/paris.html" }
  ],
  "maxItems": 50,
  "currency": "USD",
  "language": "en-us"
}
```

For dated results, use a search URL or a destination name along with two dates. For example, `search` can be `Amsterdam`, `checkIn` can be `2026-11-10`, and `checkOut` can be `2026-11-12`. Those date strings are examples; choose future dates appropriate to your run. A search URL can also carry price, star, property-category, and sorting choices made on the website. The actor preserves existing query parameters rather than silently replacing them.

You can mix city and search URLs, but rows are deduplicated by public property ID. Put the source with the fields you care most about first. If a property appears in both sources, the first version is retained. For example, place a dated search-results URL before a city URL if the dated amount matters more than the short city description.

### Output example

The following item was collected from the public Amsterdam city page in the cloud validation run. The image URL is shortened here for readability; the dataset contains the full URL. The value `priceBasis: "from"` matters: this city-card amount is an indicative displayed price, not a quote for the example dates above.

```json
{
  "order": 1,
  "propertyId": 7886254,
  "url": "https://www.booking.com/hotel/nl/tribe-amsterdam-city.html",
  "name": "Tribe Amsterdam City",
  "type": null,
  "propertyTypeId": 204,
  "description": "Located in Amsterdam, 2.2 miles from A'DAM Lookout, Tribe Amsterdam City has accommodations with a fitness center, private parking, a shared lounge and a bar.",
  "stars": null,
  "price": 103.84,
  "pricePerNight": null,
  "priceBasis": "from",
  "currency": "USD",
  "rating": 9,
  "ratingLabel": null,
  "reviews": 5811,
  "breakfast": null,
  "checkIn": null,
  "checkOut": null,
  "checkInDate": null,
  "checkOutDate": null,
  "latitude": 52.402493,
  "longitude": 4.931046,
  "address": null,
  "city": "Amsterdam",
  "countryCode": "nl",
  "district": "Amsterdam Noord",
  "image": "https://cf.bstatic.com/xdata/images/hotel/...",
  "features": [],
  "rooms": [],
  "isSoldOut": null,
  "sourcePage": "city"
}
```

In that run, two city URLs yielded 50 unique rows before the configured item limit stopped collection. Names, public property IDs, links, review scores, review counts, city names, and images were present on all 50 rows. Some source cards omitted the country code; the actor now derives it from the public hotel URL when needed. A separate dated Amsterdam search validation delivered 30 rows with dates, names, links, ratings, and review counts present on every row. The example preserves empty fields rather than filling them with estimates.

### Use cases

- Accommodation market research: collect public property names, categories, review counts, and locations for a city-level inventory.
- Destination analysis: compare public review scores and listing counts across several city pages using a single dataset.
- Trip planning: shortlist properties from a filtered search URL, then open the linked property pages to confirm the current offer.
- Location analysis: map listings with available coordinates or group them by city, district, and country code.
- Rate monitoring: run the same dated search at intervals and compare displayed amounts, keeping the dates, currency, and `priceBasis` with each snapshot.
- Content audits: identify listings with missing public descriptions, ratings, images, or other fields for follow-up checks.

A record reflects what the public page showed at retrieval time. It is suitable for research and monitoring; it does not reserve inventory, hold a rate, or confirm that every room remains bookable. When you combine runs, keep the input dates and page URL alongside the exported data so price comparisons remain interpretable.

### How much does it cost to scrape Booking.com?

The current result charge is **$1.00 per 1,000 dataset items**. At that rate, 50 delivered properties cost about **$0.05**, 500 properties about **$0.50**, and 5,000 properties across suitable inputs about **$5.00**. These are calculations from the per-result rate, not promises that one city page or one search can supply those volumes. The platform also enforces the run's own spending limit, and the actor stops pushing rows when the item cap or charge limit is reached.

A city page exposes a selected group of properties, so an input with `maxItems: 500` may return far fewer than 500 items. The Amsterdam and Paris city pages in the validation run supplied enough distinct properties to reach the configured 50-item limit together. Search pages can show more results but may be blocked by visitor verification. Inspect the actual dataset count after each run; charges depend on items delivered, not the requested maximum.

### Tips for best results

Use city URLs when you need names, links, ratings, photos, and a modest sample of properties. They are useful for a stable first pass and can be combined across destinations. City collections may include the same hotel in several page sections; the actor removes duplicates by `propertyId` before writing the dataset.

Use a search-results URL if you need website filters or a stay-specific amount. Apply property category, star, price, or sorting choices on the website, then copy the resulting URL. The actor keeps those query parameters. Add dates to see more complete pricing and room information where the page provides it. Without stay dates, a result may have no meaningful price or room match.

If a price is essential, require a nonempty `price`, `currency`, and appropriate `priceBasis` in your downstream workflow. A city-card `from` amount and a dated-search `totalStay` amount have different meanings. Do not compare them as equal nightly prices. `pricePerNight` is available only when the dated search card includes it.

Set a realistic item cap. Public search pagination commonly exposes only part of a destination's inventory, and the actor stops advancing after 1,000 search offsets per URL. To cover more inventory, submit several separately filtered search URLs and allow deduplication across them. Splitting by property category, district, or price range may expose different results, subject to what the website displays.

### Limits

The search-results page and hotel-detail routes sometimes respond with a JavaScript verification page. A browser attempt and a rotated residential session did not consistently clear that response during development. Dated destination searches use the public search data response and passed the 30-item validation. Filtered search URLs still depend on their page data and can fail verification despite retries. City landing pages returned normal property data in the validation run. A run with no collected properties fails visibly instead of returning an apparently successful empty dataset.

City pages are curated sections, not an exhaustive city inventory. In the observed Amsterdam page, its public sections exposed 34 distinct property IDs; the observed Paris page exposed 33. Those counts may change. Search results also have a visible per-query limit, and a single query should not be treated as a complete catalogue. `maxItems: 0` removes only this actor's output cap; it cannot remove site limits.

Hotel check-in and check-out times, complete addresses, detailed room offers, and full descriptions are usually absent from the city collections. The output retains their columns as `null` or empty arrays. Search cards provide some additional fields but are not hotel-detail pages. Direct hotel URLs are not accepted as `startUrls`. No login, personal account data, or private guest details are collected.

Language, currency, date, and location can affect what the site shows. If a supplied URL already sets one of those query parameters, the URL value is preserved. A city's indicative amount may not be tied to the input dates; dates apply to search pages. For any purchase decision, reopen the hotel link and verify the current price, taxes, conditions, and availability on the website.

### Use with connected clients

A connected client can run the actor with a city URL and request a table of property name, district, guest score, review count, and link. Another useful request is to run a dated search URL for two guests, return properties with a displayed total-stay amount, and sort the exported rows by review score. Include the requested dates and currency in the client's task so the result can be interpreted correctly.

The input descriptions specify accepted URL types, limits, dates, and currency. The output includes a source marker and price basis, so a downstream application can separate city catalogue rows from dated search rows. A client should handle absent fields as missing source data rather than treating `null` as zero.

### FAQ

**Is it legal to collect these listings?** The actor reads public accommodation pages. Use the data in a way that respects applicable laws, the website's terms, and privacy rules. Do not use the output to infer private guest information.

**Do I need to configure a proxy?** Usually no. The default setup starts with datacenter routing and can change session or escalate when a request receives a verification response. A custom proxy setting is available for accounts with different network requirements.

**How fast is a run?** The validated 50-row city run finished in seconds after startup. Search runs can take longer because each page needs a request and verification retries may occur. Performance also changes with the number of URLs and result pages.

**Can I schedule repeat runs?** Yes. Reuse the same input at a chosen interval and store each dataset with its run time. For price monitoring, keep the exact dates and currency in the input, and compare the same `priceBasis` across runs.

**Can I export to a spreadsheet?** Yes. Download the dataset as CSV or a spreadsheet-compatible format. Nested room summaries remain structured in JSON; for a simple table, use top-level columns such as name, rating, price, currency, city, and URL.

**Why are some columns empty?** The city and search pages show different details, and individual properties may omit optional data. Check `sourcePage`. An empty check-in time, room list, or description means it was not on the accessed page; it is not an estimate.

**Can I paste a hotel URL?** Direct hotel pages are outside the current input scope. Use a city or search-results URL and follow the `url` in the returned row to review that hotel on the website.

### Integrations

Run the actor through its API, read the default dataset, and send the records to a database, spreadsheet, notification workflow, or reporting application. Scheduled runs and webhooks support recurring collection and completion alerts. Use `propertyId` as the stable key when merging snapshots, and retain `sourcePage`, `priceBasis`, dates, and currency when building comparisons over time.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request. Include a public city or search URL, the run ID, and the field you expected so the page behavior can be checked.

# Actor input Schema

## `search` (type: `string`):

Destination name for a Booking.com search. For example, Amsterdam. Search pages may present a verification challenge.

## `startUrls` (type: `array`):

Public Booking.com search-results or city URLs. Search URLs can contain website filters. City URLs offer a smaller, more reliable set of listings.

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

Maximum number of unique properties across all inputs. Set 0 for all available public results.

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

Check-in date (YYYY-MM-DD) for search results and dated prices. Set checkOut too.

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

Check-out date (YYYY-MM-DD) after checkIn.

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

Number of adults for search-result availability and pricing.

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

Number of rooms for search-result availability and pricing.

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

Preferred three-letter currency code for displayed prices.

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

Booking.com language code for labels, such as en-us or fr.

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

Proxy settings. The actor starts with datacenter routing and escalates after verification responses.

## Actor input object example

```json
{
  "search": "Amsterdam",
  "startUrls": [
    {
      "url": "https://www.booking.com/city/nl/amsterdam.html"
    }
  ],
  "maxItems": 100,
  "adults": 2,
  "rooms": 1,
  "currency": "USD",
  "language": "en-us",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `properties` (type: `string`):

All collected public property records 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 = {
    "search": "Amsterdam",
    "startUrls": [
        {
            "url": "https://www.booking.com/city/nl/amsterdam.html"
        }
    ],
    "currency": "USD",
    "language": "en-us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/booking-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 = {
    "search": "Amsterdam",
    "startUrls": [{ "url": "https://www.booking.com/city/nl/amsterdam.html" }],
    "currency": "USD",
    "language": "en-us",
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/booking-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 '{
  "search": "Amsterdam",
  "startUrls": [
    {
      "url": "https://www.booking.com/city/nl/amsterdam.html"
    }
  ],
  "currency": "USD",
  "language": "en-us"
}' |
apify call mlg14/booking-scraper --silent --output-dataset

```

## MCP server setup

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