# Changelog of Airbnb Reviews Scraper: Anonymous Ratings & Texts (`myth84/airbnb-reviews-scraper`) Actor

- **URL**: https://apify.com/myth84/airbnb-reviews-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/myth84/airbnb-reviews-scraper.md

### Changelog

History of `airbnb-reviews-scraper` (folder `actors/airbnb-reviews`). Versions are the Apify build numbers.

#### Unreleased (agent-facing polish: field order, first screen)

- Brief `docs/cloud-tasks/2026-10-24_studio-agent-facing-polish.md` (2026-10-24): what an AI agent reads first. No change to the code, the defaults, the prices or the output.
- **Input field order** (the Apify MCP search card lists the first 20 fields): unchanged: every field is among the first 20 and the proxy and crawl settings already close the form. Nothing renamed or removed.
- **README first screen**: under the title, "Verified daily by an automated run on Apify with the default input", the Free-plan and Business-plan prices per 1,000 results of the pricing file in force and the $0.00005 start fee per run (Apify's minimum); a one-line review request before the legal note. A test binds these figures to the pricing file and forbids "to confirm" and "no start fee" in the input schema and the README.

#### Unreleased (quality score: input documented for AI agents)

- Brief `docs/cloud-tasks/2026-10-20_studio-quality-score-insights.md` (2026-10-21): the input form passes the shared checks of `runInputQualityChecks` and the Store texts those of `runSearchTextChecks`.
- **Descriptions rewritten** (the default and its reason, an example where the format is free): `maxReviewsPerListing`, `maxResults`, `sortBy`, `reviewsSince` (example `2026-09`), `locale`, `translate`, `includeListingRatings`, `proxyConfiguration`, `maxRequests`, `delayMs`, `debugStoreResponses`.
- **`maxReviewsPerListing` and `maxResults` prefilled with 20** (defaults unchanged, 100 and 1,000): the prefilled input that Apify's daily test runs (20 most recent reviews of the prefilled listing) is now exactly the input of our daily check. `store.test.ts` expects the new prefill.
- **`inputs/daily-check.json`**: the keys equal to the defaults are dropped (`sortBy`, `locale`, `includeListingRatings`); `proxyConfiguration` stays (the committed-inputs test requires it). Same run as before: 20 most recent reviews of listing 21607939. The live daily-check task follows when the pilot re-sends it.
- **Store texts**: `store.json › searchSynonyms` lists "Airbnb review analysis", "sentiment analysis" and "anonymous Airbnb reviews", all in the README head the Store search indexes. README input table: the prefills of `maxReviewsPerListing` and `maxResults`.
- No code change: rows, logs and charges are identical.

#### Unreleased (README visibility)

- **README first screen and links** (brief `docs/cloud-tasks/2026-10-20_studio-readme-visibility-new-actors.md`, 2026-10-21), same layout as the public Actors (PR #50). The Store search indexes only the first ~3,000 README characters, so they now hold: an opening sentence with the phrase agents search ("Airbnb reviews scraper — anonymous, no reviewer names", Airbnb review analysis, sentiment analysis, competitor benchmarking, monitoring new reviews), the "No login, no cookies, no personal data" line joined to the anonymity sentence of 0.1, the Free-plan price in figures in *How much* ($2.20 per 1,000 reviews; Apify's $5 monthly credit covers about 2,272), computed from `pricing/2026-10-21_pay-per-event.autopilot.json` (Free tier of the primary event, one `apify-actor-start` event deducted), and a real output snippet (12 key columns of the README example row, nothing that looks like an identity: no `reviewKey`, no URL, no text with a masked first name). Further down: *Use it from your AI assistant* (MCP URL `https://mcp.apify.com?tools=myth84/airbnb-reviews-scraper` and a ready-to-copy prompt with a minimal input), *Alternatives and switching* (official API, home-made scraper, input-name mapping table, no competitor named), *More scrapers by Flatrow Data Studio* (Store links to the travel Actors already public, the Airbnb search first) with an invitation to rate the Actor (no counterpart), and the non-affiliation line at the very bottom. The cost section and the MCP cost bullet quote the same figures; the line 1 heading is unchanged. Checked by `runStoreChecks` (`readmeVisibility`) and by a snippet-columns test. No code change: rows, logs and charges are identical.

#### Unreleased

- **Fix: a throttled session gave 2 reviews per page until `maxRequests`** (brief `docs/cloud-tasks/2026-10-20_airbnb-reviews-repair-throttle.md`, 2026-10-21). Symptom: the second release control run `bIMLxjpHxGKFlFlSv` (`inputs/control-scale.json`, 1,700 rows expected) saved 636 rows: from the first short page (offset 100 on 6429072, 150 on 1916892) every page held exactly 2 reviews whatever the offset, 193 short pages, then `maxRequests reached` (200). Diagnostic runs of 2026-10-20 (`iiWTk9UoiTsm6yT8a`, `CdzWUeZBZCRft0H8U`, `0kKnyjlFnTlRYcdgo`; probe `Lxp9THvSyh2RkmbhP`, `Fp7SRstSLRYI76xsY`, audit run 3 `5A3jOcKx8gWbVEdUk`): neither the translation nor the listing page is the cause, the threshold goes from 2 to 8 full pages, and a throttled session never comes back to pages of 50. Remedy: a new session (proxy session, cookie jar, got-scraping session token). (1) **Preventive rotation**: a new session every `REVIEW_PAGES_PER_SESSION` = 3 reviews requests, shared by the listings running at once (never more than 3 reviews requests per session; listing pages do not count). (2) **Reactive rotation**: a page shorter than asked where a full page was due (`offset + limit ≤ reviewsCount`) is asked again at the same offset in a new session, after the pause `delayMs + 1.5 s`, up to `MAX_ROTATIONS_PER_SHORT_PAGE` = 3 times; the first full answer is used and pagination goes on normally, otherwise the longest short answer is kept and the crawl goes on at `offset + reviews received` as in the previous fix. The last partial page is not asked again: Airbnb's count can be one review off (898 paginated for 899 announced on 6429072 at the audit), which would cost 3 requests per listing for nothing. (3) **Guard**: a listing stops after `MAX_SHORT_PAGES_IN_A_ROW` = 5 short pages kept in a row, whether they bring new reviews or not, with a warning naming how many of its `reviewsCount` reviews Airbnb did not return; it replaces the guard of 3 short pages without a new review. `maxRequests` stays the hard limit, now exact with listings running at once (checked after the last await before each request). Session rotation no longer awaits (a generation number): two listings never rotate the same session twice. Counters in `RUN_STATS.extra` and in the end-of-run line: `shortPages` (short answers where a full page was due, retries included), `shortPagesRecovered`, `shortPagesKept`, `sessionRotations`, `preventiveRotations`; one `info` line per rotation. With `debugStoreResponses`, the answers in a new session are stored as `<listing>-reviews-<offset>-<hash>-rotation<n>`. Rows are unchanged: a run whose short pages are all recovered pushes exactly the rows of a run without short pages, and a run without short pages makes the same requests as before. Tests: Airbnb's throttle replayed by session (full pages for the first 1 to 8 requests of a session, then 2 reviews per page for good), the control input reaching its 1,700 rows in fewer than 60 requests, short page → new session → full page → pagination to `reviewsCount`, page still short in 3 new sessions, 5 short pages in a row (with and without new reviews), preventive rotation every 3 pages, sessions shared by 4 listings, `maxRequests` exact with listings at once, last partial page not asked again. README (*Short pages are not the end*, `maxRequests`) and the `maxRequests` description of the input schema updated. The control input keeps its 1,700 rows.
- **Fix: a short page in the middle of a history ended the listing** (brief `docs/cloud-tasks/2026-10-20_airbnb-fix-release.md`, 2026-10-20). Symptom: the release control run `X44QKypf4t7mtdYn0` (`inputs/control-scale.json`, 1,700 rows expected) succeeded with 354 rows, without a block or an error: `[1916892] 152 reviews from 4 page(s) (913 on the listing)`, `[6429072] 202 reviews from 5 page(s) (899 on the listing)`. The diagnostic run `0kKnyjlFnTlRYcdgo` showed pages of 50 up to offset 200, then a page of 2 reviews at offset 250 with the same `reviewsCount` (913), `isAutoTranslateOn: true` and no GraphQL error; the truncation point changes from run to run (offset 150, then 250), the short page holds 2 reviews each time, the audit had paginated 913/913 and 898/898. Cause: the end condition `reviews < limit` took that page for the last one. Now a listing ends on an empty page, at `offset ≥ reviewsCount`, or at the customer's caps (`maxReviewsPerListing`, `maxResults`, `reviewsSince`); a short page before `reviewsCount` is logged (`short page at offset 250 (2 of 50 reviews asked, 913 on the listing): going on from offset 252`) and the crawl goes on at `offset + reviews received` (deduplication by `reviewKey` covers overlaps). Guard: after 3 short pages in a row without a review id not yet returned for the listing, the listing stops with a warning naming how many of its `reviewsCount` reviews Airbnb did not return, and the run ends with a warning to run those listings again later; `maxRequests` stays the hard limit. Without `reviewsCount` in the response, a page shorter than asked is still the last one. New counters `RUN_STATS.extra.shortPages` and `listingsCutShort`. Listings without short pages make exactly the same requests as in 0.1 (the last partial page, `offset + received = reviewsCount`, still ends the listing without one more request). Tests: the control input replayed with the two short pages of the failed run reaches its 1,700 rows; short page in the middle, empty page, 3 short pages without a new review, reset of that count, last partial page, no `reviewsCount`, `maxRequests` while short pages keep coming. README: *Short pages are not the end* in *Limits and behaviour worth knowing*. The control input keeps its 1,700 rows.

#### 0.1 — 2026-10-20

- First version, written offline on the fixtures captured by the probe on 2026-10-20 (audit `docs/audits/airbnb-reviews.md`, brief `docs/cloud-tasks/2026-10-20_airbnb-reviews-build.md`, PR #41): Airbnb's `StaysPdpReviewsQuery` read with the public web key, 50 reviews per request, paginated by offset to the end of each listing; four sort orders, topic filter, full-text search, `reviewsSince` (early stop when sorted by most recent), per-listing and run caps, deduplication by `reviewKey`, 4 listings at a time in one sticky session. Optional listing page (`includeListingRatings`, on by default) for the listing's average rating, review count and six category ratings, and for the current API key.
- Output schema `travel-review/1.0` (new in `packages/core/src/schema/travel-reviews.ts`, shared with the Booking.com reviews to come). Anonymous by design (user decision of 2026-10-20): rows built from a whitelist; first names given by the review masked in texts (`[host]`, `[guest]`, one-edit variants of names of 5 letters or more); links → `[link]`, e-mails and phone numbers → `[redacted]`; month only; `reviewKey` = first 16 hex characters of SHA-256("airbnb:<id>"); host replies reduced to `hasHostResponse`. Names inside links and e-mails are left to the link and e-mail scrubs, which remove them whole (masking them first would break the e-mail scrub).
- Errors: unknown listing → counted as not found, the run goes on; `invalid_key` → key read again from the listing page, one retry; refused query → one retry with the previous persisted-query hash, adopted for the rest of the run once it works, then "Airbnb changed its reviews API; the Actor needs an update"; unusable response → one session rotation, then give-up. A 200 JSON answer is accepted even when a review text trips a block marker. Explicit failure on empty output, naming the cause.
- `debugStoreResponses` stores copies without `reviewer`, `reviewee`, `responder`, `localizedReviewerLocation` and `response`, and only the rating summary of listing pages (never a raw body).
- Store title shortened to "Airbnb Reviews Scraper: Anonymous Ratings & Texts" (49 characters): the brief's "…Ratings, Texts & Stay Types" (61) exceeds the 50-character limit checked by the Store tests.
- Control run input capped at 1,700 reviews (`maxResults`) for two listings of ~900 reviews each, so that the release script, which expects exactly `maxResults` rows, can check the count while the pagination runs close to the end of both histories.
- Shared schema bumped to `travel-review/1.1` by the Booking.com reviews build (brief `docs/cloud-tasks/2026-10-20_booking-reviews-build.md`): four Booking.com sub-score columns added after `listingRatingValue` (`listingRatingStaff`, `listingRatingComfort`, `listingRatingFacilities`, `listingRatingWifi`), always `null` for Airbnb; README example, column table and dataset schema updated, nothing else changes.
