Skip to main content
Thirdwatchthirdwatch
Real estate

Scrape Airbnb Listings for Short-Term Rental Market Research

Pull Airbnb listings for any city: price per night, rating, review count, room type, and coordinates. Build a structured STR market dataset via the Apify API.

Jun 20, 2026 · 6 min read · 1,354 words
See the scraper →

Thirdwatch's Airbnb Scraper pulls short-term rental listings for any city with structured price per night, rating, review count, room type, and latitude/longitude. Built for researchers, real-estate analysts, and revenue managers who need a repeatable Airbnb market dataset without a headless browser or manual collection. Search by location to build a city-level dataset, or pass a single listing URL for one property. Transparent pay-per-result pricing keeps acquisition cost proportional to listings returned.

Why scrape Airbnb for short-term rental research

Short-term rental research starts with a supply-and-price question: how many active listings exist in a market, what do they charge per night, and how well-reviewed are they? Airbnb is the largest single source of that answer. The platform reported over 5 million hosts and roughly 8 million active listings worldwide — a dataset that reflects real, bookable nightly pricing rather than survey estimates or stale index figures.

The researcher's job-to-be-done is concrete. A short-term-rental analyst sizing a new market needs the count of active listings, the nightly-rate distribution, and how ratings cluster by room type. A revenue manager benchmarking a portfolio needs competitor pricing for the same neighborhood and dates. An urban-policy researcher studying housing impact needs listing coordinates to map STR density against residential zones. A real-estate investor underwriting a property needs comparable nightly rates to model gross yield.

All of these begin with the same requirement: a programmatic way to query Airbnb by city, get back structured rows with price, rating, review count, room type, and coordinates, and load them into a database or notebook. Airbnb has no public listings API, so the actor is that extraction layer.

How does this compare to the alternatives?

Three paths to getting Airbnb listing data into a research pipeline:

Approach Reliability Setup time Maintenance
DIY Python + headless browser Low — Airbnb renders from rotating internal endpoints and anti-bot defences break scripts within days 3-5 weeks to handle pagination, geo, and blocks Frequent fixes as the site changes
Generic scraping API Medium — returns rendered HTML you parse yourself, fragile to layout changes 1-2 weeks including a custom parser You own the parsing layer
Thirdwatch Airbnb Scraper High — returns clean JSON with consistent field names Under 1 hour to first dataset Thirdwatch maintains extraction logic

For a research dataset, the deciding factor is schema stability. DIY scrapers break whenever Airbnb changes how it renders results, and a broken collector mid-study corrupts your time series. The Airbnb Scraper returns a consistent schema run after run, so your ingestion code stays put. For a wider hospitality view, combine it with hotel data from the Booking.com or Trip.com actors.

How to scrape Airbnb listings for market research in 5 steps

Step 1: How do I set up my Apify token?

Sign up at apify.com for a free-tier account, open Settings then Integrations, and copy your API token. Every example below reads it from APIFY_TOKEN:

pip install apify-client pandas
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

Step 2: How do I pull all listings for a city?

Pass a location string and a maxResults cap. The actor paginates through Airbnb's results until it reaches your cap and returns one row per listing. Set currency so prices are comparable across markets.

run = client.actor("thirdwatch/airbnb-scraper").call(run_input={
    "location": "Lisbon, Portugal",
    "maxResults": 200,
    "currency": "EUR",
})

items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
print(f"Collected {len(items)} Lisbon listings")

The location field accepts cities, regions, or addresses — "Lisbon, Portugal", "Paris", or "Bali, Indonesia" all resolve. maxResults accepts up to 300 per run.

Step 3: How do I price listings for specific dates?

Pricing changes by stay. Add checkIn, checkOut (both YYYY-MM-DD), and adults so the returned price_per_night and total_price reflect that exact window — essential when benchmarking competitors for a known booking date.

run = client.actor("thirdwatch/airbnb-scraper").call(run_input={
    "location": "Paris, France",
    "checkIn": "2026-08-01",
    "checkOut": "2026-08-05",
    "adults": 2,
    "currency": "EUR",
    "maxResults": 150,
})
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())

Leave the date fields empty for an undated baseline scan of nightly rates across the market.

Step 4: How do I analyze pricing and supply across a city?

Load the rows into pandas and slice by room_type. This surfaces the nightly-rate distribution, supply mix, and rating spread that anchor most STR market studies.

import pandas as pd

df = pd.DataFrame(items)

# Strip currency symbols to a numeric nightly rate
df["nightly"] = (
    df["price_per_night"].astype(str)
    .str.replace(r"[^0-9.]", "", regex=True)
    .replace("", None)
    .astype(float)
)

# Supply and price by room type
summary = df.groupby("room_type").agg(
    listings=("name", "count"),
    median_nightly=("nightly", "median"),
    avg_rating=("rating", "mean"),
    total_reviews=("review_count", "sum"),
)
print(summary)

review_count summed by room type is a useful demand proxy: room types with high review totals relative to listing count tend to convert more bookings.

Step 5: How do I build a multi-city panel for ongoing research?

Loop over a city list, tag each row with the search market and a snapshot date, then concatenate. Re-running this on a schedule produces a time series for tracking pricing, supply, and occupancy signals.

import datetime

CITIES = ["Lisbon, Portugal", "Porto, Portugal", "Barcelona, Spain", "Valencia, Spain"]
snapshot = datetime.date.today().isoformat()
frames = []

for city in CITIES:
    run = client.actor("thirdwatch/airbnb-scraper").call(run_input={
        "location": city, "maxResults": 200, "currency": "EUR",
    })
    rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
    for r in rows:
        r["search_city"] = city
        r["snapshot_date"] = snapshot
    frames.append(pd.DataFrame(rows))

panel = pd.concat(frames, ignore_index=True)
print(f"{len(panel)} listings across {len(CITIES)} markets on {snapshot}")

Use room_id as the dedup key across snapshots, and Apify's scheduling feature to trigger weekly runs so the panel fills in automatically.

Sample output

Each location-search record carries the market-research core: identifier, price, quality signals, room type, and coordinates. Newly listed stays return null for rating and review_count because Airbnb shows "New" instead of a score.

[
  {
    "room_id": "1709223735731352361",
    "name": "Charming Apartment with Terrace in Alfama",
    "room_type": "Entire rental unit in Lisbon",
    "price_per_night": "€118",
    "total_price": "€472 for 4 nights",
    "rating": 4.91,
    "review_count": 214,
    "lat": 38.7139,
    "lng": -9.1278,
    "host": "Maria",
    "badges": ["Guest favorite"],
    "url": "https://www.airbnb.com/rooms/1709223735731352361"
  },
  {
    "room_id": "987654321098765432",
    "name": "Sunny Private Room near Bairro Alto",
    "room_type": "Private room in Lisbon",
    "price_per_night": "€54",
    "total_price": "€216 for 4 nights",
    "rating": null,
    "review_count": null,
    "lat": 38.7118,
    "lng": -9.1450,
    "host": "João",
    "badges": [],
    "url": "https://www.airbnb.com/rooms/987654321098765432"
  }
]

room_id is your primary key for deduplication across snapshots. price_per_night and total_price arrive as display strings with the currency symbol — parse them to numbers before aggregating. room_type encodes both the property class and city. lat and lng enable density maps and radius queries. amenities populate for single-listing (listingUrl) scrapes; a location search returns the core fields above.

Common pitfalls

Four issues trip up Airbnb research pipelines. Price strings, not numbersprice_per_night and total_price include the currency symbol and formatting ("€118", "€472 for 4 nights"); strip non-numeric characters before computing medians. Null ratings on new listingsrating and review_count are null whenever Airbnb shows "New"; treat null as missing, not zero, or you will understate market quality. Date sensitivity — undated runs return a baseline rate while checkIn/checkOut runs return that specific stay's price, so never mix the two in one aggregate. Per-listing depth is a separate path — full amenities and host detail come from a single listingUrl scrape, not from a city-wide location search; plan a two-pass design if you need deep per-property fields.

The actor handles pagination, currency selection, geo resolution, and Airbnb's anti-bot protection, retrying automatically when a request is rate-limited so your pipeline treats it as a stable JSON source. For listings without coordinates or for neighborhood enrichment, pair it with the Google Maps Scraper.

Related use cases

Frequently asked questions

Can I scrape every Airbnb listing in a city?

You scrape a large, representative sample, not a guaranteed census. A location search returns the listings Airbnb surfaces for that query — typically hundreds with price, rating, review count, room type, and coordinates. Set maxResults up to 300 per run and combine date and guest filters to widen coverage.

Does the actor return occupancy or booking data?

Not directly — Airbnb does not publish occupancy publicly. The actor returns review_count and rating, which are the standard proxy signals researchers use to estimate demand and tenure. Snapshot review_count over time and the month-over-month delta approximates booking velocity for each listing.

Related

Try it yourself

100 free credits, no credit card.

About 30 real searches. Add the MCP to Claude or Cursor in two minutes.