Find Airbnb listings matching location and lodging constraints such as minimum bedrooms, nightly price bounds, entire-home status, house or other property types, Wi-Fi, pool, self check-in, and Superhost status. Hostel preference can be handled as an optional ranking or content match when Airbnb exposes no reliable hostel-only parameter. Optionally verify amenities that are not exposed as reliable search filters, such as the exact listing-page amenity “Hot water.”
Use Cases
Use when the caller wants a filtered collection of Airbnb stays and the result should include listing IDs, titles, displayed prices, badges, descriptive content, and payment messages. Treat min_bedrooms=3 as at least three bedrooms; add date and guest parameters when availability or date-specific pricing is required. For amenities unavailable in search, such as hot water, search broadly and verify each candidate’s room page before returning it. For a hostel preference, leave results unfiltered unless a supported property-type parameter is known, then rank listings whose title, name, or content contains “hostel.”
Automation Flow
- Build and navigate directly to the Airbnb search URL, without visiting the homepage or using the search form:
https://www.airbnb.com/s/{URL-encoded-location-slug}/homes?query={URL-encoded-location}&checkin={YYYY-MM-DD}&checkout={YYYY-MM-DD}&adults={guests}&min_bedrooms={minimum-bedrooms}&price_min={min-price}&price_max={max-price}&amenities%5B%5D=4&amenities%5B%5D=7&amenities%5B%5D=51&property_type_id%5B%5D=2&l2_property_type_ids%5B%5D=1&room_types%5B%5D=Entire%20home%2Fapt&self_check_in=true&superhost=true&display_currency=USDOmit unused filters rather than sending placeholder values. The location slug can be an Airbnb slug such asMexico-City--Mexico; URL-encode location and query values. When Airbnb supplies them, optionally includeplace_id,acp_id,location_bb, andrefinement_paths%5B%5D=%2Fhomes. Usecheckin/checkouton search URLs andcheck_in/check_outon room URLs. - If a consent banner is present, select
Only necessary(or the equivalent necessary-cookies option) before extracting results. Locate the button by exact visible text; if selector clicking fails, use its visible bounding-box center rather than a fixed generic selector. - In the same browser call, wait briefly for hydration (typically 3–7 seconds after navigation), then evaluate this extractor on the loaded search page:
(() => { const state = document.querySelector('#data-deferred-state-0'); if (!state) return {error:'NO_SSR', title:document.title, listings:[]}; let data; try { data = JSON.parse(state.textContent); } catch (e) { return {error:'BAD_SSR_JSON', title:document.title, listings:[]}; } const results = data?.niobeClientData?.[0]?.[1]?.data?.presentation?.staysSearch?.results; if (!results) return {error:'NO_RESULTS', title:document.title, listings:[]}; const decodeId = value => { try { return atob(value).replace(/^DemandStayListing:/, ''); } catch (e) { return null; } }; const nums = value => (typeof value === 'string' ? value : '').match(/\d+(?:[.,]\d+)?/g)?.map(v => +v.replace(',', '.')) || []; return { total: results.searchResults?.length || 0, pageTitle: results.sectionConfiguration?.pageTitleSections?.sections?.[0]?.sectionData?.structuredTitle || null, pagination_cursors: results.paginationInfo?.pageCursors || [], listings: (results.searchResults || []).filter(item => item.demandStayListing).map(item => { const q = item.demandStayListing, p = item.structuredDisplayPrice?.primaryLine || {}, lines = item.structuredContent?.primaryLine || []; const baths = lines.find(x => x.type === 'BATHROOMINFO'), rating = nums(item.avgRatingLocalized || item.avgRatingA11yLabel), id = decodeId(q.id), badges = item.badges || []; const details = lines.map(x => x.body || '').join(' | '); const bedrooms = details.match(/\b(\d+)\s+bedrooms?\b/i), beds = details.match(/\b(\d+)\s+beds?\b/i); const reviewMatch = String(item.avgRatingA11yLabel || '').match(/([\d,]+)\s+reviews?\b/i) || String(item.avgRatingLocalized || '').match(/\(([\d,]+)\)/); const reviewCount = reviewMatch ? Number(reviewMatch[1].replace(/,/g, '')) : null; return { listing_id:id, url:id ? `https://www.airbnb.com/rooms/${id}` : null, title:item.title || null, name:item.name || item.nameLocalized?.localizedStringWithTranslationPreference || null, bedrooms:bedrooms ? Number(bedrooms[1]) : null, beds:beds ? Number(beds[1]) : null, bathrooms:baths ? nums(baths.body)[0] ?? null : null, lat:q.location?.coordinate?.latitude ?? null, lng:q.location?.coordinate?.longitude ?? null, coordinate_is_fuzzed:true, nightly_price:p.discountedPrice || p.price || null, nightly_price_original:p.originalPrice || null, price_qualifier:p.qualifier || null, price_a11y_label:p.accessibilityLabel || null, rating:rating[0] ?? null, review_count:reviewCount, badges:badges.map(x => x.text).filter(Boolean), superhost:badges.some(x => x.loggingContext?.badgeType === 'SUPERHOST'), guest_favorite:badges.some(x => ['GUEST_FAVORITE','TOP_TIER_FAVORITE'].includes(x.loggingContext?.badgeType)), content:lines.map(x => x.body || x.type || '').filter(Boolean).join(' | '), photo_url_primary:item.contextualPictures?.[0]?.picture || null, photo_urls:(item.contextualPictures || []).map(x => x.xlPicture || x.picture).filter(Boolean), payment_messages:item.paymentMessages || [] }; }) }; })() - If an amenity such as hot water is requested, use each extracted
listing_idto navigate directly tohttps://www.airbnb.com/rooms/{listing-id}(addingcheck_in,check_out, and guest parameters when relevant), then evaluate:Keep only listings whose(() => { const state = document.querySelector('#data-deferred-state-0'); if (!state) return {error:'NO_SSR', url:location.href}; let data; try { data = JSON.parse(state.textContent); } catch (e) { return {error:'BAD_SSR_JSON', url:location.href}; } const node = data?.niobeClientData?.[0]?.[1]?.data?.node?.pdpPresentation; if (!node) return {error:'NO_PDP_NODE', url:location.href}; const groups = node.amenities?.seeAllAmenitiesGroups || [], amenities = groups.flatMap(g => (g.amenities || []).map(a => a.title)).filter(Boolean); return {listing_id:(location.pathname.match(/^\/rooms\/(\d+)/) || [])[1] || null, url:location.href, name:node.name || null, amenities, hot_water:amenities.includes('Hot water'), page_title:document.title}; })()hot_wateris true when required. Verify the returned URL and listing name against the search result because card navigation can open an unexpected listing. - If more results are required, use the pagination cursor or the actual next-page
hrefexposed innav[aria-label="Search results pagination"] a. The visible Next control may fail to navigate; open its absolute or root-relativehrefdirectly, wait for hydration, rerun the same extractor, and concatenate results. Do not assume opaque listing IDs.
Possible Friction Points
- Airbnb’s useful filter state is directly representable in query parameters;
amenities[],min_bedrooms,price_min,price_max,property_type_id[],l2_property_type_ids[],room_types[],self_check_in, andsuperhostavoid fragile filter-dialog interaction. - Current Airbnb filter URLs observed for house searches use
amenities[]=51for self check-in,room_types[]=Entire home/apt,min_bedrooms={n}, andl2_property_type_ids[]=1. Retain the legacyproperty_type_id[]=2house option when supported; Airbnb’s taxonomy has varied, so verify the resulting page’s applied filters. - Amenity
4is Wi-Fi and amenity7is pool. Amenity51is the observed self-check-in filter. - Hot water was not exposed as a reliable search-filter option. Verify it on the room page using
pdpPresentation.amenities.seeAllAmenitiesGroupsand the exact amenity titleHot water. - No reliable hostel-only query parameter was established. Treat “hostel preferred” as an optional ranking/content preference unless a future run confirms a property-type enum; do not silently exclude other stays.
- Search results embed structured data in
#data-deferred-state-0; listing IDs are base64-encoded values such asDemandStayListing:{id}and must be decoded rather than guessed. - The room-page amenity data is also embedded in
#data-deferred-state-0, underniobeClientData[0][1].data.node.pdpPresentation.amenities.seeAllAmenitiesGroups. - A short hydration wait is required before paginated SSR listings reliably appear.
- A consent banner may block reliable interaction. The generic
button[type='button']:nth-of-type(3)selector is unreliable; find the exactOnly necessarybutton and, if needed, click its visible bounding-box center. - The visible pagination Next control may not navigate. Inspect its
hrefand open that URL directly; observed next-page URLs may includepagination_search=trueand an encodedcursor. - Card clicks can resolve to a different listing than intended. Prefer direct room URLs from decoded IDs and always verify the final URL and title/name.
price_minandprice_maxconstrain Airbnb’s displayed search price, but exact nightly totals and availability depend on dates, guests, taxes, and fees.- Extreme price bounds can produce a reliable empty state such as “No exact matches” and “0 stays”; return an empty listings array rather than treating it as an extraction failure.
- Search-result markup and the deferred-state schema are site-controlled and may change; the extractors intentionally return explicit missing-state errors.