Search Airbnb stays by PLACE NAME (no coordinates needed).
{"location":"place name, e.g. 'San Francisco, CA' or 'Barcelona, Spain'","checkin":"YYYY-MM-DD","checkout":"YYYY-MM-DD","adults":"number of adults","price_max":"max nightly USD ('' to skip)","price_min":"min nightly USD ('' to skip)"}{"count":"number","listings":"[{id,url,name,title,price,originalPrice,priceQualifier,rating,reviews,beds,badges,lat,lng,photo}]"}Search Airbnb stays by PLACE NAME, with no coordinates needed. Airbnb resolves the place itself when the name is in the URL path (/s/{location}/homes), so this navigates there and reads the embedded #data-deferred-state-0 SSR JSON in the real browser (the StaysSearch GraphQL API is blocked by rotating hashes and fingerprinting). Params: location (e.g. 'San Francisco, CA' or 'Bariloche, Argentina'), checkin and checkout (YYYY-MM-DD), adults, price_min, price_max (nightly USD, '' to skip). Returns {count, listings:[{id,url,name,title,price,originalPrice,priceQualifier,rating,reviews,beds,badges,lat,lng,photo}]}. For a precise map region instead of a place name, use airbnb.com/search-area with a bounding box.
params. It does the in-page work and returns the JSON in returns.( async (root, params) => { const el = document.querySelector('#data-deferred-state-0'); if (!el) return { count: 0, listings: [], error: 'no SSR blob (anti-bot shell)' }; let data; try { data = JSON.parse(el.textContent); } catch (e) { return { count: 0, listings: [], error: 'json parse fail' }; } let arr; try { arr = data.niobeClientData[0][1].data.presentation.staysSearch.results.searchResults || []; } catch (e) { return { count: 0, listings: [], error: 'path fail' }; } const decodeId = (b64) => { try { const s = atob(b64); const i = s.indexOf(':'); return i >= 0 ? s.slice(i + 1) : s; } catch (e) { return null; } }; const listings = arr.filter(r => r && r.demandStayListing).map(r => { const id = decodeId(r.demandStayListing.id); const pl = (r.structuredDisplayPrice && r.structuredDisplayPrice.primaryLine) || {}; const price = pl.discountedPrice || pl.price || null; const original = pl.originalPrice || null; const qualifier = pl.qualifier || null; let rating = null, reviews = null; const m = (r.avgRatingLocalized || '').match(/([\d.]+)\s*((\d+))/); if (m) { rating = parseFloat(m[1]); reviews = parseInt(m[2], 10); } const loc = (r.demandStayListing.location && r.demandStayListing.location.coordinate) || {}; const beds = ((r.structuredContent && r.structuredContent.primaryLine) || []).map(x => x.body).filter(Boolean).join(', '); const badges = (r.badges || []).map(b => b.loggingContext && b.loggingContext.badgeType).filter(Boolean); const photo = (r.contextualPictures && r.contextualPictures[0] && r.contextualPictures[0].picture) || null; return { id, url: id ? 'https://www.airbnb.com/rooms/' + id : null, name: r.subtitle || (r.nameLocalized && r.nameLocalized.localizedStringWithTranslationPreference) || null, title: r.title || null, price, originalPrice: original, priceQualifier: qualifier, rating, reviews, beds, badges, lat: loc.latitude, lng: loc.longitude, photo }; }); return { count: listings.length, listings }; } )
{
"expr": "#data-deferred-state-0",
"type": "dom"
}