Airbnb Review Scraper By Location Search avatar

Airbnb Review Scraper By Location Search

Pricing

from $2.99 / 1,000 results

Go to Apify Store
Airbnb Review Scraper By Location Search

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

SimpleAPI

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

0

Monthly active users

6 days ago

Last modified

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.

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.

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.

FieldExample ValueUse Case
type"review"Tell review rows apart from discovery, skippedListing and listingUnavailable rows
isChildtrueFilter 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
responsenullHost'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

ParameterTypeRequiredDescription
locationQueryarray of stringsNoOne 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.
listingUrlsOrIdsarray of stringsNoSpecific 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: [].
maxListingsPerQueryintegerNoHow many of the listings published for each location to consider, in the order the location publishes them. Minimum 1, maximum 30. Default: 10.
minListingReviewsintegerNoSkip 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.
minListingRatingnumberNoSkip 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.
emitSkippedListingsbooleanNoAdd a row for every listing the screen removed, with its review count, rating and skip reason. Default: true.
reviewsPerDiscoveredListingintegerNoMaximum number of reviews to collect from each listing. A listing with fewer published reviews returns all of them. Minimum 1, maximum 10000. Default: 20.
sortOrderstring (enum)NoOrder 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.
resultLanguagestring (enum)NoLanguage 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.
startUrlsarray of stringsNoCompatibility key. Same as listingUrlsOrIds — values from both are merged. Default: [].
maxCommentsintegerNoCompatibility key. Same as reviewsPerDiscoveredListing, used only when that field is left empty. Minimum 1, maximum 10000. No default.
localestringNoCompatibility key. Same as resultLanguage, used only when that field is left empty. No default.
proxyConfigurationobjectNoConnection 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:

  1. Discovery scopelocationQuery decides which market you're pulling from; listingUrlsOrIds (or startUrls) 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.
  2. Quality thresholdsminListingReviews and minListingRating run 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 by minListingRating alone, so pair it with minListingReviews if you also want to drop unrated listings.
  3. Volume controlsmaxListingsPerQuery caps how many listings from a location are considered (up to 30, in the order the location publishes them), and reviewsPerDiscoveredListing caps 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_DESC and RATING_ASC page 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:

ScraperWhat it extracts
Facebook Reviews Scraper — Headlines, Tags And Reviewer DetailFacebook Page reviews, ratings and reviewer detail
Walmart Reviews Scraper With Seller And Variant DetailsWalmart product reviews with seller and variant data
Udemy Course Reviews Scraper — Instructor Course CatalogUdemy course reviews alongside instructor and catalog data
Goodreads Book Scraper — Review Sentiment AnalysisGoodreads 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 ApifyClient
client = 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.

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.