kestrel actors › Hotel & flight prices
Airbnb Scraper — Prices, Availability Calendar & Occupancy
An Airbnb scraper for Airbnb prices and availability: search any place or paste Airbnb URLs and get every listing's nightly rate and total for your dates and guests in any currency, its rating, location and badges — then, per listing, up to 24 months of availability calendar with occupancy data, the full listing details with the exact price breakdown, and reviews. Scrape Airbnb data as rows you own — the short term rental data behind market analysis, an Airbnb price tracker, or a rate check on your own listings — without an Airbnb API key, a login or cookies: it reads the same public endpoints the airbnb.com front-end uses. You pay per row you receive; a place Airbnb does not recognise, an empty search and every status row cost nothing.
Airbnb scraper pricing: what one row costs
| Event | What it is | Price | Charged for |
|---|---|---|---|
listing | Listing row | $0.005 | One Airbnb listing with its price for your dates (or the default displayed price when no dates are given). Listings that could not be priced are free. |
calendar_month | Calendar month | $0.002 | One month of day-by-day availability for one listing (a 12-month calendar row = 12 events). |
review | Review row | $0.003 | One guest review with rating, text, date, reviewer and host reply. |
Pay per row: a listing row that carries a price, each calendar_month (a 12-month calendar row = 12 events), each review row. Listings that could not be priced, unknown places, empty searches and status rows are free, and a listing and stay is billed once per run even if two searches return it. Worked examples: 80 Lisbon listings with prices for a weekend = 80 listing events; the same 80 with a 6-month calendar = 80 + 480 calendar-month events; your 10 listings with details, 12-month calendars and 20 reviews each = 10 + 120 + 200 events. Platform compute for those runs is a fraction of a cent. Exact per-event prices are on this page's pricing tab.
Airbnb scraper input reference
Every field of Airbnb Prices & Availability Calendar — input, straight from the actor's input schema.
| Field | Type | Default | What it does |
|---|---|---|---|
locationQueries | array | — | Places to search exactly as you would type them on Airbnb: "Lisbon, Portugal", "Algarve", "Brooklyn, NY". An unrecognised place produces a status row (unknown_area) and costs nothing instead of random listings. |
startUrls | array | [] | Airbnb search URLs (all their filters are honoured: dates, guests, price, room type, bedrooms, map bounds) or listing URLs (https://www.airbnb.com/rooms/12345678). |
listingIds | array | [] | Numeric Airbnb listing ids to price and/or fetch calendars, details and reviews for — no search needed. |
maxListingsPerQuery | integer | 40 | Distinct listings to collect per place or search URL (Airbnb returns at most about 280 for one search; split big areas into neighbourhoods or price bands for more). Range 1-280. |
checkIn | string | — | Check-in date, YYYY-MM-DD or relative to the run date ("30 days", "6 weeks") — relative dates keep a schedule valid forever. Leave empty for each listing’s default displayed price. |
checkOut | string | — | Check-out date (after check-in), absolute or relative. |
adults | integer | 2 | Adults; prices depend on guests exactly as on Airbnb. Range 1-16. |
children | integer | 0 | Children (2–12). Range 0-10. |
infants | integer | 0 | Infants (under 2). Range 0-5. |
pets | integer | 0 | Pets. Range 0-5. |
currency | string | "USD" | Currency for every price (USD, EUR, GBP, …). Echoed in every row. |
locale | string | "en" | Airbnb locale for names, descriptions and reviews (en, fr, de, es, pt, it, ja, …). |
priceMin | integer | 0 | Minimum nightly price in the currency above; 0 = no minimum. Minimum 0. |
priceMax | integer | 0 | Maximum nightly price in the currency above; 0 = no maximum. Minimum 0. |
roomTypes | array | [] | Restrict to entire homes, private rooms, shared rooms and/or hotel rooms. |
minBedrooms | integer | 0 | Minimum bedrooms. Minimum 0. |
minBeds | integer | 0 | Minimum beds. Minimum 0. |
minBathrooms | integer | 0 | Minimum bathrooms. Minimum 0. |
includeDetails | boolean | false | Fetch each listing’s page: description, amenities, house rules, cancellation policy, category ratings, host, capacity, sleeping arrangement, exact price breakdown for the dates (one extra call per listing; same listing price). |
calendarMonths | integer | 0 | 0 = no calendar. N = a calendar row per listing with day-by-day availability for the next N months (up to 24) and an occupancy summary. Priced per listing-month. Range 0-24. |
maxReviewsPerListing | integer | 0 | 0 = none. N = up to N reviews per listing with rating, text, date, reviewer and host reply. Range 0-200. |
reviewsSort | string (enum) | "most_recent" | Which reviews to take first. One of: most_recent, highest_rated, lowest_rated, most_relevant. |
proxyConfiguration | object | {"useApifyProxy": true} | Airbnb accepts Apify datacenter proxies for these calls; keep the default. |
sessions | integer | 4 | Concurrent egress IPs; each paced at perIp requests per second. Range 1-16. |
perIp | number | 1 | Pace per session. Airbnb tolerated 1 request/second and short bursts in tests. Range 0.1-3. |
Example input
{ "locationQueries": ["Lisbon, Portugal"], "checkIn": "2026-10-03", "checkOut": "2026-10-06", "adults": 2, "currency": "EUR", "maxListingsPerQuery": 80 }
{ "locationQueries": ["Algarve, Portugal"], "checkIn": "30 days", "checkOut": "33 days", "adults": 2, "currency": "EUR", "maxListingsPerQuery": 200, "calendarMonths": 6 }
Airbnb scraper output fields
Row types in one dataset: listing (price for the dates, rating, location, optional details), calendar (day-by-day availability with an occupancy summary), review, status (per place or listing; never charged).
Dataset views in the Apify Console: Overview, Listings, Calendar, Reviews, Run status.
| Field | Type | What it holds |
|---|---|---|
type | string | |
id | string | Numeric Airbnb listing id. |
url | string | Listing URL. |
query | string | The place search that produced the row. |
name | string | Listing name as shown in search. |
title | string | Search title, e.g. 'Apartment in Graça' (or the listing page title with details). |
property_type | string | |
area | string | |
subtitle | string | |
check_in | string | |
check_out | string | |
nights | integer | |
adults | integer | |
children | integer | |
infants | integer | |
pets | integer | |
currency | string | |
locale | string | |
lat | number | |
lng | number | |
location_text | string | |
rating | number | |
reviews_count | integer | |
rating_breakdown | object | |
bedrooms | integer | |
beds | integer | |
bathrooms | integer | |
summary | array | |
price_total | number | Total for the stay as Airbnb displays it (after discounts). |
price_total_display | string | |
price_original | number | Total before discounts, when discounted. |
price_nightly | number | Base nightly rate from Airbnb's breakdown ('3 nights x $140.87'). |
price_nightly_effective | number | Total ÷ nights (what the stay costs per night after discounts). |
price_nights | integer | |
price_breakdown | array | Airbnb's price lines: label, amount (discounts negative), original. |
badges | array | |
is_guest_favorite | boolean | |
is_superhost | boolean | |
free_cancellation | boolean | |
pictures | array | |
details | boolean | True when the listing page was fetched (description, amenities, policies, host, exact breakdown). |
room_type | string | |
person_capacity | integer | |
available | boolean | Whether the listing can be booked for the dates (details only). |
unavailable_message | string | |
cancellation_policy | string | |
house_rules | array | |
description | string | |
amenities | array | |
amenities_count | integer | |
sleeping | array | |
highlights | array | |
host | object | |
months | integer | |
days | array | Calendar rows: one object per day (date, available, available_for_checkin, available_for_checkout, min_nights, max_nights, price). |
nights_30 | integer | |
available_30 | integer | |
occupancy_30 | number | Share of the next 30 nights not available (blocked or booked). |
nights_90 | integer | |
available_90 | integer | |
occupancy_90 | number | |
nights_365 | integer | |
available_365 | integer | |
occupancy_365 | number | |
next_available | string | |
min_nights_typical | integer | |
first_day | string | |
last_day | string | |
review_id | string | |
text | string | |
text_localized | string | |
language | string | |
created_at | string | |
date_label | string | |
reviewer_name | string | |
reviewer_id | string | |
reviewer_location | string | |
reviewer_is_superhost | boolean | |
host_reply | string | |
host_reply_date | string | |
highlight | string | |
stay_type | string | |
reviews_total | integer | |
listing_id | string | |
status | string | |
resolved_place | string | The place Airbnb actually searched (from its canonical URL). |
canonical_url | string | |
pages | integer | |
listings | integer | |
priced | integer | |
duplicates | integer | |
calendars | integer | |
calendar_months | integer | |
reviews | integer | |
detail_errors | integer | |
calendar_errors | integer | |
review_errors | integer | |
error | string | |
fetched_at | string |
Example row from a real run
Taken from the actor's README — this is the shape your dataset holds.
{ "type": "listing", "id": "1018680572065141154", "url": "https://www.airbnb.com/rooms/1018680572065141154", "name": "Lisboa Bica Nature Fast Net AC Heating", "title": "Apartment in Lisbon",
"query": "Lisbon, Portugal", "check_in": "2026-10-03", "check_out": "2026-10-06", "nights": 3, "adults": 2, "children": 0, "infants": 0, "pets": 0, "currency": "USD",
"price_total": 426.0, "price_total_display": "$426", "price_original": 516.0, "price_nightly": 171.73, "price_nightly_effective": 142.0, "price_nights": 3,
"price_breakdown": [{ "label": "3 nights x $171.73", "amount": 515.19 }, { "label": "Early booking discount", "amount": -89.73 }, { "label": "Price after discount", "amount": 425.46 }],
"rating": 4.88, "reviews_count": 156, "bedrooms": 1, "beds": 1, "bathrooms": null, "lat": 38.70975, "lng": -9.14708, "badges": ["Guest favorite"], "is_guest_favorite": true, "is_superhost": false, "free_cancellation": false,
"pictures": ["https://a0.muscache.com/im/pictures/…"], "details": false, "fetched_at": "2026-08-26T09:10:00+00:00" }
{ "type": "calendar", "id": "11562247", "name": "Charming studio in Baixa", "months": 12, "first_day": "2026-08-01", "last_day": "2027-07-31",
"nights_30": 30, "available_30": 6, "occupancy_30": 0.8, "nights_90": 90, "available_90": 47, "occupancy_90": 0.478, "nights_365": 340, "available_365": 137, "occupancy_365": 0.597,
"next_available": "2026-08-29", "min_nights_typical": 3, "days": [{ "date": "2026-08-29", "available": true, "available_for_checkin": true, "available_for_checkout": true, "min_nights": 3, "max_nights": 1125, "bookable": null, "price": null }], "fetched_at": "…" }
Airbnb scraper in Python, JavaScript and curl
Python
from apify_client import ApifyClient
client = ApifyClient("<TOKEN>")
run = client.actor("kestrel/airbnb-prices-calendar").call(run_input={"locationQueries": ["Lisbon, Portugal"], "checkIn": "30 days", "checkOut": "33 days", "adults": 2, "currency": "EUR", "maxListingsPerQuery": 80, "calendarMonths": 3})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
listings = [r for r in rows if r["type"] == "listing"]; cal = {r["id"]: r for r in rows if r["type"] == "calendar"}
for l in sorted(listings, key=lambda r: r["price_nightly_effective"] or 0)[:10]: print(l["name"], l["price_nightly_effective"], "occupancy 90d", cal.get(l["id"], {}).get("occupancy_90"))
JavaScript (Node)
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<TOKEN>' });
const run = await client.actor('kestrel/airbnb-prices-calendar').call({ listingIds: ['11562247'], checkIn: '30 days', checkOut: '33 days', includeDetails: true, calendarMonths: 12 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter(r => r.type === 'calendar').map(r => [r.id, r.occupancy_90, r.next_available]));
curl
curl -X POST "https://api.apify.com/v2/acts/kestrel~airbnb-prices-calendar/run-sync-get-dataset-items?token=<TOKEN>" -H "Content-Type: application/json" \
-d '{"locationQueries": ["Lisbon, Portugal"], "checkIn": "2026-10-03", "checkOut": "2026-10-06", "maxListingsPerQuery": 20}'
The same three calls are runnable files in the examples repository: examples/airbnb-prices-calendar.py|.js|.sh.
Limits and honest notes
- A search returns the site's ranking up to its cap (about 280 listings); cover a city with neighbourhood queries, price bands (
priceMin/priceMax) or map bounds in a pasted URL. occupancy_*counts nights that are not available; blocked nights and booked nights look the same in the calendar.- Prices are what a logged-out visitor sees in
currencyfor the stay and guests given; some markets show all-in prices, others add fees at checkout — the breakdown says which. - The query hashes change occasionally; the actor refreshes them automatically from the site's bundles, a daily canary run watches it, and failures appear as
statusrows rather than silent zeros.
FAQ
Does it need an Airbnb API key, login or cookies?
No. It uses the public key every visitor's browser uses and no session; there is no API key for guests to request.
Is there a free Airbnb scraper here?
Apify's free plan covers small runs, and the actor never charges for unknown places, unpriced listings or empty searches. The Python and JavaScript examples above are complete.
Why does a place come back as unknown_area?
An unrecognised place is answered with listings from all over the world; the actor detects that (empty canonical URL, listings thousands of km apart), returns a status row and charges nothing. Use the spelling the site suggests, e.g. "Lisbon, Portugal".
Can I get more than 280 listings for a city?
Yes — run several searches: neighbourhoods, price bands, room types, or pasted search URLs with map bounds. Each listing is billed once per run.
Is occupancy the same as bookings?
No. It is the share of nights not available; hosts also block nights. Over a market it tracks demand well; for one listing treat it as an upper bound.
Can I track Airbnb prices and availability over time?
Yes. Schedule the actor with relative dates; every run appends dated rows (fetched_at), and the calendar summary gives occupancy per run.
Can I scrape Airbnb host emails or phone numbers?
No, by design: the rows carry the host's public first name, Superhost and verification flags and stats, nothing off-platform.
Can I download an Airbnb dataset as CSV?
Every run's dataset downloads as JSON, CSV or Excel from the run page or the API — an Airbnb dataset for the market, dates and listings you chose, with calendar days as nested arrays (CSV flattens them per day).
Can I get Airbnb listing data without dates?
Yes: leave checkIn/checkOut empty and each listing row carries the default displayed price and price_nights; add includeDetails for description, amenities and host.
Which languages and currencies?
Any site locale for text (locale) and any ISO currency for prices (currency); both are echoed in every row.
Last verified working: 2026-08-29.
How to use Airbnb Scraper
Ready-made runs you can open and start
Each one is a saved input on the Apify Store — open it, press Start, and it runs with the fields already filled in. All 163 across the suite.
| Ready-made run | What it does |
|---|---|
| Airbnb Availability Calendar for a Market | Forty Lisbon listings with six months of day-by-day availability each, plus an occupancy summary per listing - one run, one dataset. |
| Airbnb Comps: Nightly Rate by Bedrooms | Entire homes with two or more bedrooms in Austin between $80 and $400 a night - the comp set a host prices against, filtered before billing. |
| Short-Term Rental Data: AirDNA Alternative | A hundred Barcelona listings with rates, amenities, house rules, host data and three months of calendar - the inputs a rental-market model needs. |
| Airbnb Occupancy Data by Listing ID | Twelve months of day-by-day availability for three known listing ids, with the booked-night count per listing - no search, no wasted rows. |
| Airbnb Nightly Rate by Listing ID | Five known Lisbon listings priced for the same three-night stay: nightly rate, total with fees, guests, bedrooms and rating in one row each. |
| Airbnb Listing Data: Amenities and Rules | Twenty-five Porto listings with description, amenities, house rules, cancellation policy, sleeping arrangements, host and category ratings. |
| Scrape Airbnb Reviews and Prices Together | Twenty Seville listings with their nightly rate and the twenty most recent guest reviews each - price and sentiment joined on the same listing id. |
| Airbnb Price Tracker from a Search URL | Paste an Airbnb search URL and every filter on it is honoured - map bounds, dates, guests, room type - so the tracked set is exactly what you saw. |
| Scrape Airbnb Listings Over $500 a Night | Aspen entire homes priced above $500 a night: the luxury tier only, with cheaper listings filtered out before a single row is billed. |
| Airbnb Market Data for Private Rooms | Berlin private rooms under 80 EUR a night: the budget tier of a market, sized and priced without pulling every entire home in the city. |
| Airbnb Data for 3-Bedroom Family Homes | Algarve entire homes with three bedrooms and two bathrooms, priced for four adults and two children on a seven-night summer stay. |
| Airbnb API Alternative: One Call, JSON Out | Airbnb has no public listings API. This is the smallest replacement: one place, one stay, 25 priced Paris listings as JSON, in French and EUR. |
| Airbnb Data API: 28-Night Monthly Rates | A 28-night stay in Medellin triggers Airbnb monthly discounts, so the nightly rate a digital nomad pays is not the nightly rate a week-long guest sees. |
Related scrapers
Run Airbnb Scraper
The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/airbnb-prices-calendar