Airbnb Rooms URLs Scraper & Guest Complaint Reviews
Pricing
from $2.99 / 1,000 results
Airbnb Rooms URLs Scraper & Guest Complaint Reviews
Airbnb Rooms URLs Scraper extracts room listing URLs from Airbnb’s search interface with precision. Ideal for scaling data pipelines, monitoring markets, building datasets, or feeding other Airbnb actors with clean, structured room URLs.
Pricing
from $2.99 / 1,000 results
Rating
0.0
(0)
Developer
SimpleAPI
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Airbnb Review Scraper — Listings, Ratings and Complaint Flags
This Airbnb review scraper pulls room listings and the guest reviews underneath them as linked parent and child rows: review text, star rating, reviewer name, localized date, the host's public reply, Airbnb's own topic tags, and a per-review complaint-keyword flag. Star-rating, topic-tag and full-text filters run on Airbnb's own servers before a byte reaches you, so even a capped run still surfaces the worst feedback first. Hospitality operators, revenue managers and market researchers use it to audit guest sentiment at scale — from a single room to a portfolio of listings — without opening a browser.
What is the Airbnb Review Scraper?
The Airbnb Review Scraper takes one or more Airbnb room URLs (or bare room IDs) and returns each listing as a parent row plus every guest review as a child row underneath it, delivered as a structured Apify dataset — JSON by default, with CSV, Excel and XML export built into the Apify platform. It talks directly to Airbnb's public listing page and its internal review endpoint; no Airbnb account, login or API key is required to run it.
Key capabilities:
- 🏠 Accepts direct room URLs or bare numeric room IDs in
startUrls; any entry that isn't a room URL is resolved through Airbnb's own search results instead, returning up to 20 matching listings per keyword - 💬 Guest reviews come back as child rows, sorted server-side by Airbnb — default is lowest-rated-first, so a capped run still keeps the worst feedback
- ⭐ A star-rating ceiling (
maxReviewRating), Airbnb's own per-listing review topic tag (reviewTag) and its full-text review search (reviewSearchText) all filter on Airbnb's servers, before the page is even downloaded - 🚩 Local, case-insensitive complaint-keyword matching (with short suffix tolerance) flags each review and rolls the hits up into a per-listing frequency table
- 👤 Optional host-profile enrichment adds a nested profile object to the listing row for one extra request
- 🛡️ Automatic proxy escalation — none → datacenter → residential — triggers the moment a page comes back without its expected data container
What data can I extract with the Airbnb Review Scraper?
Every run writes two row types into one dataset — listing rows carrying the property, host and rating data, and review rows carrying the guest text — told apart by the type, isChild and parentId columns.
Listing identity, media and policies (parent rows)
| Field | Example Value | Use Case |
|---|---|---|
id / listingId | "12937" | stable key to join parent and child rows |
listingTitle / title | "Sunny room in the heart of downtown" | display name / PDP headline |
listingUrl | https://www.airbnb.com/rooms/12937 | canonical link, tracking params stripped |
url | https://www.airbnb.com/rooms/12937?locale=en-US¤cy=USD | the full localized URL Airbnb served |
requestedUrl | https://www.airbnb.com/rooms/12937 | the exact input value that produced the row |
thumbnail | https://a0.muscache.com/... | share-card image |
androidLink / iosLink | airbnb://... | native app deep links |
propertyType / roomType | "Entire rental unit" / "entire_home" | segment by property or space type |
homeTier | 1 | Airbnb's internal listing tier |
personCapacity | 4 | party-size fit check |
sharingConfigTitle | "Charming Loft · Entire home" | fallback title, used only if title is empty |
description / htmlDescription | "Newly renovated loft…" | plain-text and raw-HTML listing copy |
subDescription | {title, items} | Airbnb's bullet-point summary block |
seoTitle / metaDescription | Airbnb's own SEO copy | on-page metadata |
breadcrumbs | [{linkRoute, linkText}] | Airbnb's own category trail |
highlights | [{title, subtitle, icon, type}] | badges such as "Self check-in" |
brandHighlights.hasGoldenLaurel | true | Guest Favourite badge flag |
images | [{caption, imageUrl, orientation}] | photo gallery |
amenities | [{title, values:[...]}] | full amenities list, grouped by section |
houseRules.general | [{title, values:[...]}] | house rules, grouped by section |
cancellationPolicies | [{title, policyName, policyId}] | cancellation terms shown on the listing page |
location / locationSubtitle | "New York, United States" / "Manhattan" | display location strings |
locationDescriptions | [{title, content, mapMarkerRadiusInMeters}] | Airbnb's "what's nearby" text blocks |
coordinates.latitude / coordinates.longitude | 40.7128 / -74.006 | mapping and geofencing |
descriptionOriginalLanguage / language | "en" / "en-US" | source language / run locale |
checkIn / checkOut | "2026-09-01" | dates echoed back from the input |
timestamp | ISO 8601 UTC | when Airbnb generated the listing payload |
scrapedAt | ISO 8601 UTC | when this Actor wrote the row |
Host and co-host (parent rows)
| Field | Example Value | Use Case |
|---|---|---|
host.id / host.contextualUserId | "50124" | host identifiers used for profile lookup |
host.name / host.profileImage | "Maria" / image URL | display name and avatar |
host.isSuperHost / host.isVerified | true | trust/status badges |
host.highlights / host.about / host.hostDetails | array / text / array | host bio and stated details |
host.ratingCount / host.ratingAverage | 312 / 4.85 | host-level review stats |
host.timeAsHost | {years, months} | hosting tenure |
host.profile | nested object | only present when enrichHostAndCoHostProfiles is on |
coHosts | [{contextualUserId, name, profilePictureUrl}] | co-hosts listed on the property |
Review summary and applied filters (parent rows)
| Field | Example Value | Use Case |
|---|---|---|
rating.accuracy / .checking / .cleanliness / .communication / .location / .value | 4.6 each | per-category star breakdown |
rating.guestSatisfaction / rating.reviewsCount | 4.8 / 312 | overall score and Airbnb's total review count |
lowestRatedCategory / lowestRatedCategoryScore | "cleanliness" / 4.3 | the category dragging the listing down |
reviewsUrl | .../rooms/12937/reviews | link to the reviews page |
reviewsCountTotal / reviewsFetched / reviewsFilteredOut | 312 / 140 / 40 | Airbnb's total, raw pages read, and rows dropped by filters |
reviewPagesRequested / reviewsStatus | 3 / "ok" | pagination cost and outcome per listing |
reviewTagsAvailable | [{name, localizedName, count}] | Airbnb's own topic tags for this listing, with counts |
reviewsScraped / lowRatingReviewsCount | 100 / 76 | rows kept vs. rows at/below the rating ceiling |
reviewRatingBreakdown | {"1":10,"2":20,"3":30,"4":25,"5":15} | kept-review count per star |
averageScrapedReviewRating | 2.9 | average of the kept (usually low) reviews only |
hostResponseCount / hostResponseRate | 18 / 0.18 | how often the host replied among kept reviews |
complaintKeywordMatchCount / complaintKeywordFrequency | 42 / {"dirty":12,"noise":9} | keyword hits, rolled up per listing |
reviewLanguages | ["en","es"] | languages seen among kept reviews |
appliedReviewFilters | {maxReviewRating, reviewTag, reviewSearchText, reviewSortBy, onlyComplaintKeywordMatches, complaintKeywords} | exact filter values used for this run, echoed for audit |
type / isChild / parentId | "listing" / false / null | row-type discriminators |
Guest review fields (child rows)
| Field | Example Value | Use Case |
|---|---|---|
type / isChild / parentId | "review" / true / "12937" | row-type discriminators, parentId joins to the listing |
listingId / listingTitle / listingUrl / requestedUrl | copied from the parent | denormalized for standalone filtering of the review table |
reviewId | 987654321 | Airbnb's review ID |
reviewRating | 2 | 1–5 star rating |
reviewText / reviewTextTranslated | "The room smelled of mold and the AC was broken." | guest's own words / Airbnb's translation, if any |
reviewLanguage | "en" | language of the review |
reviewCreatedAt / reviewLocalizedDate | ISO date / "August 2026" | raw timestamp and Airbnb's display date |
reviewerName / reviewerId / reviewerContextualUserId | "Alex" / "98765" | reviewer identity |
reviewerProfileUrl / reviewerProfileImage | URL | reviewer's public profile link and avatar |
reviewerLocation | "Austin, Texas" | reviewer's self-reported location |
hostResponse / hostResponseDate / hasHostResponse | "We apologize and refunded the cleaning fee." | host's public reply, if any |
reviewHighlight / reviewHighlightType | Airbnb-generated snippet | Airbnb's own auto-highlighted excerpt |
ratingAccessibilityLabel | "Rated 2 out of 5 stars" | Airbnb's screen-reader label for the rating |
complaintKeywordHits / complaintKeywordHitCount / hasComplaintKeywordHit | ["dirty","broken"] / 2 / true | which of your complaintKeywords matched |
isLowRating | true | whether this review is at or below maxReviewRating |
scrapedAt | ISO 8601 UTC | when this Actor wrote the row |
Guest reviews and star ratings
Each review row carries reviewRating (1–5), the review text itself (reviewText, plus reviewTextTranslated when Airbnb serves one), the reviewer's display name and profile link, and the host's public hostResponse text where one exists. The parent listing row rolls every kept review into reviewRatingBreakdown — a count per star, 1 through 5 — plus hostResponseRate and complaintKeywordFrequency. A concrete use: filter listings where hostResponseRate is below 0.5, a proxy for hosts who habitually ignore guest complaints, entirely from the parent rows without opening a single review.
Filtering and targeting fields
startUrls accepts a direct room URL or ID for a surgical single-listing pull, or a bare keyword (a city, neighbourhood or property style) resolved through Airbnb's own search for up to 20 matching listings. reviewTag narrows to one of Airbnb's own topic buckets (CLEANLINESS, LOCATION, AMENITIES, and others — the tags a given listing actually has, with counts, come back in reviewTagsAvailable). reviewSearchText runs Airbnb's server-side full-text review search. maxReviewRating sets the star ceiling that decides which reviews count as "complaints" in the first place.
Why not build this yourself?
Airbnb's room and review data isn't exposed through a general-purpose public API, so a DIY pull means reverse-engineering the same internal web endpoints this Actor already handles.
Three things break a first attempt fast:
- TLS fingerprinting. Airbnb's edge inspects the TLS handshake itself, not just the headers. This Actor impersonates a real Chrome 131 fingerprint (via
curl_cffi) on every request; a plainrequests/httpxclient typically gets an HTTP 200 shell page with no data container instead of the real listing. - The reviews endpoint is an undocumented GraphQL persisted query. Its operation hash lives in a versioned JS bundle that Airbnb rotates over time. This Actor extracts the current hash from the room page's own JS chunk at run time, and only falls back to a pinned hash (last verified against the live bundle on 2026-08-02) if that discovery fails — logging a loud warning when it does.
- Blocked and genuinely unavailable look identical at first glance. A refused exit IP and a de-listed room both answer HTTP 200 — one as an interstitial shell, the other as a well-formed but empty payload. Treating them the same either burns proxy escalation on a room that was never coming back, or silently drops a room that just needed a better IP. This Actor checks for the actual result container to tell the two apart, and only escalates proxies (datacenter, then residential) for the former.
Add non-English locale handling — Airbnb serves a signed POST handoff to a country subdomain instead of the page for most locales — and pagination that has to track review counts across up to 200 pages per listing, and a weekend script turns into an ongoing maintenance job. This Actor exists so you don't re-build and re-tune all of it.
How to use data extracted from Airbnb?
Property managers and hospitality operators
Set maxReviewRating to 3 or 4 and leave complaintKeywords at its default list (dirty, smell, noise, broken, mold, bug, rude, cold, leak, refund, cancel, and more) to get a standing complaint feed for a portfolio of listings. The output's hostResponse and hostResponseRate fields show which properties are being answered and which are being ignored, and lowestRatedCategory on the parent row points straight at the category (cleanliness, communication, location…) dragging a listing's score down — before it shows up as a public 3-star average.
Agencies and consultants auditing client portfolios
Run the same startUrls list on a schedule and diff complaintKeywordFrequency and averageScrapedReviewRating run over run to benchmark a client's properties against each other, or re-run with enrichHostAndCoHostProfiles on to attach superhost status and tenure to a competitive audit deliverable.
Market research and intelligence
Point startUrls at a city or neighbourhood keyword instead of a room URL to pull a market sample through Airbnb's own search (capped at 20 listings per keyword), then aggregate reviewTagsAvailable and rating.* category scores across the sample to map where a submarket's guest complaints cluster — cleanliness in one neighbourhood, noise in another.
AI agents and automated pipelines
complaintKeywordHits and isLowRating are pre-computed per review, so an agent or pipeline can act on them directly — routing a listing into a ticketing system, a Slack alert, or a RAG index of guest sentiment — without re-implementing keyword matching downstream.
🔼 Input sample
Only startUrls is required; every other field has a working default.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
startUrls | Yes | array | One or more Airbnb room URLs or bare room IDs. Non-URL entries are resolved via Airbnb search (up to 20 results each). Default: two sample room URLs. | ["https://www.airbnb.com/rooms/12937"] |
maxItems | No | integer | Caps parent listing rows only; review rows are capped separately. 0 = every listing supplied. Minimum 0. Default 0. | 50 |
scrapeReviews | No | boolean | Fetches review text and metadata, not just a count. One extra request per 50 reviews. Default true. | true |
maxReviewRating | No | integer | Keeps reviews at or below this star rating. Minimum 1, maximum 5. Applied after Airbnb returns each page. Default 4. | 3 |
reviewSortBy | No | string (enum) | Order reviews are requested from Airbnb: lowest-rated, most-recent, most-relevant, highest-rated. Default lowest-rated. | "lowest-rated" |
maxReviewsPerListing | No | integer | Stops after this many kept reviews per listing (counted after rating and keyword filters). Minimum 0, maximum 1000. 0 = every matching review. Default 100. | 200 |
reviewTag | No | string | Only reviews under this Airbnb topic tag (e.g. CLEANLINESS, LOCATION). An unknown tag returns zero reviews silently. Default "" (no filter). | "CLEANLINESS" |
reviewSearchText | No | string | Airbnb's own server-side full-text review search. Default "" (no filter). | "mold" |
complaintKeywords | No | array | Words flagged in review text, case-insensitively, with short suffix tolerance. Clear the list to disable flagging. Default: ["dirty","smell","noise","broken","mold","mould","bug","rude","cold","leak","refund","cancel"]. | ["mold","refund"] |
onlyComplaintKeywordMatches | No | boolean | Drop kept reviews that hit none of complaintKeywords. Default false. | false |
enrichHostAndCoHostProfiles | No | boolean | Fetches and attaches the host's public profile (one extra request). Falls back to enrichUserProfiles if unset. Default false. | false |
enrichUserProfiles | No | boolean | Legacy alias for enrichHostAndCoHostProfiles, kept for backward compatibility. Only used if the preferred key is unset. Default false. | false |
checkIn | No | string | Check-in date, YYYY-MM-DD. Sent to Airbnb as check_in. Invalid formats are ignored (logged), not fatal. | "2026-09-01" |
checkOut | No | string | Check-out date, YYYY-MM-DD. Sent to Airbnb as check_out. | "2026-09-05" |
adults | No | integer | Party size sent with the listing request. Minimum 1. | 2 |
children | No | integer | Children in the party. Minimum 0. | 0 |
infants | No | integer | Infants under 2. Minimum 0. | 0 |
pets | No | integer | Service animals / pets. Minimum 0. | 0 |
locale | No | string (enum) | Language/region for the listing page and review request; 89 locale options. Default en-US. Examples: en-GB, es-ES, fr-FR, de-DE, ja-JP. Full list in the Actor's input editor. | "en-US" |
currency | No | string (enum) | Currency header sent with every request; 47 currency options. Default USD. Examples: EUR, GBP, CAD, AUD. Full list in the Actor's input editor. | "USD" |
proxyConfiguration | No | object | Leave default to auto-escalate none → datacenter → residential on a blocked page. Set a group or your own proxy URLs to use them for every request instead. Default {"useApifyProxy": false}. | {"useApifyProxy": true} |
{"startUrls": ["https://www.airbnb.com/rooms/12937", "https://www.airbnb.com/rooms/53997462"],"maxItems": 0,"scrapeReviews": true,"maxReviewRating": 3,"reviewSortBy": "lowest-rated","maxReviewsPerListing": 200,"reviewTag": "","reviewSearchText": "","complaintKeywords": ["dirty", "smell", "noise", "broken", "mold", "bug", "refund"],"onlyComplaintKeywordMatches": false,"enrichHostAndCoHostProfiles": false,"locale": "en-US","currency": "USD","proxyConfiguration": { "useApifyProxy": true }}
Common pitfall: reviewTag only accepts the exact tag string a listing actually carries — an unrecognized or misspelled value (e.g. "clean" instead of "CLEANLINESS") returns zero reviews with no error. Run once with reviewTag empty, read the listing row's reviewTagsAvailable column for that listing's real tags and counts, then re-run with the exact value. Separately, keep locale on an English variant unless you've verified another one works for your target listings — Airbnb serves a domain-switch redirect instead of the listing page for most non-English locales, which produces no row for that URL at all.
🔽 Output sample
Output is a single Apify dataset holding both row types (listing and review), typed and normalized on every run. Export it as JSON, CSV, Excel or XML directly from the Apify Console or API — no extra configuration needed.
Listing row (trimmed — nested arrays shown with one sample element):
{"id": "12937","listingId": "12937","type": "listing","isChild": false,"parentId": null,"listingTitle": "Sunny room in the heart of downtown","listingUrl": "https://www.airbnb.com/rooms/12937","url": "https://www.airbnb.com/rooms/12937?locale=en-US¤cy=USD","propertyType": "Entire rental unit","roomType": "entire_home","personCapacity": 4,"location": "New York, United States","coordinates": { "latitude": 40.7128, "longitude": -74.006 },"rating": {"accuracy": 4.6,"checking": 4.7,"cleanliness": 4.3,"communication": 4.8,"location": 4.9,"value": 4.5,"guestSatisfaction": 4.7,"reviewsCount": 312},"lowestRatedCategory": "cleanliness","lowestRatedCategoryScore": 4.3,"host": {"id": "50124","name": "Maria","isSuperHost": true,"ratingCount": 312,"ratingAverage": 4.85},"images": [{ "caption": "Living room", "imageUrl": "https://a0.muscache.com/...", "orientation": "LANDSCAPE" }],"amenities": [{ "title": "Bathroom", "values": [{ "title": "Hair dryer", "icon": "ICON", "available": true }] }],"reviewsCountTotal": 312,"reviewsFetched": 140,"reviewsFilteredOut": 40,"reviewsStatus": "ok","reviewTagsAvailable": [{ "name": "CLEANLINESS", "localizedName": "Cleanliness", "count": 28 }],"reviewsScraped": 100,"lowRatingReviewsCount": 76,"reviewRatingBreakdown": { "1": 10, "2": 20, "3": 30, "4": 25, "5": 15 },"averageScrapedReviewRating": 2.9,"hostResponseCount": 18,"hostResponseRate": 0.18,"complaintKeywordMatchCount": 42,"complaintKeywordFrequency": { "dirty": 12, "noise": 9, "broken": 7 },"appliedReviewFilters": {"maxReviewRating": 3,"reviewTag": null,"reviewSearchText": null,"reviewSortBy": "RATING_ASC","onlyComplaintKeywordMatches": false,"complaintKeywords": ["dirty", "smell", "noise", "broken", "mold", "bug", "refund"]},"scrapedAt": "2026-08-04T12:00:03Z"}
Review row (trimmed):
{"type": "review","isChild": true,"parentId": "12937","listingId": "12937","listingTitle": "Sunny room in the heart of downtown","listingUrl": "https://www.airbnb.com/rooms/12937","reviewId": 987654321,"reviewRating": 2,"reviewText": "The room smelled of mold and the AC was broken the entire stay.","reviewLanguage": "en","reviewLocalizedDate": "August 2026","reviewerName": "Alex","reviewerProfileUrl": "https://www.airbnb.com/users/show/98765","reviewerLocation": "Austin, Texas","hostResponse": "We apologize and refunded the cleaning fee.","hasHostResponse": true,"complaintKeywordHits": ["mold", "broken"],"complaintKeywordHitCount": 2,"hasComplaintKeywordHit": true,"isLowRating": true,"scrapedAt": "2026-08-04T12:00:03Z"}
⚠️ Billing note: every pushed row — both listing rows and review rows — is charged under the single row_result pay-per-event. Listings that come back blocked or de-listed push no row at all (they're skipped, not charged), so a failed URL is invisible in the dataset rather than appearing as an error row; check the run log for FAILED / UNAVAILABLE entries if you need to know which inputs didn't produce data.
How do you filter and target specific listings?
Four real controls decide what ends up in the dataset. Scope: startUrls takes either a precise room URL/ID for a single audited listing, or a bare keyword resolved through Airbnb's own search for a broader sample (capped at 20 results per keyword — there is no way to raise this from the input). Topic precision: reviewTag and reviewSearchText are both sent to Airbnb's servers, so they narrow the result set (and therefore the run's cost and time) before a single review is downloaded — an unrecognized tag simply returns nothing. Quality threshold: maxReviewRating is the ceiling that defines a "complaint" for this run — set it to 3 for hard negatives only, leave it at the default 4 to catch everything below Airbnb's typical ~4.8★ average, or raise it to 5 to keep every review. Volume: maxItems caps listings, maxReviewsPerListing caps kept reviews per listing (up to the hard ceiling of 1000), and reviews are paginated 50 at a time up to 200 pages — with reviewSortBy left on lowest-rated, the run stops early the moment a full page comes back entirely above your rating ceiling, so a capped budget is spent on the worst reviews first.
Three real targeting examples:
{ "startUrls": ["https://www.airbnb.com/rooms/12937"], "maxReviewRating": 3, "maxReviewsPerListing": 500 }
A single-listing deep audit: only hard negatives (3★ and below), up to 500 of them.
{ "startUrls": ["downtown Austin"], "reviewTag": "CLEANLINESS", "maxReviewRating": 4 }
A neighbourhood-level sample (resolved via Airbnb search, up to 20 listings), narrowed to cleanliness-tagged complaints across all of them.
{ "startUrls": ["https://www.airbnb.com/rooms/53997462"], "complaintKeywords": ["mold", "bug", "refund"], "onlyComplaintKeywordMatches": true }
A pure keyword report: only reviews that actually mention mold, bugs, or a refund — everything else dropped, regardless of star rating.
▶️ Want to try other travel and review scrapers?
| Scraper | What it extracts |
|---|---|
| TripAdvisor Review Scraper — Photo & Visual Content Filters | Hotel reviews with ratings, reviewer profiles, owner responses, and photo-content filters |
| Google Maps Extractor with Reservation & Order Online Links | Google Maps places plus the online-booking platform and deep link behind each one |
How to extract Airbnb data programmatically
This Actor runs as a standard Apify Actor: one API call starts it, one API call reads the finished dataset back as JSON.
Python example
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<store-username>/airbnb-rooms-urls-scraper-and-guest-complaint-reviews").call(run_input={"startUrls": ["https://www.airbnb.com/rooms/12937"],"maxReviewRating": 3,"complaintKeywords": ["mold", "noise", "refund"],})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item.get("type") == "review":print(item["reviewRating"], item["reviewText"], item.get("hostResponse"))
Export to spreadsheets or CRM
Download the dataset directly as CSV or Excel from the Apify Console (or via the API's items?format=csv endpoint) and map columns straight in: listingTitle and listingUrl identify the property, reviewRating and reviewText are the review body, hostResponse is the reply, and complaintKeywordHits lists which flagged words matched — ready to import into a spreadsheet or a CRM's custom-fields import.
Is it legal to scrape Airbnb?
Yes — scraping publicly accessible Airbnb listing and review pages is generally legal; this Actor only reads pages Airbnb serves to any visitor, the same content anyone can see in a browser without logging in. Guest reviews, however, contain personal data — reviewer names, host names, host replies and reviewer-supplied locations — so GDPR (if you or your data subjects are in the EU/UK) and CCPA (California) obligations attach to how you store, use and retain that data, not to the act of reading a public page. Keep collection proportionate to your purpose and be ready to honor deletion or access requests for any personal data you retain. Consult legal counsel for commercial applications involving bulk storage of personal data.
❓ FAQ
What happens if a listing is de-listed or no longer bookable?
The Actor pushes no row at all for that URL. A de-listed, private or removed room returns a well-formed but empty Airbnb payload; the Actor detects this and logs an UNAVAILABLE line rather than writing a blank or broken row, so the dataset never carries a placeholder for a dead listing.
Can I get guest reviews along with the listing data?
Yes, by default (scrapeReviews: true). Reviews come back as child rows with reviewText, reviewRating, reviewerName, reviewLocalizedDate, and hostResponse. The one caveat: maxReviewsPerListing (default 100, hard maximum 1000) caps how many kept reviews come back per listing, counted after your rating and keyword filters are applied.
How accurate is the review and rating data?
The Actor returns review text, star ratings and host replies exactly as Airbnb's server answers at request time — it doesn't re-score or interpret anything. Accuracy of the underlying content depends on Airbnb and the reviewer/host keeping it current; treat scrapedAt as the freshness timestamp for any downstream reporting.
How many listings and reviews can I get per run?
Listings: maxItems defaults to 0 (every URL you supply, no cap); there is no other listing ceiling for direct room URLs. If you supply a keyword instead of a URL, Airbnb's search resolution is capped at 20 listings per keyword — a hard-coded limit, not adjustable from the input. Reviews: maxReviewsPerListing defaults to 100 kept reviews per listing and cannot be raised past 1000; set it to 0 to keep every review that passes your other filters instead.
How do I filter reviews down to just the complaints?
Combine maxReviewRating (the star ceiling — try 3 for hard negatives) with complaintKeywords and set onlyComplaintKeywordMatches: true to drop anything that doesn't mention one of your words. reviewTag and reviewSearchText narrow further on Airbnb's own server before any of that local filtering runs.
Does the Airbnb Review Scraper work with Claude, ChatGPT and AI agent frameworks?
It has no dedicated MCP server integration documented at this time. Any agent framework that can call an HTTP endpoint — via the Apify API or the apify_client SDK — can start a run and read the resulting dataset back as JSON, the same way the Python example above does.
How does this compare to other Airbnb review scrapers?
Compare any Airbnb scraper on three concrete axes: does it return the review text itself or only a count; does it filter by star rating, topic tag and full text on Airbnb's own server (cutting cost and time) rather than downloading everything and filtering locally; and does it expose Airbnb's own per-listing review topic tags (reviewTagsAvailable) so you know what filters are even available before you run one. This Actor does all three, sourced directly from the listing's own review payload.
Can I use this without an Airbnb account or API key?
Yes. The Actor reads Airbnb's public listing and review pages directly; no Airbnb login, account or API key is needed. You only need an Apify account to run the Actor itself.
Conclusion
The Airbnb Review Scraper turns a list of room URLs into structured listing and review data — worst reviews first, complaint keywords flagged, host replies included — without you touching Airbnb's anti-bot layer, its undocumented review API, or its locale handling. It's built for anyone who needs guest sentiment at scale: property managers, agencies auditing portfolios, and researchers mapping complaint patterns across a market. Paste in a room URL or a neighbourhood keyword and start a run on the Apify platform to see the first rows land in your dataset.