DEV Community

Vhub Systems
Vhub Systems

Posted on

How to Scrape Airbnb Listings, Prices, and Availability Without the API

How to Scrape Airbnb Listings Without the API in 2026

Airbnb removed their public API in 2020. The only way to get pricing, availability, and listing data at scale is to scrape it directly.

Here's what actually works — and the pitfalls to avoid.

What You Can Extract

Data Field Accessible
Listing title, description
Price per night, cleaning fee
Ratings and review count
Host info (name, superhost status)
Amenities list
Location (approximate)
Photos ✅ (URLs)
Availability calendar
House rules
Number of bedrooms/bathrooms

Airbnb's Anti-Scraping Setup

Airbnb uses a multi-layer approach:

  1. IP-based rate limiting — datacenter IPs get throttled within 20-30 requests
  2. Browser fingerprinting — checks Canvas, WebGL, audio fingerprint
  3. JavaScript rendering — most data loads after initial HTML
  4. Token-based API — internal API requires session tokens that rotate

The good news: the internal API is well-structured and returns clean JSON if you can get a valid token.

Method 1: Airbnb's Internal API (Most Efficient)

import requests
import json
from urllib.parse import urlencode

def search_airbnb_listings(location, checkin, checkout, adults=2, max_results=50):
    """
    Search Airbnb listings for a location and date range.
    Returns structured listing data.
    """

    # Airbnb's internal search API
    base_url = "https://www.airbnb.com/api/v3/StaysPdpSections"

    # Build search parameters
    params = {
        "operationName": "StaysPdpSections",
        "locale": "en",
        "currency": "USD",
    }

    # GraphQL variables for the search
    variables = {
        "query": location,
        "checkin": checkin,  # "2026-05-01"
        "checkout": checkout,  # "2026-05-07"  
        "adults": adults,
        "children": 0,
        "infants": 0,
        "pets": 0,
        "refinementPaths": ["/homes"],
        "tabId": "home_tab",
        "itemsPerGrid": 20,
    }

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0",
        "Accept": "application/json",
        "X-Airbnb-API-Key": "d306zoyjsyarp7ifhu67rjxn52tv0t20",  # Public app key
        "Content-Type": "application/json",
    }

    # Alternative: use the explore API
    explore_url = "https://www.airbnb.com/api/v3/ExploreSearch"
    explore_params = {
        "version": "1.8.9",
        "satori_version": "1.1.0",
        "query": location,
        "checkin": checkin,
        "checkout": checkout,
        "adults": str(adults),
        "currency": "USD",
        "locale": "en",
        "items_per_grid": "20",
    }

    r = requests.get(explore_url, params=explore_params, headers=headers, timeout=20)

    if r.status_code != 200:
        print(f"Error: {r.status_code}")
        return []

    data = r.json()

    # Navigate the response structure
    explore_tabs = data.get("explore_tabs", [])
    listings = []

    for tab in explore_tabs:
        sections = tab.get("sections", [])
        for section in sections:
            items = section.get("listings", [])
            for item in items:
                listing = item.get("listing", {})
                pricing = item.get("pricing_quote", {})

                listings.append({
                    "id": listing.get("id"),
                    "name": listing.get("name"),
                    "city": listing.get("city"),
                    "room_type": listing.get("room_type_category"),
                    "bedrooms": listing.get("bedrooms"),
                    "bathrooms": listing.get("bathrooms"),
                    "beds": listing.get("beds"),
                    "person_capacity": listing.get("person_capacity"),
                    "price_per_night": pricing.get("rate", {}).get("amount"),
                    "currency": pricing.get("rate", {}).get("currency"),
                    "rating": listing.get("avg_rating"),
                    "review_count": listing.get("reviews_count"),
                    "superhost": listing.get("is_superhost"),
                    "url": f"https://www.airbnb.com/rooms/{listing.get('id')}",
                })

    return listings

# Example usage
listings = search_airbnb_listings(
    location="Lisbon, Portugal",
    checkin="2026-05-01",
    checkout="2026-05-07",
    adults=2
)

for listing in listings[:3]:
    print(f"${listing['price_per_night']}/night | ⭐{listing['rating']} ({listing['review_count']} reviews)")
    print(f"  {listing['bedrooms']}br/{listing['bathrooms']}ba | {listing['name'][:60]}")
    print()
Enter fullscreen mode Exit fullscreen mode

Method 2: Playwright for Dynamic Content

For listing details pages (amenities, house rules, availability calendar):

from playwright.sync_api import sync_playwright
import json, re

def scrape_airbnb_listing(listing_id, checkin="2026-05-01", checkout="2026-05-07"):
    """Scrape full listing details from Airbnb listing page."""

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0"
        )

        page = context.new_page()
        url = f"https://www.airbnb.com/rooms/{listing_id}?check_in={checkin}&check_out={checkout}"
        page.goto(url, wait_until="networkidle")

        # Extract JSON-LD structured data (most reliable)
        scripts = page.query_selector_all('script[type="application/json"]')

        listing_data = {}
        for script in scripts:
            try:
                data = json.loads(script.inner_text())
                # Look for the main listing data
                if "niobeMinimalClientData" in str(data):
                    listing_data = data
                    break
            except:
                continue

        # Extract amenities from the page
        amenity_elements = page.query_selector_all('[data-section-id="AMENITIES_DEFAULT"] li')
        amenities = [el.inner_text() for el in amenity_elements]

        # Extract price
        price_el = page.query_selector('[data-testid="price-component"]')
        price = price_el.inner_text() if price_el else None

        browser.close()

        return {
            "id": listing_id,
            "amenities": amenities,
            "price_display": price,
            "url": url,
            "raw_data": listing_data,
        }

listing = scrape_airbnb_listing("1234567", "2026-05-01", "2026-05-07")
print(f"Amenities: {listing['amenities'][:5]}")
Enter fullscreen mode Exit fullscreen mode

Method 3: Apify Actor (No Session Management)

The Airbnb Listings Scraper handles proxy rotation, session tokens, and JavaScript rendering automatically.

import requests, time

run = requests.post(
    "https://api.apify.com/v2/acts/lanky_quantifier~airbnb-listings-scraper/runs",
    headers={"Authorization": "Bearer YOUR_APIFY_TOKEN"},
    json={
        "location": "Lisbon, Portugal",
        "checkIn": "2026-05-01",
        "checkOut": "2026-05-07",
        "adults": 2,
        "maxListings": 100,
        "includeCalendar": True,
        "includePricing": True
    }
).json()["data"]

while True:
    status = requests.get(
        f"https://api.apify.com/v2/actor-runs/{run['id']}",
        headers={"Authorization": "Bearer YOUR_APIFY_TOKEN"}
    ).json()["data"]["status"]
    if status in ("SUCCEEDED", "FAILED"): break
    time.sleep(5)

results = requests.get(
    f"https://api.apify.com/v2/actor-runs/{run['id']}/dataset/items",
    headers={"Authorization": "Bearer YOUR_APIFY_TOKEN"}
).json()

for listing in results[:3]:
    print(f"${listing['price']}/night | ⭐{listing['rating']} | {listing['bedrooms']}br")
    print(f"  {listing['name'][:60]}")
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

Short-term rental investors: Track average nightly rates and occupancy patterns in target markets before buying property

Property managers: Monitor competitor listings in the same market, price competitively

Travel apps: Build location-specific pricing dashboards for budget planning

Research: Analyze Airbnb market penetration, host density, or average prices across cities

Arbitrage detection: Find markets where long-term rental costs are lower than Airbnb revenue potential

Anti-Detection Tips

import time, random

# Key rules:
# 1. Always use residential proxies - datacenter IPs get rate-limited within 20-30 requests
# 2. Add natural delays between requests
# 3. Use Chrome-like headers including cookie consent headers

def get_headers():
    return {
        "User-Agent": random.choice([
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124.0.0.0",
        ]),
        "Accept-Language": "en-US,en;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Connection": "keep-alive",
    }

def safe_delay():
    time.sleep(random.uniform(2.0, 5.0))
Enter fullscreen mode Exit fullscreen mode

Pricing

Using Apify: ~$2-3 per 1,000 listings scraped via pay-per-result pricing.

Running your own scraper: Residential proxy costs of ~$5-10/GB.

For periodic monitoring (weekly pricing updates for 50 markets): expect ~$5-10/month total.


Save hours on scraping setup: The $29 Apify Scrapers Bundle includes 35+ production-ready actors — Google SERP, LinkedIn, Amazon, TikTok, contact info, and more. Pre-configured inputs, working on day one.

Get the Bundle ($29) →

Top comments (0)