Airbnb Scraper & Market Monitor
Pricing
$2.00 / 1,000 listing scrapeds
Airbnb Scraper & Market Monitor
Reliable Airbnb scraper: location/URL search, property details, availability calendar (with prices), and reviews in one Actor — plus an incremental monitoring mode that tracks new listings, price changes, availability changes and delistings over time. Public listing & host-operational data only.
Pricing
$2.00 / 1,000 listing scrapeds
Rating
0.0
(0)
Developer
Scrape Sage
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
0.16 hours
Issues response
4 days ago
Last modified
Categories
Share
Scrape Airbnb at scale and track a market over time — in one Actor. Search by location or paste Airbnb search/room URLs, then get full property details, the availability calendar, host operational data, and reviews. Re-run on a schedule in incremental monitoring mode to capture new listings, price changes, availability changes and delistings — so you watch a market, not just snapshot it.
ℹ️ Per‑day calendar nightly prices are temporarily unavailable (returning ~July 10, 2026 while billing is finalized). The calendar currently returns day‑by‑day availability; enabling
calendarPriceshas no effect until then.
Built for revenue managers, short‑term‑rental investors, market analysts, and developers who need reliable, structured Airbnb data.
Public data only. This Actor captures public listing, pricing, availability and host operational data. It intentionally excludes personal identifiers of hosts, co‑hosts and reviewers (name, profile photo, profile URL, "about" text). See Data scope.
Why this Actor
| This Actor | Typical Airbnb scraper | |
|---|---|---|
| Search + property detail + calendar + reviews | ✅ all in one Actor | often split across separate actors |
| Beyond the ~270-result search cap | ✅ automatic price‑band + map splitting | partial or manual |
| Incremental monitoring (new / price / availability / delisting) | ✅ built in | rarely offered |
Reliability: an API‑first tiered fetch — Airbnb's own GraphQL/JSON endpoints first, with a Playwright browser fallback — runs over datacenter proxy by default with automatic residential fallback, plus session rotation, rate limiting, exponential backoff and circuit breakers. In testing, a 1,000‑listing run with full detail and reviews completed with 0 blocks and every listing returned.
The differentiators are all‑in‑one coverage, incremental monitoring and reliability, backed by a rich field set: availability calendar, registration number where shown, host operational signals, grouped amenities and house‑rules, and the full photo gallery.
Features
- Multiple sources in one run: location queries, pre-filtered search URLs, and direct listing/room URLs.
- Tiered, instrumented fetch for reliability — Airbnb's GraphQL/JSON endpoints first, Playwright browser fallback if needed — routed through datacenter proxy by default with automatic residential fallback, plus rotating sessions and exponential backoff. The run summary reports which tier served results so you can measure reliability.
- Beyond the ~270-result cap: automatic price‑band splitting (primary) and optional map bounding‑box subdivision (dense cities), with dedupe by listing id.
- Availability calendar — day‑by‑day available / min‑/max‑nights / check‑in‑out
eligibility. (Optional per‑day nightly price (
calendarPrices) is temporarily unavailable — returning ~July 10, 2026; see the note at the top.) - Reviews (content, rating, date, language, opaque reviewer id only).
- Incremental monitoring mode: baseline once, then emit only what changed.
- Billing fairness: empty/unavailable/delisted/zero‑result/region‑blocked outcomes are successful runs that explain themselves and are not charged.
- Layout/endpoint‑rot detector: loud warning if Airbnb's markup/GraphQL changes.
Input
Provide at least one source. All fields are documented in the Actor's input form.
Defaults that matter: the actor returns the richest data out of the box — detail
pages are ON by default (skipDetailPages: false) and reviews are ON
(scrapeReviews: true), so each listing comes back with description, amenities,
accessibility, house rules, registration, full gallery, host data (incl. time as host and
response rate/time), and reviews. For a fast, cheap run, set skipDetailPages: true to get
search-card data only (title, price, rating, coordinates, badges, Superhost/Guest-favorite,
thumbnail), and/or scrapeReviews: false. maxListings defaults to 100; raise it for
bigger pulls or set 0 for unlimited (a dense city can be many thousands). A listing URL
passed directly is always fetched in full regardless of skipDetailPages.
| Group | Fields |
|---|---|
| Sources | locationQueries, searchUrls, listingUrls |
| Search filters | checkIn, checkOut, adults, children, infants, pets, priceMin, priceMax, minBeds, minBedrooms, minBathrooms, propertyTypes, currency, locale |
| Detail / calendar / reviews | skipDetailPages, calendarMonths (0–12), calendarPrices (per‑day nightly price — temporarily unavailable until ~July 10, 2026), maxCalendarPriceDays, scrapeReviews, maxReviewsPerListing |
| Scale & cap-splitting | maxListings, priceSplitCeiling, enableMapSplit |
| Incremental monitoring | incrementalMode, monitorKey, trackPriceChanges, trackAvailabilityChanges, detectDelistings |
| Proxy & tuning | proxyConfiguration (default datacenter, auto residential fallback), maxConcurrency, maxRequestsPerMinute, maxRequestRetries |
propertyTypes accepts Airbnb room types: Entire home/apt, Private room,
Shared room, Hotel room (unknown values are ignored).
Example: location search with calendar
{"locationQueries": ["Lisbon, Portugal"],"checkIn": "2026-07-01","checkOut": "2026-07-05","adults": 2,"currency": "EUR","calendarMonths": 2,"maxListings": 200,"proxyConfiguration": { "useApifyProxy": true }}
Example: pre-filtered search URL
{"searchUrls": ["https://www.airbnb.com/s/Miami-Beach--FL/homes?amenities%5B%5D=4"],"skipDetailPages": true}
Example: single property + calendar + reviews
{"listingUrls": ["https://www.airbnb.com/rooms/12345678"],"calendarMonths": 6,"scrapeReviews": true,"maxReviewsPerListing": 100,"currency": "USD"}
Per‑day nightly prices — temporarily unavailable (returning ~July 10, 2026). Airbnb's availability calendar contains no prices, so a nightly rate only exists as a dated booking quote (one request per day). We're rolling this out as an optional add‑on (
calendarPrices) billed per priced day; until it goes live (~July 10, 2026) the calendar returns availability only and enablingcalendarPriceshas no effect. This note will be updated when it ships.
Example: incremental monitoring (re-run on a schedule)
{"locationQueries": ["Lisbon, Portugal"],"checkIn": "2026-07-01","checkOut": "2026-07-05","calendarMonths": 1,"incrementalMode": true,"monitorKey": "lisbon-2br-summer","trackPriceChanges": true,"trackAvailabilityChanges": true,"detectDelistings": true}
Output
Every record shares a common envelope and then carries the listing superset. Example (abridged):
{"id": "1001","url": "https://www.airbnb.com/rooms/1001","scrapedAt": "2026-06-06T09:00:00.000Z","sourceType": "locationSearch","sourceQuery": "Lisbon, Portugal","status": "ok","fetchTier": "ssr-json","title": "Sunny Studio in Alfama","description": "Charming studio with river views...","propertyType": "Entire rental unit","roomType": "Entire home/apt","homeTier": 1,"personCapacity": 4,"minNights": 2,"registrationNumber": "Exempt - AL/12345","coordinates": { "lat": 38.7139, "lng": -9.1334 },"images": [{ "url": "https://.../1001-1.jpg", "caption": "Living room", "orientation": "LANDSCAPE" }],"amenities": [{ "title": "Bathroom", "values": [{ "title": "Hair dryer", "available": true }] }],"accessibilityFeatures": [{ "title": "Guest entrance and parking", "values": [{ "title": "Step-free guest entrance", "available": true }, { "title": "Wide entrance", "available": false }] }],"pricing": {"price": { "value": 95, "raw": "€95", "currency": "EUR" },"originalPrice": { "value": 120, "raw": "€120", "currency": "EUR" },"priceLabel": "Was €120, now €95 per night","qualifier": "night"},"rating": { "guestSatisfaction": 4.92, "cleanliness": 4.95, "reviewsCount": 218 },"isGuestFavorite": true,"badges": ["Superhost", "Guest favorite"],"host": { "hostId": "host-77", "isSuperhost": true, "isVerified": true, "ratingAverage": 4.9, "ratingCount": 540, "timeAsHost": { "years": 6, "months": 4 }, "responseRate": 100, "responseTime": "within an hour" },"calendar": [{ "date": "2026-07-01", "available": true, "minNights": 2, "maxNights": 30, "checkInEligible": true, "checkOutEligible": true }],"reviews": [{ "reviewId": "rev-1", "text": "Amazing stay!", "rating": 5, "createdAtISO": "2026-05-01T10:00:00.000Z", "language": "en", "reviewerId": "user-501" }]}
calendar[]currently carries availability fields only. A per‑daypricefield will be added for available, check‑in‑eligible days when the optionalcalendarPricesadd‑on ships (~July 10, 2026).
Status field
status | Meaning | Charged? |
|---|---|---|
ok | A real listing result | ✅ |
empty:unavailable | Listing exists but not bookable for the requested dates | ❌ |
empty:delisted | Property removed/404 | ❌ |
empty:region_blocked | Region/consent wall | ❌ |
empty:zero_results | Search genuinely returned nothing | ❌ |
error:parserStale | Result items were found but none parsed — Airbnb changed its result shape and the parser needs updating. Reported as a flagged fault (not empty:zero_results) so a populated market is never silently shown as empty. | ❌ |
A runSummary record (with recordType: "runSummary") is emitted at the end and is
never charged. If any search hit error:parserStale, the summary carries a non‑zero
parserStale count and a LOUD rot warning is logged.
Incremental monitoring mode
This is a recurring-revenue feature few other Airbnb scrapers offer — built for revenue managers, STR investors and market analysts.
- Turn on
incrementalModeand set a stablemonitorKey(e.g.lisbon-2br-summer). State is persisted in a named Key‑Value store (airbnb-monitor-<monitorKey>) across runs. - First run captures a baseline; every record is marked
changeType: "baseline". - Later runs compare each listing to its snapshot and emit only change records:
new— a listing id not seen before,priceChanged— withchangeDetails.price = { old, new, delta, currency },availabilityChanged— opened/closed (and calendaravailableDaysdeltas),ratingChanged— guest‑satisfaction or review‑count moved,delisted— previously seen, now absent from the market (emitted after the full scan).
- Cost: only
oklisting/change records are charged. A monitoring run that finds no changes costs about the actor‑start only — cheap and sticky. Schedule it daily or hourly via Apify Schedules.
Reliability & the run summary
Airbnb actively blocks scrapers, so reliability is engineered in depth. This Actor's defense:
- Datacenter proxy by default with automatic residential fallback and rotating sessions; rotate on any 403/429/captcha. (Recent 1,000‑listing detail+reviews test runs held on datacenter with 0 blocks; residential takes over only if datacenter starts failing.)
- Realistic, consistent per‑session headers (UA, locale, currency, the public API key extracted at runtime); jittered delays; conservative concurrency.
- Exponential backoff with jitter; capped retries, then classify and move on — one bad listing never fails the run.
The end‑of‑run runSummary reports searches, listings found/after‑dedup/emitted,
calendar days, reviews, fetch‑tier mix (SSR‑JSON vs GraphQL vs Playwright), blocks,
retries, session rotations, splits used and whether any search hit the ~270 cap. If
api‑key/hash extraction repeatedly fails or many pages parse to zero fields, a loud
"possible Airbnb change" warning is logged.
Data scope & privacy
Included: public listing, pricing, availability, ratings; host operational signals (opaque host id, isSuperhost, isVerified, host rating average/count, time as host, host response rate, host response time, property registration/license number).
Excluded by design — never in output: host/co‑host/reviewer name, profile photo, profile URL, "about" text, and host personal attributes that Airbnb shows on the profile card (age/"born in" decade, occupation, school, hometown, languages, personal bio highlights) — these identify an individual and are not collected. Host listings count is also excluded: it lives only in a separate host‑profile request and is not worth the extra cost or the broader personal‑profile footprint. Reviews keep text/rating/date/language and an opaque reviewer id only. No emails, phone numbers or contact data (Airbnb does not expose these pre‑booking). A guard scrubs output and an automated test fails the build if any excluded field ever appears.
FAQ
Why do some listings come back as empty:unavailable instead of failing?
Because they are valid public pages that simply aren't bookable for your dates. That's a
successful, explained, un‑charged result — not an error.
Why might calendar/review fetches fall back to the browser tier? Calendar and reviews use Airbnb's persisted‑query GraphQL operations, whose hashes rotate. When a hash can't be resolved from the page, the Actor uses the Playwright fallback automatically.
Does it exceed Airbnb's ~270 results per search? Yes — it splits the search by price band (and optionally by map area for dense cities) and dedupes by listing id. The summary reports cap hits and split counts.
Is the Airbnb API key hardcoded? No. The X-Airbnb-Api-Key is Airbnb's own public
web client key, extracted from the page at runtime and refreshed if it rotates.
Pricing (pay‑per‑event)
Charged events: actor-start (once per run) and listing (per ok listing/change
record). Empty/delisted/summary records are not charged. Dollar amounts and volume
tiers are configured by the publisher in the Apify Console.
Development
npm install # install depsnpm test # offline parser + incremental tests (no network)npm run lint # node --check across all source filesnpm run smoke # ONLINE smoke test (needs network + Apify proxy)
See ./VERIFICATION.md for exactly what has been verified vs what requires a live Apify run.
Changelog
0.1.21
- Added the Actor output schema (
output_schema.json) and a full dataset field schema..actor/output_schema.json(linked viaoutputinactor.json) declares that results live in the default dataset, so the Store/Console Output tab renders properly.dataset_schema.jsonnow also documents every output field — types, titles, descriptions — so the field-level data contract is visible in the Store and API. The dataset schema is permissive (additionalProperties, nothing required) and was validated against a full 1,000-listing run plus the run-summary record with zero rejections, so it never blocks output. - README accuracy pass. Removed unsourced competitor success-rate figures, corrected the proxy description to datacenter-first with automatic residential fallback, and stated observed reliability from real runs (a 1,000-listing detail+reviews run completed with 0 blocks, every listing returned). Softened absolute competitor claims to defensible, hedged statements.
0.1.20
- Richest data by default. New runs now scrape full per-listing detail (
skipDetailPagesdefaults OFF) and reviews (scrapeReviewsdefaults ON) out of the box, so a first run shows the actor's complete output without tuning. A fast/cheap search-only mode is still one toggle away. (Cost note: detail + reviews on is ~2× the requests of search-only — priced accordingly.) - No more misleading 0-star ratings. A brand-new host or listing with no reviews previously
surfaced
ratingAverage/rating scores as0, which read like a 0-star score. Rating averages of 0 are now omitted (blank = "no rating yet"); review counts of 0 are unaffected.
0.1.19
- Richer host data (no extra requests). Added time as host (years/months), host
response rate, and host response time — all pulled from the host section already in the
detail call, so zero added cost. New columns
Host response rate/Host response timein the Overview. Host personal‑profile attributes (age, occupation, school, hometown, languages, bio highlights) are deliberately NOT collected — they identify individuals and fall outside the public‑professional‑data line. Host listings‑count is also excluded (host‑profile‑only; not worth the extra request).
0.1.18
- Lower cost + faster detailed scraping (POST-only detail path). Detailed listings now
fetch via a single lightweight
StaysPdpSectionsAPI call instead of downloading the full listing HTML page — the biggest item on the wire. Fewer requests per listing and much less bandwidth, with no loss of fields: the host section is fetched in the same call, and if a response ever comes back incomplete the actor automatically falls back to the full-page method, so coverage never silently regresses. - Cost-efficient datacenter-first proxy with automatic residential fallback. The default proxy is now datacenter (far cheaper per GB); if Airbnb blocks a datacenter request the actor escalates that request to residential automatically, and switches the whole run to residential if datacenter proves unreliable. You get datacenter cost with residential reliability as a safety net. Force residential anytime by selecting the RESIDENTIAL group. The run log reports the datacenter/residential split so the cost effect is measurable.
0.1.17
- Accessibility coverage fix. The accessibility section ships an empty
seeAll…stub alongside a populatedpreview…array on most listings; the parser was stopping at the empty stub and returning nothing (only ~1–2% of listings populated). It now picks the richest non-empty group array across both the default and modal accessibility sections, so accessibility features populate whenever the listing has them (and still return the fullseeAlllist when that one is the populated one). Confirmed against the live section shape captured by the 0.1.16 diagnostics.
0.1.16
- Internal diagnostics only (no behavior or schema change): the run-end section log now
also reports the accessibility section's nested group/item key shape and the metadata
carried by the
StaysPdpSectionsresponse. These confirm (a) whether low accessibility coverage is genuine host sparsity vs. a parser key mismatch, and (b) whether the detail path can drop its separate SSR page fetch in favor of the POST alone (a planned speedup).
0.1.15
- Accessibility features captured (new
accessibilityFeaturesfield). Detail runs now extract Airbnb's accessibility section — step-free entrances, accessible parking, grab rails, step-free bedroom/bathroom access, etc. — grouped the same way as amenities, with anavailableflag on each item so unavailable features are reported asfalserather than dropped. The enrichment POST now requests the accessibility section explicitly (it's gated by the amenities fragment we already include). This is a field most competing Airbnb scrapers don't expose and is valuable for accessibility-filtered search and compliance use cases. Only present on detail runs (skipDetailPages: false); omitted when a listing has no accessibility data, never shown as misleadingly empty.
0.1.14
- Results now stream out as the search finds them. Previously, on large/split-heavy
runs the actor gathered the entire search before emitting anything, so the results
count sat at 0 for several minutes and then every listing appeared at once — which reads
as "stuck" while watching a run. Listings are now processed and pushed the moment each
one is discovered, so output starts within seconds and climbs steadily. Long searches
also log periodic progress (
N listings found so far…). Dedup and themaxListingscap stay exact (each id is reserved synchronously before its work is dispatched), and a fetch failure partway through a search now still keeps the listings already emitted instead of discarding them. Default-size runs (≤ a few hundred) behave as before. - Known follow-up (not in this build): the search itself still fetches pages sequentially, so very large runs are still bound by per-page latency. Parallelizing search-page fetching is a separate, carefully-tested change (it raises block risk) and is intentionally deferred.
0.1.13
- Cap-splitting now triggers on an exhausted page budget, not just an explicit cap hit.
A bare location search (e.g. "Lisbon") runs Airbnb's page cursors out at ~15 pages /
~240–270 results with
hasNextPage:false, so the old logic saw "natural end" and never split — capping large markets at ~243 regardless ofmaxListings. Now, consuming the full page budget is itself a split signal, so price-band splitting fires and the run can climb toward the requested total. Small markets (which end before the budget) are unaffected and don't over-split.
0.1.12
- Request rate limiting. The actor now caps how many requests start per minute
(
maxRequestsPerMinute, default 120) on top of the concurrency cap, so large/scheduled runs stay polite and avoid anti-bot throttling even ifmaxConcurrencyis raised. At the observed throughput it doesn't slow normal runs; it's a safety ceiling for scale.
0.1.11
- Photo gallery, full description, and cancellation policy now enriched via the same
StaysPdpSectionsmechanism, using the exact section IDs the discovery log surfaced (PHOTO_TOUR_SCROLLABLE_MODAL,DESCRIPTION_MODAL,CANCELLATION_POLICY_PICKER_MODAL). The enrichment now triggers whenever any of amenities / photos / description / cancellation is missing (not just amenities), and the parsers pick the section that actually carries content (e.g. the full-text description modal over the short default, the scrollable gallery over the hero, the cancellation modal over the house-rules policies section). Merge stays content-aware — stubs are replaced, richer base data is never overwritten.
0.1.10
- Amenities confirmed working live (20/20 listings, ~40 amenities each; highlights 19/20) with no regressions, $0.032, 33s. PII re-checked clean (host carries no contact fields; reviews use an opaque reviewer id; no emails/phones).
- Added a one-line PDP section discovery log at the end of a run () so the exact section IDs of not-yet-captured content (the lazy-loaded photo gallery, cancellation policy) can be identified and requested precisely rather than guessed.PDP sections seen this run: …
0.1.9
- Fixed the silent amenities skip. Airbnb's SSR ships an empty amenities section
stub; the enrichment was treating "section exists" as "amenities present" and skipping
the
StaysPdpSectionsPOST entirely (no log, amenities empty every run). The check now requires real amenities content, and the merge replaces an SSR stub with the richer GraphQL section (while never overwriting a richer base section with a thinner one). The content test is shared with the parser so they can't drift. With this, the POST actually runs — so a run now either populates amenities or logs exactly why the POST was rejected.
0.1.8
- Concurrency. Listings now process in parallel up to
maxConcurrency(default 8) instead of one at a time, cutting wall-clock time substantially. Ids are reserved synchronously before any network call, so themaxListingscap and de-duplication stay exact under parallelism (covered by tests). If you ever see blocks, lowermaxConcurrency. - Amenities enrichment now logs why it failed (once per run) instead of failing silently — e.g. a rejected request or a response with no amenities section — so the cause is visible in the log.
0.1.7
- Amenities & highlights now captured. They're lazy-loaded and absent from the
page's SSR state, so when detail is on the Actor makes a
StaysPdpSectionsGraphQL POST (verified request shape + section IDs) and merges the missing sections (AMENITIES_DEFAULT,HIGHLIGHTS_DEFAULT, full description) into the listing — merge-only, so existing SSR fields (house rules, host, room/property type) can't regress. Best-effort: if the call fails, the listing keeps its SSR data unchanged. The run summary reports how many listings were enriched.
0.1.6
- Reviews & calendar now run over cheap HTTP GraphQL — no browser. Root cause of
the expensive run found from a live request capture: the reviews operation was named
StaysPdpReviewsbut Airbnb's real operation isStaysPdpReviewsQuery, so the GraphQL path never matched and every listing fell back to Playwright (the costly tier). Fixed the operation name, corrected the reviews/calendar request variables to the verified live shape, and seeded the known persisted-query hashes so GraphQL works from the first call. Playwright remains a self-healing fallback if Airbnb rotates a hash. Expected effect: per-listing browser launches drop to ~zero, with a large cost/time reduction on detail+reviews runs.
0.1.5
- Reviews work (verified on a live run: 419 reviews across 20 listings, no crashes or timeouts) thanks to the 0.1.4 Playwright fix.
- Cost reduction (reviews): each review fetch previously launched a browser (Playwright is the expensive tier — it drove ~all of a $1.18 / 8.5-min run). Now a single browser load harvests the persisted-query hash for reviews/sections/ calendar, so subsequent fetches use cheap HTTP GraphQL. Falls back to Playwright if the GraphQL replay fails (no regression). Net browser launches should drop sharply.
maxReviewsPerListingnow defaults to 50 (was unlimited) to bound cost on listings with hundreds of reviews; set0for the full set.
0.1.4
- Fix: Playwright fallback no longer hangs. It used
waitUntil: 'networkidle'with a 90s timeout; Airbnb never reaches networkidle, so every fallback timed out (a reviews run aborted at 7 min with only 4 listings). Now usesdomcontentloaded(45s cap), treats slow navigation as non-fatal, scrolls to trigger lazy content, and waits a bounded 12s for the target XHR — and preserves any response intercepted during load. - Reviews circuit breaker. After 3 consecutive review-fetch failures, reviews are disabled for the rest of the run (LOUD warning); listings still emit without reviews. Prevents one broken path from turning N listings into N slow failures.
- Clearer detail toggle. Reworded
skipDetailPagesto remove the double-negative confusion: checked = skip (fast), unchecked = full details. - Note: full amenities/highlights are lazy-loaded by Airbnb and may be absent from the fast SSR detail path; capturing them reliably is a pending fetch-strategy decision (GraphQL sections vs Playwright).
0.1.3
- Detail pages now OFF by default (
skipDetailPages: true). The default run is fast, cheap search-only; full per-listing detail is an explicit opt-in (skipDetailPages: false). A directly-supplied listing URL is still fetched in full regardless of the flag, so single-listing requests are never near-empty. maxListingsdefault is now 100 (was unlimited). Predictable cost/time out of the box; raise it for bigger pulls or set0for an explicit unlimited crawl.
0.1.2
- Fix: detail-page parse crash.
parseRegistrationcalled.toLowerCase()on a PDP section title that Airbnb now returns as an object, throwing aTypeErrorfor almost every listing when detail pages were enabled. Section title/heading are now string-coerced first. (Detail-page data was being discarded for ~96% of listings as a result.) - Fix: inverted discount price. A two-figure price label was split as
original=first / discounted=last, which produced an
originalPricebelow the current price. Discount handling now (a) only triggers on an explicit discount signal (was/now/originally/…), and (b) assigns discounted = lower figure, original = higher figure regardless of token order. Without a discount signal, only the headline price is emitted (no fabricated discount). - Added
pricing.priceLabel— the exact displayed price string, for transparency and to confirm the real label format from live runs.
0.1.1
- Fix: zero-result searches on a populated market. Airbnb moved each search
result from
result.listing(numericid) toresult.demandStayListing(base64"DemandStayListing:<id>"), with rating now inavgRatingA11yLabelas a string. The search parser was dropping every card. It now decodes the base64 id, reads the currentdemandStayListing/structuredContent/structuredDisplayPricepaths, and remains backward‑compatible with the old shape. - New fields:
isGuestFavorite(boolean) andbadges(string array), surfaced from result badges. Title is taken fromstructuredContent.primaryLine; the host‑bearingsecondaryLine("Stay with …") is never emitted (public‑data rule). - New status
error:parserStale. If result items are located but none parse, the run no longer reports a falseempty:zero_results. It emits an explained, un‑charged fault, logs a LOUD rot warning, and the run flags rather than crashes. - Cost: on a parse miss the collector now bails after one page instead of paginating the full 15‑page budget and price‑band splitting (≈14 fewer proxy requests per affected search).