# Airbnb Reviews Scraper — Ratings, Dates & Host Replies (`thenetaji/airbnb-reviews-scraper`) Actor

Export every review on an Airbnb stay. Paste one room link or a list and each review comes back with its full text, star rating, the reviewer's name and country, the host's reply, and the exact date it was posted — not the '3 days ago' the page shows.

- **URL**: https://apify.com/thenetaji/airbnb-reviews-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Travel, Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.68 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Reviews Scraper

The Actor exports the guest reviews on an Airbnb stay, one row per review: the full text as written,
the star rating, the reviewer's first name and stated location, the host's public reply, and the
exact date and time the review was posted. A stay that no longer exists is skipped with a warning
rather than returned as an empty record.

### Accepted input

`stay_ids` is required and takes one or more stays. Room page links and bare numeric stay IDs are
both accepted, and the two may be mixed in one list. A link is reduced to its `/rooms/<id>`
segment, so the tracking query string Airbnb appends to shared links can be left in place.

`maxReviews` caps how many reviews are saved per stay, up to 500, and defaults to 100. Reviews come
back newest first, so a smaller number returns the most recent ones rather than an arbitrary slice.
Set it to `0` for as many as the Actor will fetch.

```json
{
  "stay_ids": [
    "https://www.airbnb.com/rooms/4518031",
    "1348596009084608060"
  ],
  "maxReviews": 100
}
```

Only the `/rooms/` identifier resolves. Airbnb search results also carry a `propertyId` on some
stays, which is a number of the same shape and is not interchangeable; see
`Why a stay can return nothing` below.

### Response fields

Every row carries `stay_id`, `stay_url`, `review_id`, `review_text`, `review_rating`, `review_date`,
`review_date_label`, `review_language`, `review_translated`, `review_highlight`, `reviewer_name`,
`reviewer_location`, `reviewer_tenure`, `reviewer_image`, `host_reply`, `host_reply_date`,
`review_images`, `stay_reviews_total`, and `review`.

`review_date` is an absolute UTC timestamp. This is the field the listing page itself does not
give you: it renders relative dates — "3 days ago", "last week" — which are meaningless the moment
a dataset is saved and cannot be sorted, filtered, or compared across runs. Airbnb's own relative
string is still returned as `review_date_label` for anyone who wants the page's wording.

`review_text` is the review in the language the guest wrote it, with `review_language` giving that
language as a two-letter code. `review_translated` holds Airbnb's translation **only when there is
one that actually differs** from the original. Airbnb ships the same text under several fields and
they are frequently identical strings, so a translation that matches the original is reported as
empty rather than duplicated into a second column.

`reviewer_location` and `reviewer_tenure` are two columns from one source. Airbnb shows a single
line beneath each reviewer's name, and it holds either where that guest lives or how long they have
been a member — "Lima, Peru" and "1 year on Airbnb" come through the same field. Across 200
reviewers on two listings it was an exact even split, so they are separated here: whichever applies
to a given review is filled in and the other is empty. A `reviewer_location` column fed straight
from Airbnb would otherwise be half membership ages, which breaks any grouping or filtering done on
it.

`host_reply` is the host's public response, kept verbatim — including any HTML the host's formatting
produced, because stripping it would silently run paragraphs together. `host_reply_date` is
relative, as Airbnb gives no absolute timestamp for replies.

`stay_reviews_total` is the stay's true review count, which is usually far larger than the number
of rows saved. It is repeated on every row so a capped run still records what it left behind.

`review` holds the complete record for anything not flattened into a column of its own.

```json
{
  "stay_id": "4518031",
  "stay_url": "https://www.airbnb.com/rooms/4518031",
  "review_id": "1749563023499856820",
  "review_rating": 5,
  "review_date": "2026-08-10T22:29:25Z",
  "review_date_label": "3 days ago",
  "review_language": "es",
  "review_highlight": "Stayed a few nights",
  "reviewer_name": "Gonzalo",
  "reviewer_location": "Lima, Peru",
  "stay_reviews_total": 635
}
```

### Why a stay can return nothing

Airbnb does not answer an unknown stay ID with a 404. It returns an ordinary response whose payload
is empty, and nothing about the status distinguishes a removed listing from a live one. A stay that
has been delisted behaves the same way.

The Actor treats that as the real answer it is: the stay is logged as not found and skipped, and no
rows are written. It is not reported as an error, because a listing being gone is a legitimate
outcome rather than a failure of the run.

A stay that exists but has never been reviewed is different again, and is logged as such — it
produces no rows because there is nothing to write, not because anything went wrong.

The most common cause of an unexpected empty result is a `propertyId` used in place of a stay ID.
Both appear on Airbnb search results, both are long numbers, and only the stay ID resolves. The `id`
field returned by the Airbnb Search Scraper is always the correct one.

### Behaviour on partial results

A list input is processed to the end regardless of individual failures. A stay that cannot be
fetched, whether removed or erroring upstream, is logged and skipped while the remainder continue,
so one dead link in a list of fifty does not cost the other forty-nine.

Rows are therefore not guaranteed to cover every stay in the input list. Matching on the returned
`stay_id` is reliable; matching on position is not.

Reviews are fetched fifty at a time, so a `maxReviews` of 500 is ten requests per stay and takes
proportionally longer than the default 100.

### Related Actors

The **Airbnb Search Scraper** finds stays by destination when no IDs are held, and its `id` output
feeds directly into this Actor's `stay_ids`.

The **Airbnb Listing Scraper** returns the stay itself — description, amenities, host, and the
review scores broken out by category. Those per-category scores are the aggregate; this Actor
returns the individual reviews behind them.

The **Airbnb Availability Scraper** returns the same stay's booking calendar night by night.

# Actor input Schema

## `stay_ids` (type: `array`):

One or more stays to fetch. Paste room page links, or the numeric stay IDs the search scraper returns — both work, and you can mix them.

## `maxReviews` (type: `integer`):

How many reviews to save for each stay, up to 500. Reviews come back newest first, so a smaller number gives you the most recent ones. Set 0 for as many as this scraper will fetch.

## Actor input object example

```json
{
  "stay_ids": [
    "4518031"
  ],
  "maxReviews": 50
}
```

# Actor output Schema

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

All records scraped by this run

# 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 = {
    "stay_ids": [
        "https://www.airbnb.com/rooms/4518031"
    ],
    "maxReviews": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/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 = {
    "stay_ids": ["https://www.airbnb.com/rooms/4518031"],
    "maxReviews": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/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 '{
  "stay_ids": [
    "https://www.airbnb.com/rooms/4518031"
  ],
  "maxReviews": 50
}' |
apify call thenetaji/airbnb-reviews-scraper --silent --output-dataset

```

## MCP server setup

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