# Airbnb Reviews Scraper (`mlg14/airbnb-reviews-scraper`) Actor

Scrape public Airbnb listing reviews from room URLs. Export ratings, text, dates, guest and host details, replies, and review highlights with pagination and sorting.

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

## Airbnb Reviews Scraper

Scrape Airbnb reviews from public room URLs and export ratings, review text, dates, reviewer details, host replies, and listing context to CSV, JSON, or Excel. This Airbnb reviews API alternative collects pages beyond the initial listing preview and lets you choose review order, a date cutoff, a language and region, and per-listing limits.

Each dataset item represents one public review. Supply one or several room URLs, run the scraper, and use the resulting flat records for analysis, monitoring, or reporting. The scraper reads the listing page to identify the current public review request, then paginates the review data. A tested room with 496 reported reviews returned records beyond offset 400; a 100-review run completed successfully.

### What data can you extract from Airbnb?

The dataset uses stable camelCase field names. Fields that Airbnb does not provide for a particular review are null, except `reviewPhotoUrls`, which is an empty array when no photo URLs are present. The original review language and text remain available alongside localized fields, so analysis can distinguish what the guest wrote from what was returned for the selected locale.

| Field | Description | Example |
| --- | --- | --- |
| `startUrl` | Canonical room URL associated with the review | `https://www.airbnb.com/rooms/12937` |
| `listingId` | Numeric room identifier as a string | `12937` |
| `listingReviewCount` | Total public review count reported for the room | `496` |
| `id` | Unique review identifier | `1780419577076084622` |
| `language` | Original review language code | `en` |
| `text` | Original review body | `It took me a while to fall asleep...` |
| `localizedText` | Text returned in the requested locale when available | `It took me a while to fall asleep...` |
| `localizedReview` | Legacy localized text field, when supplied | `null` |
| `createdAt` | Review timestamp in ISO format | `2026-09-22T12:15:52Z` |
| `reviewHighlight` | Short context displayed with the review | `Stayed a few nights` |
| `highlightType` | Category of that context | `LENGTH_OF_STAY` |
| `rating` | Review star rating | `5` |
| `ratingAccessibilityLabel` | Human-readable rating label | `Rating, 5 stars` |
| `response` | Public reply from the reviewed host, if one exists | `null` |
| `reviewerLocation` | Location displayed with the review | `Berkeley, California` |
| `reviewPhotoUrls` | URLs of public photos attached to the review | `[]` |
| `reviewerId` | Reviewer account identifier | `7987311` |
| `reviewerName` | Public reviewer display name | Present in full dataset |
| `reviewerPictureUrl` | Public reviewer profile image URL | Image URL or `null` |
| `reviewerProfileUrl` | Public reviewer profile link | Profile URL or `null` |
| `revieweeId` | Reviewed host account identifier | `50124` |
| `revieweeName` | Public reviewed host display name | Present in full dataset |
| `revieweePictureUrl` | Public host profile image URL | Image URL or `null` |
| `revieweeProfileUrl` | Public host profile link | Profile URL or `null` |

`id` identifies the review, while `listingId` identifies the room. Keep both when combining runs: the same room can appear in several runs, but a review should be counted once per room. The total in `listingReviewCount` is context from Airbnb, not a guarantee that a limited run exported that many rows. For example, a room can report 496 reviews while an input cap exports only 100.

The output intentionally separates reviewer and reviewed-host details into columns. That makes common operations such as grouping by room, counting ratings, and filtering for replies possible without unpacking nested user objects. A missing response does not mean the review failed to load; it means no public response was present in the returned record.

### How to scrape Airbnb reviews

1. Copy the public URL of each room you want to inspect. A URL such as `https://www.airbnb.com/rooms/12937` is sufficient; the review subpage form is also accepted.
2. Add those URLs to **Room URLs**. Set **Maximum reviews per listing** if you only need a sample from each room, or leave it empty to page through the available reviews.
3. Choose a sort order. Use **Most recent** with **Reviews since date** when you are collecting only new reviews after a previous run.
4. Start the run. The scraper fetches each listing, requests review pages, and writes one item per review to the dataset.
5. Open the dataset and download JSON, CSV, or Excel. For a recurring process, schedule the same input and compare review IDs between runs.

The scraper accepts multiple URLs in one run. It removes duplicate room IDs from the input before scraping, and its total limit stops the entire run once enough items have been written. If a room URL is invalid or unavailable, the run logs that failure and continues with the other rooms. A run where every room fails ends with an error rather than reporting a misleading successful empty dataset.

### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | Array of room URL objects | Example room URL | Required. Each entry contains a `url` for a public Airbnb room. |
| `maxReviewsPerListing` | Integer | Empty | Maximum items from each room. Empty means page until the available reviews end. |
| `maxItems` | Integer | `0` | Maximum items across all rooms. Zero means no total cap. |
| `sortBy` | String | `most-recent` | `most-recent`, `most-relevant`, `highest-rated`, or `lowest-rated`. |
| `sinceDate` | Date string | Empty | Include reviews on or after `YYYY-MM-DD`. Requires `most-recent` sorting. |
| `locale` | String | `en-US` | Requested language and region for localized fields, such as `de-DE`. |
| `proxyConfiguration` | Object | Enabled | Proxy settings used for listing and review requests. |

A realistic input for a first run is:

```json
{
  "startUrls": [
    { "url": "https://www.airbnb.com/rooms/12937" }
  ],
  "maxReviewsPerListing": 100,
  "maxItems": 100,
  "sortBy": "most-recent",
  "locale": "en-US"
}
```

The per-listing cap and total cap serve different purposes. If you provide five rooms, `maxReviewsPerListing: 100` allows up to 100 reviews from each one. Adding `maxItems: 250` stops the entire run at 250 reviews, even if some rooms still have more. The earliest rooms in the input are processed first, so order the URLs deliberately when the total cap is smaller than the sum of the per-listing caps.

For incremental monitoring, set `sortBy` to `most-recent` and enter the previous collection date as `sinceDate`. The cutoff includes reviews created on that date. The scraper stops reading a room when it reaches an older review, which avoids paging through years of records when only recent changes matter. A cutoff cannot be combined with another sort order because older reviews may appear before newer ones in those orders.

### Output example

This excerpt comes from a successful 100-review run of the sample room. Long review text and public profile fields are omitted from the excerpt; the actual dataset item includes every field in the table above.

```json
{
  "startUrl": "https://www.airbnb.com/rooms/12937",
  "listingId": "12937",
  "listingReviewCount": 496,
  "id": "1780419577076084622",
  "language": "en",
  "text": "It took me a while to fall asleep to the sound of the subway — but there were also crickets all night long, in early autumn.",
  "createdAt": "2026-09-22T12:15:52Z",
  "reviewHighlight": "Stayed a few nights",
  "highlightType": "LENGTH_OF_STAY",
  "rating": 5,
  "ratingAccessibilityLabel": "Rating, 5 stars",
  "response": null,
  "reviewPhotoUrls": []
}
```

The text above is shortened for readability. The dataset preserves the full text returned for each review. Timestamps use the source's ISO timestamp, which can be converted into reporting periods or compared directly with a date cutoff. The `rating` is a numeric value suitable for aggregation; the accessibility label is the source's display wording.

### Use cases

- **Property quality monitoring:** Collect recent reviews for a portfolio of rooms, then group comments by room and month. Compare rating distributions and read the actual text behind a change in average rating.
- **Host response audits:** Filter records where `response` is present to see which public reviews received a reply. Use `createdAt` and the room URL to review the surrounding context.
- **Travel research:** Compare publicly visible guest feedback on cleanliness, location, sleep quality, and communication across several candidate rooms. The review text gives detail that a headline rating alone cannot convey.
- **Hospitality research:** Build a sample of reviews from rooms in a chosen set and analyze themes across time. Preserve `listingId` and `id` so repeated runs can be deduplicated.
- **Recent-review alerts:** Schedule a most-recent run with a cutoff date, then notify an internal team when new review IDs appear. A modest per-listing limit keeps recurring checks bounded.
- **Localization checks:** Request a chosen locale while retaining `language` and `text`. This helps distinguish original-language reviews from localized text returned by the site.

Use public review data with care. Display names, profile links, images, and locations can refer to people. Collect only fields needed for a legitimate purpose and apply appropriate retention and access controls to exported datasets.

### How much does it cost to scrape Airbnb?

The configured event price is **$0.001 per delivered review**, or **$1 per 1,000 reviews**. Charges are based on items written to the dataset, so a smaller limit produces a smaller event charge. Platform usage is included in this per-result price. The number of public reviews shown on a listing is not itself billed; only delivered records count.

| Delivered reviews | Event cost |
| ---: | ---: |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 5,000 | $5.00 |
| 10,000 | $10.00 |

For example, checking ten rooms and exporting 100 reviews from each produces at most 1,000 results, so the event charge is at most $1. A daily check that yields 20 new results for 30 days produces 600 results and an event charge of $0.60, assuming each review is delivered once. Repeatedly exporting the same reviews in separate runs counts those items again. Keep your own list of review IDs if you need to avoid repeated downstream processing.

A room may have fewer reviews than the requested cap. If it has 37 public reviews and the cap is 100, the dataset can contain only the 37 available records from that room, and the result charge follows the actual count. You can also set a global `maxItems` value to bound the maximum event cost of a multi-room run.

### Tips for best results

Use the direct room URL, including its numeric room ID. Search result links, host profile links, and URLs from unrelated domains are not valid inputs. The scraper accepts an optional `/reviews` suffix but normalizes the output to the canonical room URL, making it easier to join records from repeated runs.

Start with a cap of 50 or 100 reviews when evaluating a set of rooms. Once the output fields and coverage fit your workflow, leave the per-listing cap empty to collect all available pages. The review endpoint returned 50 records when requested for 100 in testing, so the scraper uses 50-record pages. It advances the offset after each page and stops at the reported total or when no more records are returned.

Choose the sort order for the question you are answering. **Most recent** is suited to monitoring and date cutoffs. **Highest rated** and **Lowest rated** surface opposite ends of the rating distribution. **Most relevant** follows the site's relevance order; it is useful for browsing but should not be interpreted as chronological. The sorting options are passed to the same public review query used for listing reviews.

Use `locale` when display language matters. The `language` field identifies the review's original language, while localized fields reflect what the source returned for the requested locale. The site does not guarantee a translation for every item. If you need a single comparable corpus, inspect both the original and localized columns before treating them as interchangeable.

For recurring checks, use a date cutoff and save review IDs from prior runs. The cutoff reduces requests when only recent reviews matter, while the IDs let you deduplicate any overlap around the boundary date. A review created late on the cutoff date is included because the comparison uses the review's calendar date.

### Limits

Only publicly accessible room reviews are supported. An unavailable room or a page requiring a sign-in can prevent collection for that URL. The scraper does not access private messages, booking details, unpublished reviews, or information visible only to an account holder.

The source can omit individual fields. Public host responses are absent on many reviews, so `response` is often null. Photo URLs are empty on reviews without attached photos. The legacy `localizedReview` field may be null even when `localizedText` is filled. A reported listing review count describes the source at request time and may change after the run.

Testing reached a room's final page at offset 480, returning the last 16 of 496 reviews. This confirms pagination beyond a short preview for that room; it does not promise the same coverage for every room. The site's review query, its operation identifier, or the listing page's public configuration can change. If the source changes, a run may fail until the scraper is updated.

The site returned some nonfatal errors for review-tag summary fields while still returning complete review records. The scraper uses the review records when present and does not expose those summary fields. If the review records themselves are missing, it reports a failure rather than substituting partial listing metadata for reviews.

### Automated workflows

The same input can be reused in a scheduled run or sent through a programmatic call. A monitoring workflow can supply a set of room URLs, `sortBy: "most-recent"`, and a recent `sinceDate`, then consume the dataset after the run. A comparison workflow can submit several rooms with `maxReviewsPerListing: 100`, group the resulting records by `listingId`, and calculate rating or response statistics.

When automating repeated collections, keep the input stable and save both `listingId` and `id` from each item. Those two fields are sufficient to identify the room and review in a downstream table. Add `createdAt` when you need time windows, and retain `startUrl` when analysts need a link back to the room. If the same room appears twice in an input list, it is collected only once in that run.

### FAQ

#### Is it legal to collect Airbnb reviews?

The scraper collects public pages and review data. Whether a particular use is permitted depends on the site's terms and applicable law. Respect privacy rules, including GDPR where relevant, and avoid collecting or using personal data without a legitimate basis. Consult qualified counsel for decisions about your specific use.

#### Do I need to provide a proxy?

The default proxy setting is ready for a standard run. You can override it if your account and workflow require different routing. The scraper starts with a lower-cost route and can escalate on an access block. A successful test of the sample room did not encounter a block.

#### How fast is a run?

The sample run delivered 100 reviews in one successful remote run. Actual time depends on how many rooms and pages you request, source response time, and any access retries. A limit of 100 needs fewer review requests than a full historical collection from a room with hundreds of reviews.

#### Can I schedule and monitor it?

Yes. Save the input as a recurring task or schedule a run at the interval you need. Compare review IDs with a prior dataset, or use a recent cutoff date to reduce historical paging. Check the run status and logs if a room becomes unavailable.

#### Can I export to a spreadsheet?

Yes. Download the dataset as CSV or Excel for spreadsheet analysis, or as JSON for a data pipeline. The output fields are flat, so ratings, dates, room IDs, and public replies become ordinary columns.

#### Why is a field empty?

The source may not provide it for that review. For example, a review without a public host reply has `response: null`, and a review without photos has `reviewPhotoUrls: []`. The original text and key identity fields were filled in the 100-review test; optional fields vary by item.

#### Can it collect every review from a room?

Leave the per-listing cap empty and do not set a restrictive total cap. The scraper pages until the source returns fewer records than requested, no records, or the reported total is reached. A tested room was accessible through its final page, but availability and source behavior can vary.

### Integrations

Use the dataset through the platform's API, scheduled runs, or webhooks. Export files to a spreadsheet, send new review records to an internal database, or trigger a notification when a new review ID appears. The room and review identifiers make it straightforward to join results across runs and prevent duplicate downstream records.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

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

Airbnb room URLs to collect public guest reviews from.

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

Maximum reviews for each room. Leave empty to collect all available reviews.

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

Hard cap across all room URLs. Zero means no total cap.

## `sortBy` (type: `string`):

Order in which reviews are collected.

## `sinceDate` (type: `string`):

Include reviews on or after this date in YYYY-MM-DD format. Use Most recent sorting.

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

Language and region for localized review fields, such as en-US or de-DE. Original review text is also returned.

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

Proxy settings for accessing public listing pages and reviews.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.airbnb.com/rooms/12937"
    }
  ],
  "maxItems": 0,
  "sortBy": "most-recent",
  "locale": "en-US",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `reviews` (type: `string`):

All collected reviews 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 = {
    "startUrls": [
        {
            "url": "https://www.airbnb.com/rooms/12937"
        }
    ],
    "locale": "en-US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/airbnb-reviews-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 = {
    "startUrls": [{ "url": "https://www.airbnb.com/rooms/12937" }],
    "locale": "en-US",
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/airbnb-reviews-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 '{
  "startUrls": [
    {
      "url": "https://www.airbnb.com/rooms/12937"
    }
  ],
  "locale": "en-US"
}' |
apify call mlg14/airbnb-reviews-scraper --silent --output-dataset

```

## MCP server setup

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