# Airbnb Scraper — Sub-Ratings, Review Topics & Sleeping Plans (`signalbench/airbnb-scraper`) Actor

Airbnb listings with the fields nobody else extracts: Airbnb's own review-topic counts, the 5-to-1 star distribution, all six sub-ratings, per-room sleeping plans, co-hosts, host response rate and time, and a 12-month availability calendar. No browser. JSON, CSV, Excel.

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

## Pricing

from $2.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?

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 Scraper — Sub-Ratings, Review Topics, Sleeping Plans & Calendar

Most Airbnb scrapers give you the title, the price and a pile of amenities. This one gives you the fields that actually answer questions — **what guests complain about, how the ratings break down, and how the property is laid out** — in a single request per listing, with no browser.

### What you get that other scrapers don't

| Field | Why it matters |
|---|---|
| **`reviewTopics`** — Airbnb's own topic counts | `{topic: "Location", count: 292}`, `{topic: "Cleanliness", count: 83}`. This is Airbnb's NLP run over the *entire* review corpus, not a sample. Answering "what do guests complain about in this submarket" normally means scraping and classifying thousands of review bodies yourself. Here it arrives as structured integers. |
| **`starDistribution`** | The full 5→1 star split as fractions. A 4.8 built from 90% fives and 4% ones is a very different property from a 4.8 built from steady fours. |
| **`subRatings`** | All six category scores — cleanliness, accuracy, check-in, communication, location, value. |
| **`sleepingArrangements`** | Per room: `{room: "Bedroom 1", beds: ["1 queen bed", "1 floor mattress"]}`. The real sleeping capacity, not just a bed count. |
| **`host.responseRate` / `responseTime`** | "100%", "Responds within an hour". |
| **`host.cohosts`** | Co-host names and profile IDs — how you spot property managers running portfolios. |
| **`highlights`** | Airbnb's three generated selling points for the listing. |
| **`location.neighborhoodDescription`** + `isLocationVerified` | The host's own neighbourhood write-up, and whether Airbnb has verified the address. |
| **`location.breadcrumbs`** | The full geographic chain, coarse to fine. |
| **`isGuestFavorite`**, Superhost, years hosting | Badge and tenure signals in one place. |
| **`availability`** | 1–12 months of night-by-night availability, plus minimum-stay rules and a summary. |

Plus the usual: description, every amenity with its category, every photo with the host's caption, capacity, house rules, coordinates, individual reviews with host replies, and prices on search runs.

### Two ways to use it

**Search a location** — give it `Austin, TX` (or paste a full Airbnb search URL to reuse filters you set on the site). Results carry the price Airbnb showed on the card; add check-in/check-out dates and those prices become real quotes for that stay.

**Specific listings** — paste listing URLs or bare numeric IDs.

### Output

One row per listing. Example (abridged):

```json
{
  "id": "24169503",
  "title": "Carpe Diem Garden - great views, an oasis in Söğüt",
  "propertyType": "Entire rental unit in Marmaris, Turkey",
  "personCapacity": 6, "bedrooms": 2, "beds": 5, "baths": 2,
  "rating": 5, "reviewCount": 30,
  "subRatings": { "cleanliness": 5, "accuracy": 5, "checkin": 4.9,
                  "communication": 5, "location": 4.9, "value": 5 },
  "starDistribution": { "5": 1, "4": 0, "3": 0, "2": 0, "1": 0 },
  "reviewTopics": [ { "topic": "Hospitality", "count": 22 },
                    { "topic": "View", "count": 21 },
                    { "topic": "Cleanliness", "count": 12 } ],
  "sleepingArrangements": [ { "room": "Bedroom 1", "beds": ["1 double bed"] },
                            { "room": "Living room", "beds": ["2 sofa beds"] } ],
  "isGuestFavorite": true,
  "host": { "name": "Ipek", "isSuperhost": true, "yearsHosting": 9,
            "responseRate": "Response rate: 100%",
            "responseTime": "Responds within an hour", "cohosts": [] },
  "location": { "latitude": 36.6472, "longitude": 28.0965,
                "country": "Türkiye", "region": "Muğla", "place": "Söğütköy Köyü",
                "isLocationVerified": true },
  "availability": { "daysScanned": 91, "availableNights": 74,
                    "unavailableNights": 17, "unavailableShare": 0.1868 },
  "amenityCount": 72, "photoCount": 78
}
```

### Pricing and billing

**$1.00 per 1,000 listings**, all-in — you don't pay separate platform usage on top.

One complete listing = one charge. A listing that returns no content is never stored, so it's never billed. Feed it a page of dead IDs and the run costs you $0.00 in result charges.

### Notes on what the data does and doesn't mean

- **`availability` is an occupancy proxy, not a booking rate.** A blocked night can be a booking or a host simply closing the date, and Airbnb doesn't distinguish the two. It's reported as `unavailableShare` for exactly that reason. Don't sell it as occupancy.
- **Prices appear on search runs**, taken from the card Airbnb rendered. Add dates to get a real quote for a specific stay. Listings with no availability for your dates show no price — that's Airbnb's behaviour, not a scrape failure.
- **`reviews` are best-effort.** The main request always returns the aggregate review data (topics, distribution, sub-ratings). Individual review bodies come from a secondary endpoint; if Airbnb changes it, that array comes back empty and the rest of the listing is unaffected — and still correct.
- **Check your location spelling.** Airbnb never returns "no results" for a search — hand it a place it doesn't recognise and it quietly falls back to somewhere else entirely. The listings you get back are real, so they're stored and billed; they just won't be where you meant. If a run comes back with unexpected cities, the location string is the thing to check.
- **Personal data.** Host names, profile IDs, profile text and reviewer names are personal data under GDPR/CCPA. Turn on **Exclude personal data** to drop all of it and keep only aggregate host metrics.
- **Locale.** Listing text (titles, descriptions, amenity labels) follows the `locale` you set. Search-card labels stay in English — Airbnb serves localised search behind a redirect that doesn't carry the data this Actor reads.

### How it works

No browser. Airbnb serves this data through its own public GraphQL API, reachable with the key embedded in every listing page, so one HTTP request per listing returns the entire record. That's roughly a tenth the compute of a Puppeteer-based scraper, which is why the price can be what it is.

The Actor reads Airbnb's current query hashes and API key from a live page on every run rather than pinning them, so an Airbnb deploy doesn't silently break it.

### Responsible use

Airbnb's Terms of Service prohibit scraping, and you should read them and decide for yourself. This Actor stays off every path Airbnb's `robots.txt` disallows for listing pages — it uses only the canonical `/rooms/{id}` page, never the disallowed `/rooms/*/reviews`, `/rooms/*/amenities`, `/rooms/*/photos` or `/users/*/listings` sub-pages. Note that Airbnb's `robots.txt` **does** disallow `/s/*/*`, which covers the search-results path used by this Actor's search mode; if that matters for your use case, use **Specific listings** mode instead, which is fully within `robots.txt`.

Requests are paced and retried politely, and concurrency is deliberately conservative.

# Actor input Schema

## `mode` (type: `string`):

"Search a location" walks Airbnb search results for a place. "Specific listings" scrapes only the listing URLs or IDs you supply.

## `location` (type: `string`):

Where to search — a city, region or country, e.g. "Austin, TX", "Barcelona", "Lake Tahoe". Ignored when you supply a search URL or use "Specific listings".

## `searchUrl` (type: `string`):

Paste a full Airbnb search URL to reuse filters you set on the site (price, rooms, amenities, property type). When set, it overrides the Location field.

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

Optional YYYY-MM-DD. Adding dates makes search results carry real prices for that stay instead of no price at all.

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

Optional YYYY-MM-DD.

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

Guest count used for the search. Affects which listings match and what they cost.

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

Airbnb listing URLs (https://www.airbnb.com/rooms/12345) or bare numeric IDs. Used when mode is "Specific listings".

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

Hard cap on how many listings this run scrapes and bills for.

## `includeReviews` (type: `integer`):

How many individual reviews to fetch per listing, with their host replies. Set to 0 to skip them — the review-topic counts, star distribution and six sub-ratings are collected either way, because they come from the main request at no extra cost.

## `includeCalendar` (type: `boolean`):

Fetch each listing's night-by-night availability and minimum-stay rules, plus a summary. Note that a blocked night can mean either a booking or a host-blocked date — Airbnb does not say which — so treat it as an occupancy proxy, never as a booking rate.

## `calendarMonths` (type: `integer`):

How many months of availability to pull, starting this month.

## `excludePersonalData` (type: `boolean`):

Drop host names, profile IDs, profile text and reviewer names from the output, keeping only aggregate host metrics. Useful when your legal basis for processing does not cover personal data.

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

ISO currency code used for any prices, e.g. USD, EUR, GBP.

## `locale` (type: `string`):

Language for titles, descriptions and labels, e.g. en, es, fr, de.

## `politenessDelayMs` (type: `integer`):

Randomised pause between requests. Lower is faster; raise it if you see blocks on large runs.

## `maxRetries` (type: `integer`):

How many times to retry a blocked or failed request, rotating the proxy session each time.

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

Optional. Airbnb serves this data without a bot-detection stack, so a direct connection normally works and is fastest. Turn a proxy on for very large runs or if you start seeing 429s.

## Actor input object example

```json
{
  "mode": "search",
  "location": "Austin, TX",
  "searchUrl": "https://www.airbnb.com/s/Austin--TX/homes?price_max=300&room_types%5B%5D=Entire%20home%2Fapt",
  "maxItems": 20,
  "includeReviews": 10,
  "includeCalendar": true,
  "calendarMonths": 6,
  "excludePersonalData": false,
  "currency": "USD",
  "locale": "en",
  "politenessDelayMs": 1200,
  "maxRetries": 3
}
```

# Actor output Schema

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

Every scraped listing in JSON, with all nested fields.

## `resultsCsv` (type: `string`):

The same listings flattened for spreadsheets.

## `summary` (type: `string`):

Counts of listings stored, empty and failed for this run.

## `viewInConsole` (type: `string`):

Open this run and its dataset in the Apify 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 = {
    "location": "Austin, TX",
    "maxItems": 20,
    "includeReviews": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalbench/airbnb-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 = {
    "location": "Austin, TX",
    "maxItems": 20,
    "includeReviews": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("signalbench/airbnb-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 '{
  "location": "Austin, TX",
  "maxItems": 20,
  "includeReviews": 10
}' |
apify call signalbench/airbnb-scraper --silent --output-dataset

```

## MCP server setup

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