Airbnb Scraper: Listings, Prices & Availability by Location avatar

Airbnb Scraper: Listings, Prices & Availability by Location

Pricing

Pay per event

Go to Apify Store
Airbnb Scraper: Listings, Prices & Availability by Location

Airbnb Scraper: Listings, Prices & Availability by Location

Search Airbnb by city or pasted search URL and export listings with prices, ratings, rooms and coordinates. Add dates for exact stay prices and availability, or pass listing URLs for full details: host, amenities, rules. Public logged-out data, no login.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Changefeeds Tools

Changefeeds Tools

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

By Changefeeds Tools. An Airbnb scraper that searches by location, prices by date and reports availability. Search a city or paste an airbnb.com search URL to export listings with prices, ratings, room counts, coordinates and badges. Add check-in and check-out dates for real stay prices, or pass specific listing URLs for full details: description, amenities, house rules, host and sub-ratings, plus price and availability for your dates. It reads the same public pages a logged-out browser on airbnb.com gets. No Airbnb account, cookie, or login.

Who it is for: short-term-rental analysts comparing a market, hosts watching competitors' prices, travel and relocation tools, and anyone who needs a spreadsheet of what Airbnb shows for a place and a date range.

Input

FieldTypeDefaultWhat it does
locationslist of stringsA place name per line ("Lisbon, Portugal"), or a full airbnb.com/s/... search URL copied from your browser. A pasted URL is used as-is, with all its filters (room type, amenities, map area).
listingUrlslist of stringsairbnb.com/rooms/<id> URLs (any Airbnb country domain) or bare numeric ids. One detail row each.
checkIn / checkOutYYYY-MM-DDnoneOptional; give both or neither. With dates, search prices are for your stay and listing URLs also get a price and availability.
adultsinteger 1-162Guests. Changes which listings show and their prices.
currency3-letter codeUSDEUR, GBP, JPY, ... A pasted search URL that already has currency= keeps its own.
minPrice / maxPriceintegernoneAirbnb's own per-night price filter, in currency. Searches only.
maxItemsinteger 1-270100Max listings per location.
includeDetailsbooleanfalseAlso open each search result's listing page and add the detail fields (one extra page per listing; see Pricing).
proxyproxy settingsApify Proxy, residentialAirbnb blocks many datacenter IPs, so requests go through residential proxy by default.

Search a city, cheapest way:

{ "locations": ["Lisbon, Portugal"], "maxItems": 20 }

Priced for your dates, with details:

{ "locations": ["Porto, Portugal"], "checkIn": "2026-11-20", "checkOut": "2026-11-23", "currency": "EUR", "minPrice": 60, "maxPrice": 150, "includeDetails": true, "maxItems": 50 }

Specific listings, with availability:

{ "listingUrls": ["https://www.airbnb.com/rooms/12537150", "1144816383633864634"], "checkIn": "2026-11-28", "checkOut": "2026-12-03" }

Sample output

A search row (live run, 2026-09-30, no dates given; images shortened):

{
"type": "listing",
"id": "12537150",
"url": "https://www.airbnb.com/rooms/12537150?check_in=2026-11-28&check_out=2026-12-03&adults=2",
"name": "Deluxe apartment in Chiado",
"title": "Apartment in Santa Maria Maior",
"subtitle": "Álvaro Siza Vieira building in Chiado, with a modern interior and A/C",
"roomType": null,
"propertyType": "Apartment",
"area": "Santa Maria Maior",
"city": null,
"lat": 38.7118,
"lng": -9.13868,
"rating": 4.95,
"reviewsCount": 625,
"isNew": false,
"isSuperhost": null,
"guestFavorite": true,
"price": {
"amount": 795,
"currency": "USD",
"qualifier": "total",
"nights": 5,
"originalAmount": null,
"label": "$795 for 5 nights",
"checkIn": "2026-11-28",
"checkOut": "2026-12-03",
"datesChosenByAirbnb": true,
"breakdown": [{ "description": "5 nights x $158.95", "amount": 794.73, "text": "$794.73" }],
"notes": []
},
"available": null,
"unavailableReason": null,
"priceError": null,
"images": ["https://a0.muscache.com/im/pictures/airflow/Hosting-12537150/original/2121117c-5284-43d2-8bdb-dc3723827676.jpg"],
"badges": ["Guest favorite"],
"paymentMessages": ["Free cancellation"],
"bedrooms": 1,
"beds": 2,
"bathrooms": 1,
"guests": null,
"propertyId": null,
"searchInput": "Lisbon, Portugal",
"position": 1,
"hasDetails": false,
"detailsError": null,
"description": null,
"amenities": null,
"houseRules": null,
"highlights": null,
"host": null,
"subRatings": null,
"scrapedAt": "2026-09-30T05:37:35.346Z"
}

With includeDetails on, or for any listingUrls row, the same row also carries the listing page's fields (live run, shortened):

{
"roomType": "Entire home/apt",
"propertyType": "Entire rental unit",
"city": "Porto",
"isSuperhost": true,
"guests": 2,
"hasDetails": true,
"description": "Beautiful studio apartment set in a fully renovated 18th-century building, with air conditioning for heating and cooling…",
"amenities": ["River view", "Garden view", "Hair dryer", "Shampoo", "…38 in total"],
"houseRules": ["Check-in: 3:00 PM - 12:00 AM", "Checkout before 11:00 AM", "Self check-in with keypad", "2 guests maximum", "No pets"],
"highlights": ["Top 10% of homes", "Self check-in", "Vibrant neighborhood"],
"host": { "id": "245989121", "name": "Isabel & Igor", "isSuperhost": true, "isVerified": true, "rating": 4.9, "reviewsCount": 4616, "yearsHosting": 7, "responseRate": "Response rate: 100%", "responseTime": "Responds within an hour" },
"subRatings": { "accuracy": 4.93, "checkIn": 4.97, "cleanliness": 4.91, "communication": 4.98, "location": 4.95, "value": 4.89 }
}

A listing URL with dates it is booked for comes back as "available": false, "unavailableReason": "Those dates are not available", "price": null. With dates it is free for, price holds the booking-box total, e.g. "$795 for 5 nights", with "available": true.

A failed input gets a free status row instead:

{ "type": "status", "input": "https://www.airbnb.com/rooms/1", "source": "listingUrls", "status": "not_found", "error": "Listing 1 not found (removed, unlisted, or never existed).", "checkedAt": "2026-09-30T05:37:53.680Z" }

status is one of:

  • not_found: the listing is gone, or Airbnb did not recognise the place name (it fell back to a generic map area).
  • invalid: the entry is not a place, search URL, listing URL or id (a /rooms/ URL put in locations is pointed at listingUrls), or a duplicate listing.
  • blocked: Airbnb's bot protection refused the request from every proxy IP tried.
  • error: any other HTTP or network failure.

The key-value store record OUTPUT holds a run summary: counts by type, charged_events, stopped_reason, truncated_inputs, and per location the search URL used, Airbnb's own heading ("Over 1,000 homes in Lisbon"), pages fetched and why it stopped. Every run leaves at least one dataset row, so a quiet run is never mistaken for a broken one.

Pricing

Pay per event, nothing else:

  • listing-returned: $0.003 per listing row ($3 per 1,000 listings).
  • details-added: $0.002 extra per listing with details (search results with includeDetails on, and every listingUrls row, since those always come from the listing page). A search row whose listing page could not be read is delivered with detailsError and charged as a plain listing.

So a plain search costs $3 per 1,000 listings, and a search with details or a list of listing URLs costs $5 per 1,000. Proxy traffic is included in the price. Failed inputs (not_found, invalid, blocked, error) are never charged. If you set a maximum total charge for the run, the actor checks the remaining budget before fetching each page, stops cleanly once it is reached, and says so in OUTPUT.stopped_reason ("max_total_charge_reached").

Limits, stated plainly

  • Airbnb shows at most 15 pages of 18 results per search, and the pages overlap. A live 15-page run for "Lisbon, Portugal" returned 242 unique listings, although Airbnb says "Over 1,000 homes". When a search hits that wall the actor marks it truncated and lists it in OUTPUT.truncated_inputs. To get more, split the search: run the same place in price bands (minPrice/maxPrice, e.g. 0-80, 80-150, 150-300, 300+), by neighbourhood, or paste smaller map-area search URLs. maxItems is capped at 270 for this reason.
  • Without dates, the prices are for dates Airbnb picked. Airbnb then prices each result for its own sample stay (often 5 nights, different per listing). The row says so: price.datesChosenByAirbnb: true, with those dates in price.checkIn / price.checkOut. Give dates for comparable prices.
  • Prices are what the card or booking box shows, in the display format Airbnb chooses for your currency and region. Depending on the region that may be before taxes. price.breakdown and price.notes carry Airbnb's own lines. Hotels can show "Price may vary by room type".
  • isSuperhost is null on plain search rows unless the card shows a Superhost badge; search cards do not say when a host is not one. The listing page (includeDetails) gives the real value.
  • Listing URLs get a price only with dates. The listing page has no price on its own. With dates, the actor makes one more request for the listing's booking box. If that call fails, the row is still delivered with priceError.
  • Search results include what Airbnb puts in them, such as "Featured hotel" placements (badges, propertyId set).
  • Not supported: vanity /h/... links (use the /rooms/<id> URL), experiences, reviews text, calendars beyond the dates you give, and anything that needs a login. The actor never solves CAPTCHAs and never uses an account. If Airbnb challenges a request, it retries from a new proxy IP (up to 8 times), then reports a free blocked row.
  • It stays polite: search pages one at a time, at most 2 listing pages in flight, at least 500 ms between requests, 20 s timeouts, and HTTP 429/5xx retried with backoff (up to 3 retries, Retry-After honoured and capped at 60 s).
  • Airbnb changes its site. The actor reads the data embedded in Airbnb's own pages, and it finds the booking-box query id in Airbnb's current JavaScript at run time rather than hard-coding it. A layout change can still break a field until the actor is updated; a page without the expected data becomes an error row, never a silently empty result.

FAQ

Do I need an Airbnb account? No. Only public, logged-out pages.

Can I use my own search with filters? Yes. Set up the search on airbnb.com (any country domain), copy the URL from the address bar and put it in locations. Its filters are kept. Your checkIn, checkOut, adults, currency and price fields are only added where the URL does not already set them.

Why fewer listings than "Over 1,000 homes"? See the 15-page limit above: split the search into price bands.

Why is price null? On a listing URL without dates, Airbnb shows no price. On one with dates, the listing may be unavailable for them (see available and unavailableReason), or the booking-box call failed (priceError).

Local development

pnpm --filter @mmnm/airbnb test # unit tests on saved Airbnb responses, no network
pnpm --filter @mmnm/airbnb build

node src/main.ts runs the actor locally with Apify's local storage (./storage, input in storage/key_value_stores/default/INPUT.json); leave proxy out of the input to fetch directly from your own connection.