Airbnb Review Scraper By Location Search
Pricing
from $2.99 / 1,000 results
Airbnb Review Scraper By Location Search
Airbnb Review Scraper extracts reviews from listings found by location search, including ratings, review text, dates, reviewer details, listing information, and URLs. Ideal for guest sentiment analysis, property research, competitor monitoring, and short-term rental market intelligence.
Pricing
from $2.99 / 1,000 results
Rating
0.0
(0)
Developer
SimpleAPI
Maintained by CommunityActor stats
0
Bookmarked
5
Total users
0
Monthly active users
6 days ago
Last modified
Categories
Share
Airbnb Review Scraper — Reviews, Ratings and Listings by Location
Airbnb Review Scraper By Location Search finds the listings published for a city, region or neighbourhood and collects guest reviews from the ones that clear a quality bar you set — review count, rating, review text, review date, reviewer name, host reply and the listing's own published review total. The screen runs before a single review is fetched, so a listing that doesn't qualify costs nothing. Revenue managers, short-term-rental agencies and data teams use it to build a location-scoped Airbnb review dataset in one run, with full discovery provenance on every row. From here, the fields below map straight into a spreadsheet, a database or a pricing model.
🧭 What is Airbnb Review Scraper By Location Search?
Airbnb Review Scraper By Location Search is an Apify Actor that turns a place name into the Airbnb listings published there, screens each discovered listing against a minimum review count and a minimum rating, and collects structured guest reviews from the ones that pass. It reads Airbnb's own public search and listing pages — no Airbnb account, login or API key is required to run it. Every run writes one JSON record per review, per screened-out listing, per unreachable listing, and per location searched, all distinguishable by a type field in the same dataset.
Key features:
- 📍 Discovers listings by place — hand it a city, region or neighbourhood (
Lisbon,Kyoto) and it resolves the place before returning any listing. - 🧹 Screens before it collects — a minimum review count and minimum rating are applied to each discovered listing before any of its reviews are fetched, so a listing that fails the screen costs nothing.
- 💬 Full review records — text (cleaned and raw), star rating, review date, reviewer, host reply, and the listing's own published review total.
- 🔀 Four review orders — newest first, highest rated, lowest rated, or Airbnb's own ranking.
- 🌍 50 locale options for the localized review-text fields.
- 🔗 Direct listing URLs or IDs can be mixed into the same run as location search, and are always collected regardless of the screen.
📊 What data can I extract with Airbnb Review Scraper By Location Search?
Each review row carries the guest's text and rating alongside the listing context that qualified it and the location that surfaced it. Every field below is copied from the row-building code in src/main.py.
| Field | Example Value | Use Case |
|---|---|---|
type | "review" | Tell review rows apart from discovery, skippedListing and listingUnavailable rows |
isChild | true | Filter to review-level rows only |
parentListingId | "13231767" | Join a review back to its listing |
scrapedAt | "2026-08-10T14:22:05Z" | Track when the row was collected |
roomId | "13231767" | Airbnb's numeric listing ID |
startUrl | "https://www.airbnb.com/rooms/13231767" | Link back to the listing page |
locale | "en-US" | Confirm which language/region was requested |
id | "1382948573920184756" | Airbnb's review ID, useful for deduplication |
language | "en" | Language the review was actually posted in |
text | "Great location, walking distance to everything.\nThe host was very responsive." | Cleaned review text, ready for spreadsheet or NLP use |
localizedText | "Great location, walking distance to everything.\nThe host was very responsive." | Review text in the requested resultLanguage, cleaned |
localizedReview | {"comments": "Great location..."} | Airbnb's localized-review object, kept in full |
localizedDate | "July 2026" | Human-readable review date, as Airbnb displays it |
localizedReviewerLocation | "2 years on Airbnb" | Reviewer's displayed location or tenure line — not reliably a place, see note below |
createdAt | "2026-07-14T09:12:33Z" | Machine-sortable review timestamp |
reviewHighlight | "Clean and comfortable" | Airbnb's short pull-quote for the review, when one exists |
highlightType | "positive" | Category Airbnb assigned to the highlight |
rating | "5" | Star rating as a string; null on reviews with no rating |
response | null | Host's reply text, when one exists |
ratingAccessibilityLabel | "Rating, 5 stars" | Accessibility label Airbnb attaches to the rating |
textRaw | "Great location, walking distance to everything.<br/>The host was very responsive." | Original text exactly as served, before newline cleanup |
reviewerName | "Marta" | Reviewer's first name |
reviewerId | "1608886371300404735" | Airbnb's numeric reviewer ID |
hostName | "Ana" | Host's display name |
reviewUrl | "https://www.airbnb.com/rooms/13231767/reviews" | Link to the listing's reviews page |
reviewee | {"id": "9137641", "firstName": "Ana", ...} | Full host-side object: id, firstName, hostName, pictureUrl, profilePath, profilePicture |
reviewer | {"id": "1608886371300404735", "firstName": "Marta", ...} | Full guest-side object, same shape as reviewee |
Listing context on every review
Each review row also carries the listing-level facts that decided whether it was collected at all: listingStatus ("ok", "partial" or "unavailable" — whether the full review history was reachable), listingReviewTotal (the listing's own published review count), overallRating (the listing's star rating at discovery time), screenReviewCount (the review count the screen tested against) and screenCountVerified (a boolean, whether screenReviewCount matched listingReviewTotal exactly). Together these let you re-derive why a listing passed the screen without a second lookup, and flag any listing whose review history could not be fully retrieved.
Location and discovery fields
discoveredFrom (the location query that surfaced the listing, null for direct URLs/IDs), resolvedLocation (the place Airbnb actually resolved the query to), discoveryRank (position on that location's results page), searchKeyword (duplicate of discoveredFrom, kept for compatibility) and listingFromSearch (the listing URL, populated only for location-discovered listings). These are what you group and filter on when comparing markets or auditing which listings a given search actually returned.
🛠️ Why not build this yourself?
Airbnb does not offer a public API for listing search or review data. Airbnb closed public API applications in 2016, and its only current developer surface is an invite-only Preferred Software Partner / Connectivity API aimed at property-management software integrations — not third-party search or review retrieval (confirmed via public developer documentation and third-party API guides, checked 2026-08-15).
Building this yourself against Airbnb's web app means solving several problems this Actor already handles: recovering the GraphQL operationId and API key from a page's inline scripts and static JS bundles (they change between deploys), parsing listing cards out of a server-rendered data-deferred-state script tag rather than a documented endpoint, decoding relay global IDs to numeric listing IDs, telling a genuine block apart from a legitimate large response (a refusal page and a 900 KB result page can both contain the word "forbidden"), and distinguishing a listing whose reviews the source declines to serve from a listing that genuinely has zero reviews — the source answers both with HTTP 200, and only the second holds a numeric review count.
Build it yourself if you need a one-off pull of a handful of listings you already know. Use this Actor if you need repeatable, location-driven collection with a pre-collection quality screen and structured output.
💼 How to use data extracted from Airbnb
Revenue managers and short-term-rental hosts
Search your own market (locationQuery: ["Austin, TX"]) with minListingRating set to your target's rating and minListingReviews set to a volume you trust, then read overallRating, listingReviewTotal and the review text for the listings that clear the bar. The output lands as one dataset per run — export it as CSV and drop it straight into a comp-set spreadsheet next to your own occupancy numbers.
STR management agencies and consultants
Run one location per client market, or several markets in a single run, with emitSkippedListings left on so you retain a record of which listings were excluded and why (skipReason). Repeating the same locations on a schedule turns the discovery rows (listingsDiscovered, listingsQueried, listingsSkipped) into a lightweight audit trail for a client-facing report, without re-explaining your screening rules every time.
Market research and intelligence
Sweep several cities in one run (locationQuery: ["Lisbon", "Porto", "Kyoto"]) and compare overallRating and listingReviewTotal distributions across markets, or mine text for recurring guest complaints and praise by neighbourhood using resolvedLocation. Because discovery and review rows share the same dataset, no join step is needed to connect a review back to its market.
AI agents and automated pipelines
Feed text, rating and response into a sentiment-classification or summarization step, or use the Actor as a tool call in an agent pipeline that answers "what are guests saying about listings in [place]?" from fresh data rather than a stale index. The Actor is callable as a standard Apify Actor run from any HTTP client or the apify-client SDK, so it drops into an existing agent's tool set without custom scraping code.
🔼 Input sample
| Parameter | Type | Required | Description |
|---|---|---|---|
locationQuery | array of strings | No | One or more places to look up — a city, region or neighbourhood. Each entry is resolved to a real place before any listing is accepted; an unrecognised entry returns an empty result for that entry instead of unrelated homes. Default: none. |
listingUrlsOrIds | array of strings | No | Specific Airbnb listings to collect in the same run, as full URLs (https://www.airbnb.com/rooms/12937) or bare listing IDs (12937). Always collected, never removed by the screen. Default: []. |
maxListingsPerQuery | integer | No | How many of the listings published for each location to consider, in the order the location publishes them. Minimum 1, maximum 30. Default: 10. |
minListingReviews | integer | No | Skip a discovered listing unless it publishes at least this many reviews. 0 keeps every listing. A listing whose review count is not published yet is skipped whenever this is above 0. Minimum 0, maximum 5000. Default: 10. |
minListingRating | number | No | Skip a discovered listing whose overall rating is below this value. A listing with no published rating is not removed by this rule alone. Minimum 0, maximum 5. Default: 4.0. |
emitSkippedListings | boolean | No | Add a row for every listing the screen removed, with its review count, rating and skip reason. Default: true. |
reviewsPerDiscoveredListing | integer | No | Maximum number of reviews to collect from each listing. A listing with fewer published reviews returns all of them. Minimum 1, maximum 10000. Default: 20. |
sortOrder | string (enum) | No | Order reviews are read in: MOST_RECENT (Newest first), RATING_DESC (Highest rated first), RATING_ASC (Lowest rated first), BEST_QUALITY (Airbnb's own ranking). Default: MOST_RECENT. |
resultLanguage | string (enum) | No | Language used for the localized review fields; the region part is kept in the output to confirm what was requested. Default: en-US. Full list of the 50 supported codes below. |
startUrls | array of strings | No | Compatibility key. Same as listingUrlsOrIds — values from both are merged. Default: []. |
maxComments | integer | No | Compatibility key. Same as reviewsPerDiscoveredListing, used only when that field is left empty. Minimum 1, maximum 10000. No default. |
locale | string | No | Compatibility key. Same as resultLanguage, used only when that field is left empty. No default. |
proxyConfiguration | object | No | Connection settings. Default: {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}. |
Input example:
{"locationQuery": ["Lisbon", "Kyoto"],"listingUrlsOrIds": ["https://www.airbnb.com/rooms/12937"],"maxListingsPerQuery": 10,"minListingReviews": 10,"minListingRating": 4.0,"emitSkippedListings": true,"reviewsPerDiscoveredListing": 20,"sortOrder": "MOST_RECENT","resultLanguage": "en-US"}
Common pitfall: locationQuery and listingUrlsOrIds (or startUrls) are both optional individually, but the run fails with "Nothing to collect" if every one of them is empty. And listingUrlsOrIds only accepts a full /rooms/<digits> URL or a bare numeric ID — a host profile link or a username is not resolved to anything; it is skipped with a warning telling you to use locationQuery instead.
🔽 Output sample
Every run pushes typed, normalized JSON to the Actor's dataset, exportable as JSON, CSV, Excel, XML or an HTML table, or readable directly through the Apify API. The dataset holds four row types, told apart by the type field.
review — one row per guest review (the Actor's primary, billed output):
{"type": "review","isChild": true,"parentListingId": "13231767","scrapedAt": "2026-08-10T14:22:05Z","roomId": "13231767","startUrl": "https://www.airbnb.com/rooms/13231767","locale": "en-US","id": "1382948573920184756","language": "en","text": "Great location, walking distance to everything.\nThe host was very responsive.","localizedText": "Great location, walking distance to everything.\nThe host was very responsive.","localizedReview": { "comments": "Great location, walking distance to everything.<br/>The host was very responsive." },"localizedDate": "July 2026","localizedReviewerLocation": "2 years on Airbnb","createdAt": "2026-07-14T09:12:33Z","reviewHighlight": "Clean and comfortable","highlightType": "positive","rating": "5","response": null,"ratingAccessibilityLabel": "Rating, 5 stars","textRaw": "Great location, walking distance to everything.<br/>The host was very responsive.","reviewerName": "Marta","reviewerId": "1608886371300404735","hostName": "Ana","reviewUrl": "https://www.airbnb.com/rooms/13231767/reviews","discoveredFrom": "Lisbon","resolvedLocation": "Lisbon, Portugal","discoveryRank": 2,"searchKeyword": "Lisbon","listingFromSearch": "https://www.airbnb.com/rooms/13231767","listingStatus": "ok","listingReviewTotal": 920,"overallRating": 4.83,"screenReviewCount": 920,"screenCountVerified": true,"reviewee": {"id": "9137641","firstName": "Ana","hostName": "Ana","pictureUrl": "https://a0.muscache.com/im/pictures/user/abc123.jpg","profilePath": "/users/show/9137641","profilePicture": "https://a0.muscache.com/im/pictures/user/abc123.jpg"},"reviewer": {"id": "1608886371300404735","firstName": "Marta","hostName": null,"pictureUrl": "https://a0.muscache.com/im/pictures/user/def456.jpg","profilePath": "/users/show/1608886371300404735","profilePicture": "https://a0.muscache.com/im/pictures/user/def456.jpg"}}
discovery — one row per location searched, free of charge:
{"type": "discovery","isChild": false,"scrapedAt": "2026-08-10T14:20:01Z","searchQuery": "Lisbon","resolvedLocation": "Lisbon, Portugal","locationResolved": true,"listingsDiscovered": 10,"listingsQueried": 3,"listingsSkipped": 7,"minListingReviewsApplied": 10,"minListingRatingApplied": 4.0,"reviewRequestsIssued": 3,"reviewSortUsed": "MOST_RECENT"}
skippedListing — one row per listing the screen removed, free of charge (only when emitSkippedListings is true):
{"type": "skippedListing","isChild": false,"scrapedAt": "2026-08-10T14:20:04Z","roomId": "1746615384391785355","startUrl": "https://www.airbnb.com/rooms/1746615384391785355","discoveredFrom": "Lisbon","resolvedLocation": "Lisbon, Portugal","discoveryRank": 3,"screenReviewCount": 4,"overallRating": 5.0,"skipReason": "only 4 reviews, below the minimum of 10"}
listingUnavailable — one row per listing whose reviews could not be served, free of charge:
{"type": "listingUnavailable","isChild": false,"scrapedAt": "2026-08-10T14:21:12Z","roomId": "27551038","startUrl": "https://www.airbnb.com/rooms/27551038","discoveredFrom": "Lisbon","resolvedLocation": "Lisbon, Portugal","discoveryRank": 5,"listingStatus": "unavailable","listingReviewTotal": null,"overallRating": 4.9,"screenReviewCount": 340,"note": "Listing data is not being served for this listing right now"}
Only review rows are charged, through the row_result event. discovery, skippedListing and listingUnavailable rows carry no charge — filter a dataset export on type == "review" to see billed rows only, or keep all four types for full run provenance at no extra cost.
🎯 How do you filter and target specific reviews?
This Actor's precision comes from where you set the screen, not from a category picker — there is no category axis, since a location search returns every listing type Airbnb publishes for that place. Three controls do the targeting:
- Discovery scope —
locationQuerydecides which market you're pulling from;listingUrlsOrIds(orstartUrls) bypasses discovery entirely for listings you already know, and those are never removed by the screen below. Use location search to explore a market, and direct IDs to track a fixed panel across runs. - Quality thresholds —
minListingReviewsandminListingRatingrun before any review is fetched, so raising either one directly lowers cost, not just row count. A listing with no published rating is not excluded byminListingRatingalone, so pair it withminListingReviewsif you also want to drop unrated listings. - Volume controls —
maxListingsPerQuerycaps how many listings from a location are considered (up to 30, in the order the location publishes them), andreviewsPerDiscoveredListingcaps reviews per listing (up to 10,000, or fewer if the listing has fewer). ⚠️sortOrder: "BEST_QUALITY"uses Airbnb's own ranking, which repeats rows as it pages — part of a long review history can be unreachable under it.MOST_RECENT,RATING_DESCandRATING_ASCpage without repeating and are the safer choice for a complete pull.
{ "locationQuery": ["Austin, TX"], "minListingReviews": 50, "minListingRating": 4.7, "maxListingsPerQuery": 20 }
{ "listingUrlsOrIds": ["https://www.airbnb.com/rooms/12937", "https://www.airbnb.com/rooms/27551038"], "reviewsPerDiscoveredListing": 500 }
{ "locationQuery": ["Lisbon", "Porto", "Kyoto"], "minListingReviews": 0, "minListingRating": 0, "sortOrder": "MOST_RECENT" }
▶️ Want to try other Airbnb scrapers?
There is no other Airbnb listing or review scraper in this catalogue yet. For comparable review-extraction work on other platforms:
| Scraper | What it extracts |
|---|---|
| Facebook Reviews Scraper — Headlines, Tags And Reviewer Detail | Facebook Page reviews, ratings and reviewer detail |
| Walmart Reviews Scraper With Seller And Variant Details | Walmart product reviews with seller and variant data |
| Udemy Course Reviews Scraper — Instructor Course Catalog | Udemy course reviews alongside instructor and catalog data |
| Goodreads Book Scraper — Review Sentiment Analysis | Goodreads book reviews with sentiment scoring |
💻 How to extract Airbnb data programmatically
Every run is a standard Apify Actor call: one API request starts it, one dataset endpoint returns structured JSON back — no Airbnb credentials involved.
Python example
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/airbnb-review-scraper-by-location-search").call(run_input={"locationQuery": ["Lisbon"],"minListingReviews": 20,"minListingRating": 4.5,"reviewsPerDiscoveredListing": 50,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item.get("type") == "review":print(item["roomId"], item["rating"], item["text"][:80])
Export to spreadsheets or CRM
Use the Actor's dataset export button, or the Apify API's dataset export endpoint, to pull results as CSV. Map roomId and startUrl to your listing key column, rating and text to review columns, and overallRating / listingReviewTotal to a listing-summary sheet — the same row shape works whether you're loading a spreadsheet or a CRM's custom-object importer.
⚖️ Is it legal to scrape Airbnb?
Scraping publicly available Airbnb listing and review pages is generally legal — the data is published for anyone visiting the site to see, without a login. Review rows in this Actor's output do include personal data (reviewerName, hostName, and the nested reviewer/reviewee objects with first names and profile pictures), so GDPR (if you or your data subjects are in the EU/UK) and CCPA (California residents) considerations apply to how you store and use it — you need a lawful basis for holding personal data, not just for collecting it. Airbnb's own Terms of Service also govern automated access to the site, independent of any data-protection regime. Consult legal counsel for commercial applications involving bulk storage of personal data.
❓ Frequently asked questions
How does it handle a listing whose reviews can't be retrieved?
It's reported as its own row with type: "listingUnavailable", listingStatus: "unavailable" and listingReviewTotal: null — never folded into a listing that simply has zero reviews. The Actor requires positive evidence (a reviews container with a numeric total) before accepting any response as an answer, so a page the source declines to serve is never silently reported as an empty review history.
Can I get listing-level rating data along with each review?
Yes. Every review row carries overallRating, listingReviewTotal and screenReviewCount from the listing it belongs to, so no join against a separate listing dataset is needed.
How accurate is the review data?
The Actor returns review text, rating and dates exactly as Airbnb serves them at request time — cleaned of Airbnb's <br/> line-break encoding in text, and preserved byte-for-byte in textRaw. Accuracy after that depends on Airbnb itself; a review edited or removed on the site after your run will not retroactively change already-collected rows.
How many records can I get per run?
Per location, up to maxListingsPerQuery (max 30) listings are considered, each up to reviewsPerDiscoveredListing (max 10,000) reviews. In practice, both ceilings are outer bounds: a location's search page typically publishes fewer listings than the maximum since the Actor reads that one results page per location without deeper pagination, and a listing returns all it has once its published review count is exhausted.
What's the most effective way to target only well-reviewed listings?
Combine minListingReviews and minListingRating — both apply before any review is fetched, so a listing that fails either one is reported (if emitSkippedListings is on) but costs nothing. A listing with a published review count but no published rating still passes the rating check; if you want to exclude unrated listings entirely, set minListingRating above 0.
Does Airbnb Review Scraper By Location Search work with Claude, ChatGPT and AI agent frameworks?
It's callable as a standard Apify Actor run from any HTTP client or agent framework's tool-use layer, via the Apify API or the apify-client SDK — there is no dedicated MCP server for this Actor documented at this time.
Can I use it without an Airbnb account or API key?
Yes. No Airbnb login, account or API key is used or required — the Actor reads Airbnb's public pages directly. You only need an Apify account to run the Actor itself.
Does it work with my existing input file from another Airbnb review scraper?
Likely yes for the common keys. startUrls, maxComments and locale are accepted as compatibility keys and are merged with listingUrlsOrIds, reviewsPerDiscoveredListing and resultLanguage respectively, so an older input file continues to work unchanged.
Conclusion
Airbnb Review Scraper By Location Search turns a place name into a screened, review-rich Airbnb dataset in one run — no listing URLs to collect by hand, no low-value listings pulled into your review costs. Structured JSON output, a pre-collection quality screen, and clear provenance on every row make it fit for revenue management, agency reporting, market research and AI-agent pipelines alike. Start a run from the Apify Console with a location and a review threshold, and read the results straight from the dataset.