# Airbnb Property-Operator Discovery Scraper (`xtracto/airbnb-operator-scraper`) Actor

Finds Airbnb hosts who manage multiple properties (5+ listings) across India, Malaysia, Philippines, Thailand, Hong Kong/Macau, Japan, Australia and New Zealand. Searches major cities, groups listings by host, and returns the complete raw Airbnb JSON for every qualifying operator and listing.

- **URL**: https://apify.com/xtracto/airbnb-operator-scraper.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** Travel, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.33 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Property-Operator Discovery Scraper

Find Airbnb hosts who run multiple properties — not casual hosts renting a spare room, but
property-management operators with 5 or more active listings. Searches major cities across
your target markets, groups every listing it finds by host, and keeps only the hosts that
clear your listing-count threshold.

### Why use this actor

- Purpose-built for finding **multi-property operators**, not single listings
- Covers India, Malaysia, Philippines, Thailand, China, Japan, Australia, New Zealand out of the box
- Groups listings by host automatically — no manual matching
- Returns the **complete, unmodified Airbnb JSON** for every operator, listing, and search hit — every field Airbnb sends is preserved, nothing renamed or dropped
- No login, no account, no lead-gen enrichment — only what Airbnb already shows publicly
- Output as a ready-to-download dataset (JSON) plus flattened `operators.csv` / `listings.csv` summaries

> A real, captured sample run (47 operators found across Thailand/Malaysia/Japan, incl.
> full CSVs and a few complete raw JSON records) is checked in at
> [`sample-output/`](./sample-output/).

### How it works

1. Searches Airbnb's results pages across your chosen cities in each target market.
2. Visits each listing found and reads off its host.
3. For every host discovered, visits their public Airbnb profile page — this reveals their
   **exact total listing count**, even for hosts with dozens of properties.
4. Keeps only hosts whose total listing count meets your threshold (default: 5+).
5. Returns one `OPERATOR` record per qualifying host and one `LISTING` record per listing
   found for them, each carrying the raw Airbnb data behind it.

### Input

```json
{
  "countries": ["Thailand", "Malaysia"],
  "minListingsPerHost": 5,
  "maxSearchPagesPerCity": 2,
  "maxListingsPerCity": 20,
  "maxHostsToResolve": 150,
  "maxListingsPerOperator": 15,
  "concurrency": 3,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

| Field | Type | Description |
|---|---|---|
| `countries` | array | Target markets to search. Default: all 8. |
| `citiesPerCountry` | object | Advanced: override the built-in city list, e.g. `{"Japan": ["Tokyo--Japan", "Kyoto--Japan"]}`. |
| `minListingsPerHost` | integer | Keep only hosts with at least this many listings. Default 5. |
| `maxSearchPagesPerCity` | integer | Search-result pages swept per city (~18 listings/page). Default 2. |
| `maxListingsPerCity` | integer | Cap on candidate listings collected per city. Default 20. |
| `maxHostsToResolve` | integer | Cap on unique hosts checked per run. Default 150. |
| `maxListingsPerOperator` | integer | Cap on listings fetched in full per qualifying operator. Default 15. |
| `concurrency` | integer | Parallel requests. Default 3 — keep this low. |
| `proxyConfiguration` | object | Residential proxy recommended for a full run. |

The default settings run a small, quick sweep (2 cities per country) so a first run finishes
in well under 30 minutes. Raise the city list (via `citiesPerCountry`) and the caps above for
a full production sweep — see "Coverage & limitations" below for what to expect at scale.

### Output

Two record types in the same dataset, distinguished by `recordType`.

**`OPERATOR`** — one per qualifying host:

```json
{
  "recordType": "OPERATOR",
  "_scrapedAt": "2026-07-24T12:18:04Z",
  "host_id": "27721148",
  "host_name": "Lily",
  "host_profile_url": "https://www.airbnb.com/users/show/27721148",
  "is_superhost": true,
  "managed_listings_total_count": 96,
  "listings_discovered_count": 15,
  "listing_ids_discovered": ["1515350780921666997", "..."],
  "countries": ["Thailand"],
  "cities": ["Bangkok"],
  "raw_host": { "...": "the complete, untouched Airbnb host-profile JSON" }
}
```

**`LISTING`** — one per listing found for a qualifying operator:

```json
{
  "recordType": "LISTING",
  "_scrapedAt": "2026-07-24T12:18:04Z",
  "country": "Thailand",
  "city": "Bangkok",
  "listing_id": "1597685945984399331",
  "listing_url": "https://www.airbnb.com/rooms/1597685945984399331",
  "host_id": "27721148",
  "host_profile_url": "https://www.airbnb.com/users/show/27721148",
  "raw_search": { "...": "the search-result card JSON, if this listing was found via city search" },
  "raw_listing": { "...": "the complete, untouched Airbnb listing JSON" }
}
```

`raw_search` / `raw_listing` / `raw_host` are Airbnb's own response data, kept exactly as
received — every field, every nested object, nothing renamed or filtered. `country`/`city`
are null for listings found only via an operator's profile page rather than city search
(see below).

**CSV export**: `operators.csv` and `listings.csv` are written alongside the dataset with
flattened summary columns (names, URLs, counts, titles) for quick spreadsheet review. The
full raw JSON lives in the dataset — a single listing's payload can run past 100KB, too
large for a CSV cell in Excel/Sheets, so the CSVs index into the JSON rather than embed it.

### Coverage & limitations (please read)

- **"China" is served via Hong Kong and Macau.** Airbnb has had no domestic listings or
  hosts inside mainland China since it exited that market in 2022. Selecting "China"
  searches Hong Kong and Macau instead — the closest active market — and the `country`
  field on results reflects Airbnb's own resolved location (e.g. "Hong Kong"), not the
  literal word "China".
- **Operators above 10 listings are sampled, not fully enumerated.** Airbnb's public host
  profile page always reports an **exact** total listing count, but only ever *shows* up to
  10 of those listings on the page itself — confirmed against real hosts with 15, 61, and 96
  total listings, all capped at 10 shown. This actor returns the accurate total count for
  every operator, plus full raw JSON for those ~10 sampled listings and any additional ones
  the city search happens to surface for the same host. For an operator with, say, 96
  listings, expect the actor to return the true count (96) alongside full data for roughly
  10-25 of their actual listings, not all 96 — Airbnb does not publicly expose a way to list
  every listing for a large host beyond that sample.
- **Runtime scales with coverage.** Defaults are intentionally small (2 cities/country,
  capped search pages and host resolutions) so a first run finishes quickly. A full sweep
  across all 8 markets with a wide city list is a much larger run — raise the caps
  incrementally and watch the run duration before committing to the broadest settings.
- A residential proxy is recommended for any run beyond a quick test.

# Actor input Schema

## `countries` (type: `array`):

Markets to search. Note: Airbnb has had no domestic listings/hosts inside mainland China since 2022, so 'China' is served via Hong Kong and Macau, its closest active market — see README.

## `citiesPerCountry` (type: `object`):

Optional. Override the built-in city list for one or more countries, e.g. {"Japan": \["Tokyo--Japan", "Kyoto--Japan", "Fukuoka--Japan"]}. Values are Airbnb search-URL slugs, i.e. the part after /s/ in https://www.airbnb.com/s/<slug>/homes. Countries not listed here use the built-in defaults (2 cities each).

## `minListingsPerHost` (type: `integer`):

Only keep hosts whose Airbnb profile reports at least this many managed listings. The brief asks for property-management operators, so 5 is the recommended floor.

## `maxSearchPagesPerCity` (type: `integer`):

How many pages of search results (~18 listings/page) to sweep per city. Higher = more candidate listings discovered, more requests.

## `maxListingsPerCity` (type: `integer`):

Hard cap on candidate listings collected per city before PDP enrichment.

## `maxHostsToResolve` (type: `integer`):

Hard cap on unique hosts whose public profile gets fetched in one run (this is the step that reveals the true listing count). Raise for a full production sweep.

## `maxListingsPerOperator` (type: `integer`):

For a qualifying operator, how many of their listings to fetch full raw JSON for (beyond what the city search already found). Airbnb's own public profile page samples at most 10 listings per host regardless of their true total — see README 'Coverage & limitations'.

## `concurrency` (type: `integer`):

How many requests to run in parallel. Keep low (1-4) to stay under Airbnb's rate limits.

## `logLevel` (type: `string`):

Set DEBUG for verbose troubleshooting output.

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

Residential proxy is strongly recommended for a full multi-country run.

## Actor input object example

```json
{
  "countries": [
    "Thailand"
  ],
  "minListingsPerHost": 5,
  "maxSearchPagesPerCity": 1,
  "maxListingsPerCity": 10,
  "maxHostsToResolve": 10,
  "maxListingsPerOperator": 3,
  "concurrency": 3,
  "logLevel": "INFO",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `host_id` (type: `string`):

Host id as reported by the source.

## `host_name` (type: `string`):

Host name as reported by the source.

## `host_profile_url` (type: `string`):

Link to the host profile.

## `is_superhost` (type: `string`):

Is superhost. Boolean value.

## `managed_listings_total_count` (type: `string`):

Managed listings total count. Whole number.

## `listings_discovered_count` (type: `string`):

Listings discovered count. Whole number.

## `countries` (type: `string`):

Countries.

## `cities` (type: `string`):

Cities.

## `_scrapedAt` (type: `string`):

UTC timestamp of the scrape, ISO 8601.

## `country` (type: `string`):

Country.

## `city` (type: `string`):

City.

## `listing_id` (type: `string`):

Listing id as reported by the source.

## `listing_url` (type: `string`):

Link to the listing.

## `recordType` (type: `string`):

Which kind of row this is.

## `listing_ids_discovered` (type: `string`):

Listing ids discovered as reported by the source.

## `raw_host` (type: `string`):

Raw host as reported by the source.

## `raw_search` (type: `string`):

Raw search as reported by the source.

## `raw_listing` (type: `string`):

Raw listing as reported by the source.

## `status` (type: `string`):

Status reported by the source.

## `url` (type: `string`):

Direct link to the scraped item.

## `headers` (type: `string`):

Headers.

## `body` (type: `string`):

Body as reported by the source.

## `method` (type: `string`):

Method as reported by the source.

## `payload` (type: `string`):

Payload as reported by the source.

## `use_extended_unique_key` (type: `string`):

Use extended unique key as reported by the source.

## `keep_url_fragment` (type: `string`):

Keep url fragment as reported by the source.

## `name` (type: `string`):

Name of the item.

## `title` (type: `string`):

Title of the item.

## `description` (type: `string`):

Short description or summary.

## `seoTitle` (type: `string`):

Seo Title as reported by the source.

## `seoDescription` (type: `string`):

Seo Description as reported by the source.

## `versions` (type: `string`):

Versions.

# 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 = {
    "countries": [
        "Thailand"
    ],
    "maxSearchPagesPerCity": 1,
    "maxListingsPerCity": 10,
    "maxHostsToResolve": 10,
    "maxListingsPerOperator": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/airbnb-operator-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 = {
    "countries": ["Thailand"],
    "maxSearchPagesPerCity": 1,
    "maxListingsPerCity": 10,
    "maxHostsToResolve": 10,
    "maxListingsPerOperator": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("xtracto/airbnb-operator-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 '{
  "countries": [
    "Thailand"
  ],
  "maxSearchPagesPerCity": 1,
  "maxListingsPerCity": 10,
  "maxHostsToResolve": 10,
  "maxListingsPerOperator": 3
}' |
apify call xtracto/airbnb-operator-scraper --silent --output-dataset

```

## MCP server setup

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