Airbnb Listings Scraper - Search by City, Nightly Prices avatar

Airbnb Listings Scraper - Search by City, Nightly Prices

Pricing

from $1.40 / 1,000 results

Go to Apify Store
Airbnb Listings Scraper - Search by City, Nightly Prices

Airbnb Listings Scraper - Search by City, Nightly Prices

Get Airbnb listings by city with dates and guests: price per night, total price, rating, beds and URL - or paste listing URLs / room IDs. An Airbnb reviews scraper too: review text, rating, date and host reply for any listing, lowest rated first for complaints. No API key, no login.

Pricing

from $1.40 / 1,000 results

Rating

0.0

(0)

Developer

Flash Scrape

Flash Scrape

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 hours ago

Last modified

Share

Search Airbnb by location and get every listing with its nightly price. Type a place (with optional dates and guests) and the actor returns one row per listing from Airbnb's own search results: the per-night rate you would pay (the after-discount rate when a discount applies, with the pre-discount rate and the discount line beside it), the total for the stay, the strike-through price when the card shows one, rating and review count, bed/bath info, coordinates, Superhost / Guest-favorite badges, free-cancellation flag, cover photo and a working listing URL. No browser, no login, no API key.

  • Search by location - "Lisbon, Portugal", "Brooklyn, New York", any place you would type on Airbnb; several per run
  • Real prices for real dates - nightly + total from Airbnb's own price breakdown ("4 nights x $131.27"), in the currency you choose; leave the dates empty and Airbnb picks them (the status message tells you)
  • URLs are constructed, never scraped - the listing URL is built from the decoded listing id, never scraped from a link
  • Details on demand - enrichDetails adds description, property/room type, capacity, sub-ratings and host from each listing page; or run the classic detail mode on your own listing URLs / room IDs
  • Guest reviews - set reviews to with_listings or only for one row per review (text, stars, date, the host's reply), filtered by stars or date before billing
  • Fail-loud - a run that finds nothing tells you why (place not recognised, blocked, 0 results, unreachable) and you are not charged for it

Quick start

Open the actor, keep the defaults ("Lisbon, Portugal", 2 adults, USD, 50 listings) and press Save & start. Then put in your own places and dates, and export CSV, JSON or Excel from the dataset tab - or call it from the API and read the dataset programmatically.

A real row

Parsed from the Paris search page saved on 2026-09-30 (currency=USD, 2026-11-10 → 2026-11-14, 2 adults), provenance columns shortened:

{
"room_id": "4088083",
"name": "Typical French Appt for 2. Eiffel tower",
"title": "Apartment in Paris",
"url": "https://www.airbnb.com/rooms/4088083",
"latitude": 48.84035,
"longitude": 2.30049,
"rating": 4.88,
"review_count": 81,
"is_new": false,
"bedrooms": null,
"beds": 1,
"bathrooms": 1.0,
"bed_info": "1 sofa bed · 1 bath",
"image": "https://a0.muscache.com/im/pictures/7daf6fbf-e5d4-437f-80ef-ea3f4b4cdd00.jpg",
"images_count": 21,
"price_total": 526.0,
"price_total_raw": "$526",
"price_original": null,
"price_nightly": 131.27,
"price_nightly_raw": "$131.27",
"price_nightly_before_discount": null,
"discount_label": null,
"discount_amount": null,
"fees_amount": null,
"nights": 4,
"currency": "USD",
"currency_symbol": "$",
"checkin": "2026-11-10",
"checkout": "2026-11-14",
"is_superhost": null,
"is_guest_favorite": true,
"badges": ["Guest favorite"],
"free_cancellation": true,
"search_query": "Paris, France",
"resolved_place": "Paris, France",
"search_rank": 1,
"scraped_at": "2026-09-30T13:06:00Z"
}

How it works

Search mode (the default): the actor requests Airbnb's server-rendered search page for your place, dates and guests, reads the JSON Airbnb embeds in it, and follows Airbnb's own page cursors - up to 15 pages of 18 results per location. Rows are de-duplicated by listing id across pages and locations. If the page did not answer the place you asked for, the actor falls back to Airbnb's search API with a request hash it discovers at run time (nothing is hardcoded, so a site update does not silently break the run). Measured 2026-09-30: from an Apify datacenter IP with no proxy the search page and two API pages answered HTTP 200 (search page 1-1.8 s); from a Mac, 20 of 20 API calls 0.7 s apart answered 200 with no rate-limit headers. Search pages and the API are always requested in English (prices, nights and ratings are parsed from the English rendering); your locale applies to the listing pages read by enrichDetails and detail mode. A run that is throttled or blocked retries through a fresh proxy session when Apify Proxy is enabled - and never sleeps or retries past the run's timeout: whatever was collected is delivered.

Detail mode (leave the locations empty, give listingUrls or roomIds): unchanged from before - each listing page embeds JSON-LD plus a deferred-state blob; the actor fetches the page, parses both and reconciles them into one row.

Where the price comes from. Airbnb does not render a price on the listing page server-side; the price lives on the search surface, quoted for a date range and guest count. That is why search mode carries price_nightly / price_total and detail mode does not - and why a quote always names its checkin / checkout.

Output columns (search mode), with measured fill

Fill measured 2026-09-30 on 342 grid rows across 19 search pages (Paris + Lisbon); rows marked † re-measured on the 148 fixture rows the test suite runs on:

ColumnFilledNotes
room_id, url100%id decoded from Airbnb's listing key; URL built from it
name, title, subtitle100%title is Airbnb's short label, e.g. "Apartment in Paris"
latitude, longitude100%
price_nightly100%the effective per-night rate: the after-discount total ÷ nights when Airbnb's breakdown carries a discount line, otherwise the rate on the breakdown line "N nights x
price_nightly_raw100%the breakdown rate exactly as printed - on a row with a discount_label this is the rate before the discount (× nights = the pre-discount total, not price_total)
price_nightly_before_discount, discount_amount55% †the pre-discount per-night rate and the discount, on every discounted stay: 30 rows carry a discount line in the breakdown (discount_label set, e.g. "Early booking discount", 5 of them with no strike-through on the card), 52 show the discount only as a strike-through (discount_label null, amount = original − total); null = no discount
fees_amount1% †extra fee lines in the breakdown ("Resort fee" on two hotel rows); null otherwise
price_total (+ _raw), currency, currency_symbol, nights98%a few hotel rows carry no price
price_original57%only when the quote is discounted
rating, review_count96%the rest are "New place to stay" (is_new: true, review_count: 0) or, on a few hotel/unrated rows, no rating at all (rating and review_count null, is_new false)
bed_info, bathrooms98%from Airbnb's short labels ("1 sofa bed · 1 bath")
beds97%
bedrooms64% †null when the card shows only beds (the sample row above) - enrichDetails fills it from the listing page
image, images_count (images with includeImages)100%the search card ships at most 29 pictures, so images_count 29 means 29 or more (22% of rows)
checkin, checkout100%your dates, or the ones Airbnb chose when you gave none
is_superhost / is_guest_favorite, badgesbadge on 16% / 58%the card has one badge slot: on a Guest-favorite (or "Featured hotel" / "Luxe") row the Superhost status is unknown → is_superhost null unless enrichDetails reads the listing page; false only when the card shows no badge
free_cancellation100%true/false
search_query, search_rank100%which location found the row and where it ranked
resolved_place100%the place Airbnb resolved your query to: "Springfield" → "Springfield, MO", "Algarve, Portugal" → "Faro, Portugal" (measured 2026-09-30)
scraped_at, actor_build, actor_run_id100%provenance

With enrichDetails: true every row also carries the detail-mode columns below (description, property_type, room_type, person_capacity, the six sub-ratings, host_name, host_id, host_rating_count, locality) plus enriched: true/false. A missing value is null; the column set never shifts within a mode, so your CSV import is stable across runs.

Detail-mode columns (unchanged): room_id, name, url, description, property_type, room_type, person_capacity, bedrooms, beds, bathrooms, rating, review_count, rating_accuracy, rating_cleanliness, rating_location, rating_value, rating_checkin, rating_communication, is_superhost, is_guest_favorite, host_name, host_id, host_rating_count, locality, latitude, longitude, image, images_count, provenance.

How many listings per location

Airbnb's search surface exposes about 270 result slots per location (15 pages × 18), and it repeats listings across pages: reading 10 pages returned 180 rows and 139 distinct listings for Paris (77%), 151 for Lisbon (84%). Pages beyond that add fewer new rows, and the run stops a location as soon as a page adds nothing new. To cover a large city, run several narrower queries (neighbourhoods, nearby towns) - duplicates across queries are removed inside the run. maxItems caps the whole run; a later location gets the remaining budget.

Reviews

Off by default. Set reviews and every listing the run reads - the ones your search found (after maxItems) or the listing URLs / room IDs you gave - also delivers its guest reviews, one row per review.

InputDefaultWhat it does
reviewsoffoff = listing rows only, exactly as before. with_listings = each listing row followed by its review rows. only = review rows only: the listings are still read to know which rooms, but their rows are not delivered or billed.
maxReviewsPerListing50Review rows per listing (1-2000), counted after the filters below.
reviewsSortMOST_RECENTOr MOST_RELEVANT (Airbnb's default order), LOWEST_RATED or HIGHEST_RATED. LOWEST_RATED with reviewsMaxRating: 3 reads the complaints and stops paging as soon as higher ratings begin.
reviewsMaxRating5Keep reviews with this many stars or fewer - 3 = the complaints.
reviewsSince-YYYY-MM-DD: keep reviews written on or after it. With MOST_RECENT a listing stops paging as soon as a whole page is older.

Both filters run before billing: a skipped review is never delivered or charged. A listing stops when it has maxReviewsPerListing matching reviews, when Airbnb has no more (a short page, or every review read), when reviewsSince ends it, or at a page limit (enough 24-review pages for your maximum, plus 20 more while reviewsMaxRating is below 5). The run's status message names every listing that hit a limit, had no reviews, or could not be read.

Review row columns: record_type ("review"), review_id, room_id, listing_url, listing_name, rating (the guest's stars, 1-5), comments (the review as written, in its own language - never a machine translation; language says which), created_at (ISO timestamp), localized_date ("1 week ago"), reviewer_first_name, reviewer_location, reviewer_tenure, host_response, host_response_date, search_query (search mode) and the provenance columns. Airbnb prints either a place or the reviewer's tenure under the name: the place goes to reviewer_location, "7 years on Airbnb" to reviewer_tenure. With reviews on, listing rows carry record_type ("listing"), reviews_total (Airbnb's review count for the listing) and review_tags (Airbnb's review topics, e.g. {"name": "Hot tub", "count": 164}).

Privacy: a review row carries the reviewer's first name and the place Airbnb shows under it - never a reviewer or host id, profile link or profile picture. Those identify a private person across the whole site, and no review analysis needs them.

How it reads them: reviews are not in the listing page. They come from the persisted query Airbnb's own listing page calls (24 per page); its request hash changes with Airbnb's site updates, so the actor discovers it at run time from Airbnb's own scripts and, when Airbnb rejects a stale one, discovers it once more. If it cannot be found the run delivers no review rows and charges nothing for reviews (with with_listings the listing rows are still delivered; with only nothing is delivered or charged), and says so. Measured 2026-10-02 from Apify's cloud (a private test run, with and without the datacenter proxy): 6 of 6 review pages answered HTTP 200 in about 1.5 s each including a 1 s pause (this actor pauses 1.2 s between pages), complete for 16/16 and 26/26 reviews.

Price: each delivered review row is one result event - $0.002 per review row on the free plan - $2 per 1,000, less on higher plans (the Pricing tab is the authority). Other Airbnb review scrapers in the Store charged $0.0019 to $0.005 per review on 2026-10-02.

Input reference

FieldTypeDefaultNotes
locationQueriesarray["Lisbon, Portugal"]Places to search. Non-empty = search mode.
checkIn, checkOutstring-YYYY-MM-DD. Both or neither; with neither, Airbnb picks the dates and the status message says so.
adultsinteger21-16
children, infants, petsinteger0
currencystringUSDISO code; currency_symbol on the row shows what Airbnb printed.
maxItemsinteger50Per run, across all locations; hard cap 500.
enrichDetailsbooleanfalseAlso read each listing page and merge its details (one extra page per listing).
includeImagesbooleanfalseAdd the images array.
localestringenLanguage for the listing pages (enrichDetails and detail mode), e.g. en, fr, es, de. Search pages are always requested in English - prices, nights and ratings are parsed from the English rendering.
listingUrls, roomIdsarraysample URLsDetail mode - used when locationQueries is empty or still the sample "Lisbon, Portugal" and you supplied listing URLs / room IDs.
proxyConfigurationobjectoffApify Proxy; not needed for the search surface on 2026-09-30, residential proxies if a run reports being blocked.
reviewsstringoffwith_listings / only add one row per guest review (see Reviews).
maxReviewsPerListinginteger50Review rows per listing, after the filters; 1-2000.
reviewsSortstringMOST_RECENTOr MOST_RELEVANT, LOWEST_RATED, HIGHEST_RATED.
reviewsMaxRatinginteger5Keep reviews with this many stars or fewer.
reviewsSincestring-YYYY-MM-DD: keep reviews written on or after it.

Pricing

Pay per listing - the result event is charged once per row delivered: $0.002 per listing on the free plan, $0.0018 on Bronze down to $0.001 on Diamond (live record read 2026-09-30; the Pricing tab on this page is the authority). Zero results cost zero, a run that stops early delivers - and bills - only what it collected, and a maxTotalChargeUsd you set trims the dataset to exactly the rows you paid for. With reviews on, each review row is billed the same way (one result event; with only, listing rows are not delivered or billed), rows are delivered in order (each listing row, then its reviews), so a budget cut keeps the earlier listings and stops at the first row it cannot pay for - the last listing delivered may arrive with only some, or none, of its reviews.

The actor reads public Airbnb pages the way a browser does: no login, no personal account, no bypass of access controls. It is polite by design (one request at a time, a pause between pages, a hard per-location ceiling). You are responsible for how you use the data; keep to Airbnb's terms and to the privacy law that applies to you.

Use cases

  • Pricing benchmarks - what listings in Alfama cost per night for your dates, in one CSV - filter by bedrooms in your sheet (the count is null when Airbnb's card shows only beds)
  • Market scans - listing density, rating distribution, Superhost share and new-listing rate by neighbourhood
  • Revenue management - schedule the same query weekly and track how the market moves before your dates
  • Host lead generation - with enrichDetails, host name, host id and host review count next to every property
  • Review mining - reviewsMaxRating: 3 across a neighbourhood's listings = what guests complain about, with the host's reply beside each

Honest limits

  • ~270 slots per location is Airbnb's ceiling, not ours; narrower queries are the way past it
  • A run that hits an unexpected page shape or an internal error still delivers everything collected up to then and says so in the status message
  • Prices are quotes for the dates and guests you gave (or the dates Airbnb chose); they include Airbnb's fee logic as shown in search and can change by the hour
  • Detail mode still has no price column - the listing page does not carry one
  • The API fallback depends on Airbnb's public search API answering; the default path needs no API at all

Scheduling & integrations

Every input works identically from the API, so this actor drops into schedules and pipelines: run it on an Apify Schedule for a recurring price export, point a webhook at the finished run to push rows into Slack, Google Sheets (via Zapier/Make/n8n) or your warehouse, and page through the dataset with the standard Dataset API. Saved tasks keep your configuration one click away.

FAQ

Why do some rows show different checkin / checkout dates? You left the dates empty, so Airbnb quoted each listing for a short stay of its own choosing (it did the same in its own search UI). Give checkIn / checkOut for one comparable window.

Why is bedrooms null on some rows? Airbnb's search card shows either "N bedrooms" or only the bed line ("1 sofa bed"); we never guess. enrichDetails reads the listing page where the count is stated.

Can I get more than ~270 listings for one city? Split the city into neighbourhoods or nearby towns and list them all in locationQueries; the run de-duplicates across them.

What happens when I misspell a place? Airbnb answers a string it cannot resolve with HTTP 200 and its worldwide inspiration feed (18 listings from Brazil, Mexico, Croatia...). The actor detects that (the page carries no canonical location), delivers nothing for that query, bills nothing, and names it in the status message - it never fills your dataset with listings from the wrong continent. Check resolved_place on the rows to see what Airbnb made of an ambiguous name.

Does detail mode still work? Yes, unchanged: leave the locations empty (or keep the sample "Lisbon, Portugal") and give listingUrls or roomIds.


Built by Flash Scrape - measured claims, stable columns, no API keys. Something missing? Open an issue on this actor and ask.


More from Flash Scrape

Same publisher, same rules: no API keys, pay per row, filters run before billing.