# Airbnb Search Scraper - Prices, Ratings & Photos (`sumitr_mardy/airbnb-search-scraper`) Actor

Export public Airbnb listings with numeric prices, ratings, photos and map coordinates. Search cities or paste a search URL with your filters. Automatic price splitting reaches beyond one search's page cap. Failed and empty inputs are free.

- **URL**: https://apify.com/sumitr_mardy/airbnb-search-scraper.md
- **Developed by:** [Sumitr Mardy](https://apify.com/sumitr_mardy) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 listings

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?

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

## Airbnb Search Scraper

Export **public Airbnb search listings** with numeric prices, ratings, photos and map coordinates. Search a city or paste an Airbnb search URL with filters. Automatic price splitting expands coverage beyond a single search's page cap.

This independent actor is **not affiliated with or endorsed by Airbnb**. It reads public search results only.

### Quick start

```json
{
  "locations": ["Lisbon"],
  "checkIn": "2026-11-28",
  "checkOut": "2026-12-01",
  "adults": 2,
  "currency": "USD",
  "maxListings": 100,
  "splitByPrice": true
}
```

Dates are optional; update dated examples before running them. The small daily Store example uses Lisbon without dates and at most 20 listings.

### Input

| Field | Default | Behavior |
|---|---|---|
| locations | Required | Place names or full https://www.airbnb.com/s/ search URLs. Up to 50 effective searches; duplicates removed. |
| checkIn, checkOut | Empty | Both or neither, YYYY-MM-DD, check-out after check-in. |
| adults | 2 | 1–16 adults. |
| currency | USD | Three-letter code; always added when a pasted URL does not specify currency. |
| minPrice, maxPrice | Empty | Optional per-night filters in the effective search currency. |
| maxListings | 100 | Maximum unique listings per location, capped at 5,000. |
| splitByPrice | true | Split full result sets into narrower nightly price buckets. |

Pasted URLs retain their filters, including room types, map bounds, dates, guests and price limits; existing URL values take precedence over the corresponding input defaults. The page cursor is reset so coverage starts on page one. The actor accepts Airbnb.com search URLs, not arbitrary websites or room-detail URLs.

### Coverage and price splitting

One Airbnb search exposes up to 15 pages, usually 18 cards per page, with repeated listings. A platform spike on **2026-10-29**, Lisbon with a three-night stay on December 10–13, returned **209 unique listings** from one 15-page search. Price splitting returned **1,000 unique listings**, then stopped at its configured limit; seven buckets remained. This is a measured comparison, not a total inventory count.

For a full search, the actor reads the first and last pages. If page 15 has 18 cards, it splits the price range rather than reading the parent's remaining 13 pages. The shortcut matched a complete 15-page baseline in the spike. It is a heuristic tied to Airbnb's pagination, not a proof of exhaustiveness.

Buckets split until complete, one currency unit wide, or bounded by your listing limit or the internal work guard. Inclusive boundary overlap is deduplicated by listing id. Unbounded searches start with 0–1,000 and a higher-price tail; the tail is expanded as needed, so expensive listings are not silently discarded at 1,000. Small limits up to 36 can be filled directly without probing the last page.

### Output

One row per unique listing **per input location**. All keys are always present, never null; missing strings are "", numbers 0, arrays \[]. Numeric zero for a missing price or rating does not mean the listing is free.

| Fields | Meaning |
|---|---|
| inputIndex, input, location | Original input position/value and search location. |
| listingId, url | Decoded DemandStayListing id and canonical listing link; propertyId is not used. |
| name, title, summary | Host listing name, search-card type/area and available room summary. |
| latitude, longitude | Coordinates exposed in the public card; may be approximate. |
| rating, reviewsCount, isNew | Parsed numeric rating/review count; new or unrated cards use 0/0 and isNew=true. |
| badges, isGuestFavorite, isSuperhost | Available public badge text and flags. |
| priceTotal, priceOriginal, pricePerNight, nights | Advertised search total, original total when supplied, derived average per night, and stay length. |
| currency, priceLabel | Effective requested currency and original public price label. |
| checkIn, checkOut, adults | Effective explicit search dates and guest count. Dates remain empty when you did not set them. |
| photos, thumbnail | Public search-card image URLs and first image. |
| searchUrl, pageNumber | Exact bucket/page URL and page position within that search. |
| status, subStatus, error, scrapedAt | Result status, machine-readable detail, error and UTC ISO timestamp. |

The nightly price is total divided by stay length, rounded to two decimals; it is not a separately verified base rate. Without dates Airbnb can choose a default stay, such as five nights. The actor parses that displayed stay length and total; it does not assume the price is for tonight. Photos, badges and fields depend on the public card. Unidentifiable cards with no usable listing id are skipped to preserve reliable deduplication; if all cards lack ids, the actor emits a free parse_error row.

| status / subStatus | Listing charge |
|---|---|
| ok / listing | Charged |
| no_results / no_results | Free empty search |
| failed / invalid_input | Free |
| failed / http_CODE, blocked, parse_error, network_error | Free |
| failed / free_work_limit | Free; internal free-work guard stopped the search |
| failed / cost_limit_reached | Free; your event spending cap was reached |

If a later page fails, saved listings remain and a free terminal failure row explains the stop.

### Pricing

One **listing** event per saved unique listing per location, with all available search-card fields included. FREE $0.0015, BRONZE $0.0012, SILVER $0.001, GOLD and above $0.0008 per listing ($1.50 / $1.20 / $1.00 / $0.80 per 1,000). Default Actor start fee: $0.00005 per GB, minimum one event. Failed and empty rows have no listing charge; the start fee still applies. No dataset-item surcharge.

The actor saves one batch per page, then charges for that batch. It honors your maximum charge and bounds unpaid work.

### Honest limits

- Public search only: no host contact data, room-detail extraction, calendar availability or review collection.
- Prices are what the search page shows for that stay and currency. Fees, taxes, discounts and availability can change; verify booking details on Airbnb.
- A single search is capped at 15 pages. Splitting improves coverage but cannot guarantee all listings in a city, including dense one-unit buckets or inventory hidden by ranking and availability.
- Results change by dates, filters, currency, geography, ranking and time. Repeated runs can differ.
- Public field paths can change. Missing fields use stable sentinels; absent/unparseable embedded state produces a free parse_error and is not retried.
- No login, API key, residential proxy, browser automation or fingerprint spoofing is used for collection. A 403/429 gets one direct retry after backoff, then at most one Apify datacenter proxy attempt if available.
- Pages and locations are processed sequentially. Hard request deadlines, a recursion bound and a free-work guard limit unproductive work.

### API examples

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/sumitr_mardy~airbnb-search-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"locations":["Lisbon"],"maxListings":20,"currency":"USD"}'
```

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("sumitr_mardy/airbnb-search-scraper").call(
    run_input={"locations": ["Lisbon"], "maxListings": 20, "currency": "USD"}
)
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["name"], listing["pricePerNight"], listing["currency"])
```

### Enrich listings with amenities, host fields and category ratings

Pass the listingId values from successful search rows to [Airbnb Listing Details](https://apify.com/sumitr_mardy/airbnb-listing-details). It returns public amenities, photos, host profile fields, six numeric category ratings and star distribution in one stable schema. Join both datasets by listingId. The Details actor does not return prices; keep the search price fields when combining them.

### Use it from AI agents (MCP)

Connect through [Apify MCP](https://mcp.apify.com/?tools=sumitr_mardy/airbnb-search-scraper) using hosted OAuth sign-in:

```json
{"mcpServers":{"airbnb-search":{"url":"https://mcp.apify.com/?tools=sumitr_mardy/airbnb-search-scraper"}}}
```

For Bearer-auth clients, configure your own token locally:

```json
{"mcpServers":{"airbnb-search":{"url":"https://mcp.apify.com/?tools=sumitr_mardy/airbnb-search-scraper","headers":{"Authorization":"Bearer <YOUR_APIFY_TOKEN>"}}}}
```

For stdio clients with Node.js:

```json
{"mcpServers":{"airbnb-search":{"command":"npx","args":["-y","@apify/actors-mcp-server","--actors","sumitr_mardy/airbnb-search-scraper"],"env":{"APIFY_TOKEN":"<YOUR_APIFY_TOKEN>"}}}}
```

Agent tips: start with 20 listings, inspect status, use future dates, compare prices only for equivalent stays/currencies, and disclose that split coverage is bounded. To export more of the same city, raise maxListings and keep splitByPrice=true.

Support: [Actor issues](https://apify.com/sumitr_mardy/airbnb-search-scraper/issues).

# Actor input Schema

## `locations` (type: `array`):

Place names or full https://www.airbnb.com/s/ search URLs. Up to50 locations, deduplicated by effective search URL. Pasted filters take precedence.

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

Optional YYYY-MM-DD. Supply checkOut too; dates in pasted URLs take precedence.

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

Optional YYYY-MM-DD, after checkIn.

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

Number of adults. Existing pasted URL value is preserved.

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

Three-letter currency code, used when the pasted URL does not already specify one.

## `minPrice` (type: `number`):

Optional per-night lower price filter in the effective search currency.

## `maxPrice` (type: `number`):

Optional per-night upper price filter in the effective search currency.

## `maxListings` (type: `integer`):

Maximum unique listings per input location. Default100, limit5000; prefill20 for daily Store test.

## `splitByPrice` (type: `boolean`):

Split full fifteen-page result sets into smaller nightly price ranges to increase coverage. Still bounded by maxListings, minimum bucket width and internal work guard.

## Actor input object example

```json
{
  "locations": [
    "Lisbon"
  ],
  "adults": 2,
  "currency": "USD",
  "maxListings": 20,
  "splitByPrice": true
}
```

# Actor output Schema

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

No description

# 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 = {
    "locations": [
        "Lisbon"
    ],
    "maxListings": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("sumitr_mardy/airbnb-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 = {
    "locations": ["Lisbon"],
    "maxListings": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("sumitr_mardy/airbnb-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 '{
  "locations": [
    "Lisbon"
  ],
  "maxListings": 20
}' |
apify call sumitr_mardy/airbnb-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sumitr_mardy/airbnb-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/w2gBaq3J2vjnz7YTW/builds/4IfL6Sb6g3zwhv1WT/openapi.json
