# Airbnb Listings Scraper (search + details + reviews) (`ceo.sss/airbnb-listings-scraper`) Actor

Airbnb search results and full listing details for any location: price, rating, host, amenities, coordinates, reviews. Beats the ~270-result cap with automatic price-band splitting. Plain HTTP, pay per listing.

- **URL**: https://apify.com/ceo.sss/airbnb-listings-scraper.md
- **Developed by:** [Swaraj Samant Singh](https://apify.com/ceo.sss) (community)
- **Categories:** Travel, Real estate, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 listing scrapeds

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

## Airbnb Listings Scraper (search + details + reviews)

Get every Airbnb listing for a location or a search URL as clean JSON/CSV: price per night, total for your dates, rating, review count, host, coordinates, bedrooms/beds/baths, photos. Turn on details for amenities, description, rating breakdown, house rules and cancellation policy, and reviews for the newest guest reviews. Pay per listing.

Data source: airbnb.com, the same GraphQL endpoints the Airbnb web app calls. No browser, no login. Airbnb caps any single search at about 270 results; when you ask for more the actor automatically re-runs the search per price band until it has what you asked for (see [Limitations](#limitations)).

### What you get per listing

Search-level fields (always, one request per 18 listings):

```json
{
  "listingId": "15971352",
  "url": "https://www.airbnb.com/rooms/15971352",
  "title": "Modern Downtown Castle View Apartment",
  "roomType": "Entire home/apt",
  "propertyType": "Apartment",
  "area": "Arroios",
  "lat": 38.71491,
  "lng": -9.13652,
  "pricePerNight": 184.23,
  "totalPrice": 716,
  "originalTotalPrice": 922,
  "nights": 5,
  "currency": "USD",
  "checkIn": "2026-08-24",
  "checkOut": "2026-08-29",
  "rating": 4.88,
  "reviewsCount": 432,
  "isSuperhost": true,
  "badges": ["Superhost"],
  "bedrooms": 1,
  "beds": 1,
  "bathrooms": 1,
  "images": ["https://a0.muscache.com/im/pictures/094c8a4a-....jpg", "..."],
  "query": "Lisbon, Portugal",
  "scrapedAt": "2026-08-17T19:41:23.209Z"
}
```

With `includeDetails` (one more request per listing) the same row also carries:

```json
{
  "propertyType": "Entire apartment",
  "propertyTypeCode": "CONDOMINIUM",
  "city": "Lisbon",
  "isExactLocation": false,
  "maxGuests": 2,
  "ratingCleanliness": 4.9, "ratingAccuracy": 4.91, "ratingCheckin": 4.87,
  "ratingCommunication": 4.97, "ratingLocation": 4.74, "ratingValue": 4.79,
  "ratingDistribution": { "5": 89.4, "4": 9.3, "3": 1.4, "2": 0, "1": 0 },
  "isGuestFavorite": true,
  "hostId": "103698670",
  "hostName": "Pedro",
  "hostIsVerified": true,
  "hostRating": 4.87,
  "hostReviewsCount": 1577,
  "hostYears": 9,
  "hostResponseRate": 100,
  "hostResponseTime": "within an hour",
  "hostAbout": "Lisboner, citizen of the world! ...",
  "cohosts": ["Mila", "Tetiana"],
  "description": "This apartment is located in the Martim Moniz Square...",
  "highlights": ["Furry friends welcome", "24-hour self check-in"],
  "amenities": ["City skyline view", "Wifi", "Kitchen", "Washing machine", "..."],
  "amenitiesUnavailable": ["Exterior security cameras on property"],
  "houseRules": ["Check-in after 3:00 pm", "Checkout before 11:00 am", "No smoking"],
  "safetyNotes": ["Carbon monoxide alarm installed", "Smoke alarm installed"],
  "cancellationPolicy": "Moderate",
  "images": ["... up to 20 photos ..."]
}
```

With `includeReviews`, `reviews` is an array of `{ id, date, rating, text, language, reviewerName, reviewerId, hostResponse }`, newest first.

| Field | Present | Meaning |
|---|---|---|
| `listingId`, `url` | always | Airbnb's numeric listing ID and its canonical `airbnb.com/rooms/<id>` URL. Rows are de-duplicated on `listingId` |
| `title` | always | Listing name as the host wrote it |
| `roomType` | always | `Entire home/apt`, `Private room`, `Shared room`, `Hotel room`. From the search card it is inferred from Airbnb's label; with details it is Airbnb's exact `spaceType` |
| `propertyType`, `area` | always | Airbnb's card label split: "Apartment in Arroios" gives `Apartment` + `Arroios`. `area` is whatever Airbnb prints there (city, town or neighbourhood). Details add `city` and `propertyTypeCode` |
| `lat`, `lng` | always | Coordinates from Airbnb. `isExactLocation` (details) tells you whether Airbnb shows the exact spot or an approximate circle |
| `pricePerNight`, `totalPrice`, `nights`, `checkIn`, `checkOut` | when Airbnb prices the listing | Numbers in `currency`. `totalPrice` is Airbnb's total for the stay (its "total before taxes" display); `pricePerNight` is the nightly rate from Airbnb's own price breakdown. If you gave `checkIn`/`checkOut`, they are for your dates. If not, Airbnb picks example dates per listing and reports them in `checkIn`/`checkOut`; the price is for those |
| `originalTotalPrice` | when discounted | Pre-discount total |
| `rating`, `reviewsCount` | when the listing has reviews | Average and count. New listings carry `isNew: true` instead |
| `isSuperhost`, `isGuestFavorite`, `badges` | when true / present | From the search card badges; details confirm `isSuperhost` from the host profile |
| `bedrooms`, `beds`, `bathrooms` | when shown | Studio = `bedrooms: 0` |
| `images` | always | Up to 6 photo URLs from the search card, up to 20 with details |
| `hostId`, `hostName`, `hostIsVerified`, `hostRating`, `hostReviewsCount`, `hostYears`, `hostResponseRate`, `hostResponseTime`, `hostAbout`, `cohosts` | details | Host profile |
| `ratingCleanliness` ... `ratingValue`, `ratingDistribution` | details, when rated | Airbnb's six category ratings and the star distribution in percent |
| `description`, `highlights`, `amenities`, `amenitiesUnavailable`, `houseRules`, `safetyNotes`, `cancellationPolicy`, `maxGuests` | details | Description is plain text (HTML stripped). `amenitiesUnavailable` is Airbnb's "not included" list (no smoke alarm etc.) |
| `reviews` | with `includeReviews` | Newest first, up to `maxReviewsPerListing` |
| `query`, `scrapedAt` | always | Which input entry produced the row, and when |

Fields Airbnb does not provide for a listing are omitted, never filled with nulls or guesses.

### Input

```json
{
  "locationQueries": ["Lisbon, Portugal", "Goa, India"],
  "startUrls": ["https://www.airbnb.com/s/Barcelona--Spain/homes?room_types%5B%5D=Entire%20home%2Fapt&price_max=150"],
  "checkIn": "2026-10-10",
  "checkOut": "2026-10-14",
  "adults": 2,
  "currency": "USD",
  "priceMin": 50,
  "priceMax": 300,
  "minBedrooms": 1,
  "roomTypes": ["Entire home/apt"],
  "maxListingsPerQuery": 500,
  "includeDetails": true,
  "includeReviews": false,
  "maxReviewsPerListing": 20
}
```

| Field | Default | Notes |
|---|---|---|
| `locationQueries` | `[]` | Places as you would type them into Airbnb. One search each |
| `startUrls` | `[]` | Airbnb search URLs copied from the browser. Every query parameter is passed through unchanged (map bounds `ne_lat`/`sw_lng`, `amenities[]`, `guest_favorite`, `flexible_trip_lengths`, `monthly_start_date`...). URL parameters override the fields below |
| `checkIn`, `checkOut` | none | `YYYY-MM-DD`. Give both for exact prices for that stay. Without dates each listing is priced for example dates Airbnb picks (reported per row) |
| `adults` | 2 | |
| `currency` | `USD` | ISO code. Prices, `priceMin`/`priceMax` and price bands are in this currency |
| `priceMin`, `priceMax` | none | Per night |
| `minBedrooms` | none | |
| `roomTypes` | all | Any of `Entire home/apt`, `Private room`, `Shared room`, `Hotel room` |
| `maxListingsPerQuery` | 100 | Above ~270 the actor splits the search into price bands automatically (see below) |
| `maxItems` | none | Hard cap across all searches |
| `includeDetails` | false | Fetches `airbnb.com/rooms/<id>` for each listing. Roughly doubles the run time |
| `includeReviews`, `maxReviewsPerListing` | false, 20 | 24 reviews per request, newest first |
| `minDelayMs` | 1500 | Pause between requests. Measured 2026-08-18: 0.8-1.5 s pacing ran 65+ requests from one IP with no block, laptop and datacenter VM alike |
| `proxyConfiguration` | Apify residential | Residential is the safe default for Airbnb; datacenter worked in tests but Airbnb is known to block datacenter ranges. Sessions rotate on 403/429 |

At least one of `locationQueries` or `startUrls` is required.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Actor start | $0.005 per run |
| Listing scraped | $0.003 per listing row |
| Listing details | $0.005 per listing, only with `includeDetails` |
| Review scraped | $0.0005 per review, only with `includeReviews` |

1,000 listings without details = about $3. With details = about $8. Set "Maximum total charge" on the run to cap spend; the actor stops cleanly at the cap and everything already scraped stays in the dataset.

### Getting past Airbnb's ~270 result cap

Airbnb serves at most 15 pages of 18 results for any one search, whatever the real count ("1,000+ places"). Other scrapers stop there. This actor reads the total from the first page; if the search is saturated and you asked for more, it drops the search and re-runs it as two per-night price bands (Airbnb's slider range halved), then keeps halving whichever band is still saturated. Each split costs one extra request. Rows are de-duplicated on `listingId` across bands, so a listing that sits on a band edge appears once. Every band split is logged:

```
"Lisbon, Portugal": Airbnb caps this search at ~270 of 1000+ listings; splitting into price bands 0-1025, 1026-2000, 2001-∞
"Lisbon, Portugal" [0-1025 USD/night]: Airbnb caps this search at ~270 of 1000+ listings; splitting into price bands 0-537, 538-1025
...
```

Measured 2026-08-18: 350 unique Lisbon listings took 33 requests (42 s); 30 listings with details took 34 requests (56 s).

For very dense areas (all of Lisbon at 1,000+ listings under $60/night) a band can be one currency unit wide and still saturated; that band yields its 270 and the actor moves on. Add a `startUrls` entry with map bounds (`ne_lat`, `ne_lng`, `sw_lat`, `sw_lng`) or `roomTypes` to slice further.

### Limitations

- **Prices are Airbnb's display prices**: `totalPrice` is the stay total as Airbnb shows it in search (typically before taxes), `pricePerNight` is the nightly rate from Airbnb's own breakdown. Cleaning and service fees are not itemised. Without `checkIn`/`checkOut` the price is for Airbnb-chosen example dates, which differ per listing; give dates for comparable numbers.
- **`roomType` from search cards is inferred** from Airbnb's label ("Room in Lisbon" = private room, otherwise entire home). Turn on `includeDetails` for Airbnb's exact value.
- **Availability calendar and per-date pricing tables are not scraped.**
- **Airbnb's persisted-query hashes change with its builds.** The actor ships the current ones and, when Airbnb answers `persisted_query_not_found`, rescans Airbnb's JS bundles for the new hash at run time. If Airbnb restructures the payloads themselves the actor fails loudly rather than returning wrong data.
- **Geo redirects**: Airbnb sends non-US IPs to a local domain (`airbnb.co.in`, `airbnb.co.uk`...). The actor follows that once and uses it; data and IDs are identical, `url` always points at `airbnb.com`.
- **Rate limits**: Airbnb answers abuse with 403 or a 429. The actor paces requests (`minDelayMs`), backs off 5 s / 15 s / 45 s / 135 s and rotates to a new proxy session on each block. Runs of 350 listings from a single IP at 1 s pacing were not blocked in testing.

### FAQ

**Why fewer rows than Airbnb's "1,000+ places"?** `maxListingsPerQuery` caps each search (default 100). Raise it and the actor splits by price band. Duplicates across bands and across your queries are removed.

**Do I get all listings on the map?** You get what Airbnb's search returns for that query and filters, which is what a user sees. For a strict area use a `startUrls` entry with the map bounds Airbnb puts in the URL when you drag the map.

**How do I get exact prices for my dates?** Set `checkIn`/`checkOut`/`adults`. `nights`, `totalPrice`, `pricePerNight` then refer to that stay.

**Are the listing IDs and URLs real?** Yes: `listingId` is decoded from Airbnb's `DemandStayListing:<id>` global ID and `url` is `https://www.airbnb.com/rooms/<id>`, which opens the listing.

**Does it use a browser?** No, plain HTTP against Airbnb's own API endpoints, so it runs on 256 MB and a search page of 18 listings is one request.

**Which host, and what happened, at the end of a run?** The run log ends with a `Done.` line and the key-value store holds `RUN_STATS` (`listings`, `searches`, `priceBandSearches`, `failedSearches`, `details`, `detailFailures`, `reviews`, `reviewFailures`, `httpRequests`, `host`).

### Local development

```bash
npm install
## put your input in storage/key_value_stores/default/INPUT.json
apify run --purge          # or: npm start
npm test                   # offline parser checks on saved Airbnb payloads + one live search (skips when blocked)
ACTOR_TEST_PAY_PER_EVENT=1 ACTOR_MAX_TOTAL_CHARGE_USD=6 npm start   # exercise the pay-per-event cutoff locally
```

# Actor input Schema

## `locationQueries` (type: `array`):

Places to search, exactly as you would type them on Airbnb: "Lisbon, Portugal", "Goa, India", "Brooklyn, NY". One search per entry.

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

Airbnb search URLs copied from your browser (https://www.airbnb.com/s/<place>/homes?...). Every filter in the URL is honoured (map bounds, amenities, guest favourites, flexible dates...). URL filters override the fields below.

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

YYYY-MM-DD. With dates, prices are exact for that stay. Without dates Airbnb quotes each listing for dates it picks itself (see checkIn/checkOut on every row).

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

YYYY-MM-DD. Required together with checkIn.

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

Number of adult guests. Affects availability and price.

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

ISO code (USD, EUR, INR, GBP...). Prices and price bands are in this currency.

## `priceMin` (type: `integer`):

In the selected currency.

## `priceMax` (type: `integer`):

In the selected currency. Per night.

## `minBedrooms` (type: `integer`):

Only listings with at least this many bedrooms.

## `roomTypes` (type: `array`):

Leave empty for all.

## `maxListingsPerQuery` (type: `integer`):

Airbnb returns at most ~270 per search. Ask for more and the actor automatically re-runs the search per price band (using Airbnb's own price histogram) until it has enough or the bands are exhausted.

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

Hard cap across all searches.

## `includeDetails` (type: `boolean`):

One extra request per listing: amenities, description, host profile, rating breakdown, house rules, cancellation policy, up to 20 photos, exact room type. Charged as 'listing-details'.

## `includeReviews` (type: `boolean`):

One extra request per 24 reviews per listing. Charged per review.

## `maxReviewsPerListing` (type: `integer`):

Newest first. 24 reviews per request.

## `minDelayMs` (type: `integer`):

Pause between requests to Airbnb (per run). Lower is faster but gets a session blocked sooner.

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

Residential proxy recommended: Airbnb answers datacenter IPs with 403 far more often. Sessions rotate automatically on 403/429.

## Actor input object example

```json
{
  "locationQueries": [
    "Lisbon, Portugal"
  ],
  "adults": 2,
  "currency": "USD",
  "maxListingsPerQuery": 100,
  "includeDetails": false,
  "includeReviews": false,
  "maxReviewsPerListing": 20,
  "minDelayMs": 1500,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

One row per Airbnb listing: price, rating, host, location, optional details and reviews.

# 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 = {
    "locationQueries": [
        "Lisbon, Portugal"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ceo.sss/airbnb-listings-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 = {
    "locationQueries": ["Lisbon, Portugal"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ceo.sss/airbnb-listings-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 '{
  "locationQueries": [
    "Lisbon, Portugal"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ceo.sss/airbnb-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ceo.sss/airbnb-listings-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/rAZ4Otg0T3tbQIIZW/builds/OdLyGYTaYZqdDamS5/openapi.json
