kestrel actors › Guest & customer reviews
Airbnb Reviews Scraper — Guest Reviews, Ratings & Replies
An airbnb reviews scraper that returns every guest review a listing has, as structured rows: airbnb ratings, the review exactly as the guest wrote it, Airbnb's translation, the language it was written in, the reviewer, the date, the stay type and the host's public reply. It is a scrape airbnb reviews tool for hosts and analysts who want airbnb review data in bulk, not one page at a time.
Run Airbnb Reviews Scraper on Apify →
Airbnb reviews scraper pricing: what one row costs
| Event | What it is | Price | Charged for |
|---|---|---|---|
review | Review row | $0.005 | One guest review: rating, text, original language and translation, reviewer, date, stay type and the host's reply. Listings with no reviews are free. |
Pay per event, one event per delivered review row. Free: listing rows, status rows, reviews removed by minRating, listings with no reviews, unrecognised places, and failed jobs. A listing is harvested once per run however many inputs point at it.
A listing with 441 reviews is 441 rows if you ask for all of them — maxReviewsPerListing and minRating are there to make that a deliberate choice. Verified runs behind this listing cost $0.0002–$0.0003 each in platform compute. Exact per-row pricing is on this page's pricing tab.
Airbnb reviews scraper input reference
Every field of Airbnb Reviews, straight from the actor's input schema.
| Field | Type | Default | What it does |
|---|---|---|---|
startUrls | array | [] | Listing URLs (https://www.airbnb.com/rooms/12345678) to take reviews from, or search URLs (https://www.airbnb.com/s/Lisbon--Portugal/homes) to take reviews from every listing they return. |
listingIds | array | [] | Numeric Airbnb listing ids, e.g. 11562247 — the fastest input when you already know the listings. |
locationQueries | array | [] | Places typed as you would on Airbnb: "Lisbon, Portugal", "Algarve". Every listing found is harvested for reviews. An unrecognised place costs nothing instead of returning listings from all over the world. |
maxListingsPerQuery | integer | 20 | How many listings to take from each place search before harvesting their reviews. Ignored for direct listing inputs. Range 1-280. |
maxReviewsPerListing | integer | 100 | 0 = every review the listing has (Airbnb pages them 50 at a time). N = the first N in the order below. This is the main cost control. Range 0-5000. |
reviewsSort | string (enum) | "most_recent" | Which reviews come first. "Lowest rated" is the fastest way to find complaints; "Most recent" is the one to schedule. One of: most_recent, highest_rated, lowest_rated, most_relevant. |
minRating | integer | 0 | 0 = keep every review. 1-5 = keep only reviews rated at or below this, so a complaint feed costs nothing for the happy ones. Filtered reviews are not charged. Range 0-5. |
locale | string | "en" | Language for Airbnb's translations, e.g. en, es, fr, de. Every review also carries its original text and the language it was written in. |
includeListingRow | boolean | true | Also emit one row per listing with its title, rating, review count and host — useful context alongside the reviews. Never charged. |
sessions | integer | 4 | How many proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately. Range 1-16. |
perIp | number | 1 | Pace for each session. Airbnb tolerates about 1/s per IP. Range 0.1-3. |
proxyConfiguration | object | {"useApifyProxy": true} | Apify Proxy is required: Airbnb blocks datacentre traffic that is not rotated. The default group works. |
Example input
{ "listingIds": ["11562247", "45307024"], "maxReviewsPerListing": 0, "reviewsSort": "lowest_rated", "minRating": 3 }
{ "locationQueries": ["Lisbon, Portugal"], "maxListingsPerQuery": 50, "maxReviewsPerListing": 100 }
Airbnb reviews scraper output fields
Three row types share one dataset: review (one guest review, charged), listing (context for a listing, free) and status (per listing or place, free).
Dataset views in the Apify Console: Overview, Reviews, Listings, Run status.
| Field | Type | What it holds |
|---|---|---|
type | string | Row type: review, listing or status. |
id | string | Airbnb listing id the row belongs to. |
url | string | Listing URL. |
name | string | Listing title, when known. |
reviews_total | integer | How many reviews Airbnb reports for the listing. |
reviews_fetched | integer | Listing rows: how many reviews were fetched for it. |
locale | string | Language requested for translations. |
review_id | string | Airbnb's id for the review. |
rating | string | integer | Star rating the guest gave, 1-5. |
text | string | The review as written, in its original language. |
text_localized | string | Airbnb's translation into the requested language, when it differs. |
language | string | Language the review was written in. |
created_at | string | When the review was posted (UTC). |
date_label | string | Date as Airbnb shows it, e.g. "1 week ago". |
reviewer_name | string | Reviewer's first name. |
reviewer_id | string | Reviewer's Airbnb id. |
reviewer_location | string | Where the reviewer says they are from. |
reviewer_is_superhost | boolean | Whether the reviewer is themselves a Superhost. |
host_reply | string | The host's public reply, when there is one. |
host_reply_date | string | When the host replied. |
highlight | string | The sentence Airbnb highlights from the review. |
stay_type | string | Airbnb's tag for the stay, e.g. a group or family trip. |
status | string | Status rows: ok, unknown_area, no_results or error. |
listings | integer | Status rows: listings harvested. |
reviews | integer | Status rows: review rows delivered. |
filtered | integer | Status rows: reviews dropped by the rating filter, never charged. |
duplicates | integer | Status rows: listings already harvested by another input. |
listing_id | string | Status rows: the listing this job was for. |
query | string | Status rows: the place searched. |
error | string | Status rows: why the job failed, null when it did not. |
fetched_at | string | UTC timestamp when the row was fetched. |
Example row from a real run
Taken from the actor's README — this is the shape your dataset holds.
{
"type": "review", "id": "11562247", "url": "https://www.airbnb.com/rooms/11562247",
"name": "Charming studio in Baixa", "reviews_total": 441, "locale": "en",
"review_id": "1752111840493470784", "rating": "5",
"text": "El departamento es muy lindo. Esta todo muy bien ubicado.",
"text_localized": "The apartment is very nice. Everything is very well located.",
"language": "es", "created_at": "2026-08-14T10:53:27Z", "date_label": "1 week ago",
"reviewer_name": "Luis Armando", "reviewer_id": "313623872",
"reviewer_location": "Buenos Aires, Argentina", "reviewer_is_superhost": false,
"host_reply": null, "host_reply_date": null, "highlight": null, "stay_type": null
}
{ "type": "listing", "id": "11562247", "url": "https://www.airbnb.com/rooms/11562247", "name": "Charming studio in Baixa", "reviews_total": 441, "reviews_fetched": 100, "locale": "en" }
Airbnb reviews scraper in Python, JavaScript and curl
Python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/airbnb-reviews-scraper").call(run_input={
"listingIds": ["11562247"], "maxReviewsPerListing": 0, "reviewsSort": "lowest_rated", "minRating": 3,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["type"] == "review":
print(row["rating"], row["language"], row["text"][:80])
JavaScript (Node)
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/airbnb-reviews-scraper').call({
locationQueries: ['Lisbon, Portugal'], maxListingsPerQuery: 20, maxReviewsPerListing: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'review').length, 'reviews');
curl
curl -X POST "https://api.apify.com/v2/acts/kestrel~airbnb-reviews-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"listingIds":["11562247"],"maxReviewsPerListing":50}'
The same three calls are runnable files in the examples repository: examples/airbnb-reviews-scraper.py|.js|.sh.
Limits and honest notes
ratingis a string as Airbnb returns it ("5"), not a number. Cast it if you are aggregating.reviewer_locationis self-declared and often blank — treat it as a hint, not a fact.- Translations are Airbnb's, not ours.
textis always the original;text_localizedis only present when Airbnb translated it. - An unrecognised place is reported, not guessed. Airbnb answers a nonsense place with listings from all over the world; the run detects that, marks the job
unknown_areaand charges nothing. - Airbnb pages reviews 50 at a time. Deep listings take several requests;
maxReviewsPerListingbounds it. - No host contact details. Not scraped, not exposed, not an input — by design.
FAQ
Does it need an Airbnb API key, login or cookies?
No. There is no public airbnb review api to hold a key for. This uses the same public GraphQL endpoint airbnb.com's own front-end calls, with no session, and it refreshes Airbnb's query hashes automatically if they change.
Can I download Airbnb reviews as CSV or Excel?
Yes. Every run writes an Apify dataset, which exports to CSV, Excel, JSON or XML from the Console, or over the API with ?format=csv. That is the short path to download airbnb reviews for a listing or a market.
How do I get only Airbnb negative reviews?
Set minRating to the ceiling you care about — 2 for one- and two-star, 3 to include mediocre stays — and reviewsSort to lowest_rated. Reviews above the ceiling are dropped before billing, so a complaints feed on a heavily reviewed listing costs cents.
Can I get reviews for every listing in a city?
Yes, with locationQueries plus maxListingsPerQuery. Airbnb caps a single search at roughly 280 listings, so for a large city, run several searches with different filters (price bands, room types) and let the de-duplication merge them.
Are reviews translated?
Each review carries language (what it was written in) and text (the original). When Airbnb offers a translation into your locale, it arrives as text_localized. So airbnb review translation is available without you calling a translation API, and the source text is never lost.
Does it include host replies?
Yes — host_reply and host_reply_date when the host answered publicly. That is how you find listings where negative reviews were left unaddressed.
How many reviews can one listing return?
All of them. Airbnb pages 50 at a time and the run follows the pages until it has what you asked for; 300 unique reviews from a single listing was verified in testing. reviews_total tells you how many exist, so you can check you got the lot.
Can I scrape Airbnb reviews without an API?
That is exactly what this is — airbnb reviews without api access, no key to apply for and no approval process. You call an Apify actor and read rows back.
What does a run cost for bulk work?
Bulk airbnb reviews are billed per delivered row, so cost scales with data rather than with time. Filtering with minRating or capping with maxReviewsPerListing cuts it directly — those rows are never charged.
Which languages and locales are supported?
Any locale Airbnb supports for translations — en, es, fr, de, pt, it, ja, ko and the rest. Set locale and text_localized follows it. The language field is independent: it tells you what the guest actually wrote in.
How to use Airbnb Reviews Scraper
Ready-made runs you can open and start
Each one is a saved input on the Apify Store — open it, press Start, and it runs with the fields already filled in. All 163 across the suite.
| Ready-made run | What it does |
|---|---|
| Airbnb Negative Reviews Across a Market | Lowest-rated first, three stars and below only: the complaints from ten Lisbon listings, with the happy reviews filtered out before billing. |
| Airbnb Sentiment Analysis Dataset (CSV) | Five hundred recent Barcelona guest reviews with rating, original text, language, date and host reply - a labelled corpus, exportable as CSV. |
| Bulk Airbnb Reviews by Listing ID | Three known listing ids, a hundred reviews each, no search step: the fastest way to refresh reviews for properties you already track. |
| Scrape Airbnb Reviews from a Search URL | Paste an Airbnb search URL and every listing behind it is harvested for reviews - the filters you set in the UI decide the sample. |
| Airbnb Host Reviews, Highest Rated First | Pull the five-star guest reviews for your own listings first - the quotes that belong on a direct booking site, with reviewer name and date. |
| Airbnb Review Translation into Spanish | Every Madrid review in Spanish plus its original text and detected language, so a mixed-language market reads as one column. |
| Download Airbnb Reviews for One Listing | The full review history of a single listing, up to a thousand reviews in one run - due diligence before buying or taking over a property. |
| Review Monitoring for Your Own Listings | Ten newest reviews for each of five listings, cheap enough to run every morning - a guest feedback feed you can wire to Slack or email. |
| Airbnb Reviews for a City, by Listing | A census of an Algarve market: every listing with its total review count and how many were fetched, so you can rank properties by traction. |
| Airbnb Competitor Reviews for a Comp Set | Twenty Miami Beach competitors, forty reviews each: eight hundred rows of guest feedback about the properties you compete with, not your own. |
Related scrapers
Run Airbnb Reviews Scraper
The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/airbnb-reviews-scraper