# Changelog of TripAdvisor Scraper API — Hotels, Restaurants, Emails, Leads (`pro100chok/tripadvisor-all-in-one`) Actor

- **URL**: https://apify.com/pro100chok/tripadvisor-all-in-one/changelog.md
- **Full Actor documentation**: https://apify.com/pro100chok/tripadvisor-all-in-one.md

## Changelog

### 1.5 (2026-09-15) — hotel lists work again

- **Hotel searches came back empty.** TripAdvisor removed a field from its schema, and
  the stored query this Actor uses for hotel lists stopped validating against it — every
  city search, every start URL that resolves to a hotel list, and hotel emails with it,
  answered with an error instead of results. The Actor now uses TripAdvisor's current
  query, and those modes return data again.
- **One field is gone for good:** `strikethroughPrice` (the crossed-out "was" price next
  to a partner offer). TripAdvisor no longer publishes it at all, so it is no longer part
  of an offer record; every other offer field is unchanged.

### 1.4 (2026-08-27) — reviews that stop disappearing, emails for restaurants

**Fixed — every place with few reviews silently lost all of them**

- **A place with fewer reviews than you asked for came back with none.** The review API's
  last page is a 171-byte JSON body, and it contains the field name
  `ReviewsProxy_getReviewListPageForLocation`. The premium-proxy health check flagged any short
  body containing the word "PROXY" as a dead exit node — so that empty last page read as a
  proxy failure, the request was retried on three fresh IPs, and the place's review pull ended
  in an error. Every place with fewer reviews than `Max reviews per place` hit it: the record
  was still pushed, with `reviews: []`, indistinguishable from a place that genuinely has no
  reviews. A run of 10 New York places now returns 100 of 100 reviews where it returned 92 and
  one hard failure. The gateway check now looks only at bodies that are not a JSON or HTML
  document, and a regression test pins the exact response that broke it.
- **Reviews are retried on a fresh IP before being given up on.** A review pull that comes back
  empty because of one bad exit node now runs a second time on a new session and IP. If it
  still fails, the place is marked `reviewsError` in the dataset and counted in the run log and
  status message — "we could not read them" and "there are none" are no longer the same output.
- **A dead proxy gateway now falls back after 2 failures instead of 8.** A gateway with no exit
  node in the requested country answers with a plain `NO_HOST_CONNECTION` body rather than an
  error, and that was filed as a "blocked request" — the category that gets the most retries by
  design, because DataDome blocks a share of IPs normally. It is a refusal, not a block, so it
  now drops to the Apify Residential tier underneath almost immediately.

**Fixed — wasted requests**

- **No more request past the end of a list.** Review pagination and both city listings asked for
  one more page after the last one, which could only ever come back empty. For a place with 2
  reviews and a cap of 10, that empty page was the entire failure above. The same run now makes
  23 requests where it made 30 — less proxy traffic for identical output.

**Added — business emails for restaurants**

- **Restaurants now come with an `emails` array too.** TripAdvisor's restaurant listing exposes
  no email and no website field at all (unlike hotels, whose addresses it publishes directly).
  The venue's own domain is taken from its TripAdvisor menu link — booking, delivery and social
  hosts excluded — and the contact address read from that site, following its contact page (or
  trying `/contact` when the site renders its navigation in JavaScript). Measured over 360
  places across 6 cities: **43% of hotels** and **37% of restaurants** come back with an email,
  and **70% of restaurants** with a `website`.
- **New field `website`** on restaurant records — the venue's own site, which is also where
  their emails are read from.
- **New field `reviewsError`** — present only when the review feed could not be read for that
  place. Its absence with `reviewsScraped: 0` means the place really has no reviews.
- README's email numbers were corrected from a claimed ~65% of hotels to the measured 43%.

**Tests**

- `tests/test_transport_guards.py` (new, offline) pins all four fixes: the real empty-review-page
  body must not read as a gateway error, a short page must not trigger another request, a gateway
  refusal must demote the proxy tier at 2, and the email link normalisation.
- The daily smoke tests in raven-api gained the two paths that let this ship: `reviewsTail` (the
  last review page of a place that has 2 reviews — every existing review test used a large hotel
  whose page always fills, which is exactly why they stayed green) and `emails` (hotel addresses
  from the listing, restaurant menu links still pointing at their own domain).

### 1.3 (2026-08-24) — richer records

**Added — 33 more fields per place, no extra requests**

The GraphQL responses already carried far more than the Actor was mapping. Nothing here costs
an extra call; the hotel record went from 34 to 59 fields and the restaurant record from 26 to 39.

- **Per-category subratings on every hotel** — `subratings: {cleanliness, location, service, value}`,
  the same breakdown TripAdvisor shows on the review tab. Previously only reviews carried subratings.
- **Official star class** — `starRating` / `hotelClass` (e.g. `4.0`), plus the raw
  `hotelClassTagId` so a class TripAdvisor has not published a mapping for is never dropped.
- **Booking deep links** — every entry in `offers` now has a `bookingUrl` that resolves to the
  partner's booking page, decoded from TripAdvisor's own commerce token, next to
  `strikethroughPrice`, `shortDisplayPrice`, `pricingMode`, `priceTrendTier`,
  `availabilityStatus`, `timeOfPayment`, `isMemberRate`, `isMobileRate` and `providerLogoUrl`.
- **Live restaurant reservations** — `reservationOffers` carries each partner slot with its
  timeslots (`dateTime`, `status`, `bookingUrl`, `discountText`), plus `reservationUrl`,
  `restaurantSpecialOffer`, `hasMenu` and the machine-readable `openStatus`.
- **Sponsored / merchandising transparency** — `isSponsored`, `isPremium` and
  `merchandisingLabels` say when a row is a paid placement instead of an organic result.
- **Featured review** — `featuredReview` (id, title, text, rating, date, permalink, author and
  author profile) and `descriptionSource`, which says whether `description` is TripAdvisor's
  LLM summary or a real review snippet.
- **Direct contact details** — `businessPhone`, `businessPhoneRaw` and `websiteUrl` for
  Business Advantage listings, alongside the existing `emails`.
- **Ranking, geo and media context** — `rankingDenominator`, `rankingOutOf`, `awardYear`,
  `accommodationCategory`, `parentGeoName`, `countryId`, `priceRangeMinUsd`, `imageCaption`,
  `highlightUrl`, `notices`, `establishmentTypeIds`, `distanceFromCenter` and
  `reviewSnippets` (all highlighted excerpts, not just the first).
- **Reviews** now also carry `reviewer.username` and `reviewer.helpfulVotes`, plus `roomTip`,
  `publishPlatform`, `originalLanguage`, `translationType`, `isMachineTranslated`, `labels` and
  `createdDate` for the locales and place types where TripAdvisor returns them.

**Docs**

- The dataset schema now documents all 83 fields the Actor can emit, so the Output tab and the
  CSV/Excel exports match what the run actually produces.

### 1.2 (2026-08-21) — reliability

**Fixed — runs that failed for no good reason**

- **A proxy that stops working no longer takes the whole run with it.** There used to be one
  proxy per run and no way down: a gateway that went down mid-run, or a language whose country
  has no exit node, failed every request until the run ended — and the proxy setup call itself
  could raise before the first request, killing the run with a stack trace instead of an
  explanation. The run now walks a short ladder (premium → Apify Residential pinned to the
  language's country → Apify Residential unpinned), steps down only after a proxy has refused
  repeatedly, and names the switch in the log. Residential only — datacenter IPs are never used.
- **"No results" is no longer reported as a broken run.** A typo'd city, a query that only
  matches tours, a place with no reviews yet — all of those finished RED with a message
  blaming attractions. They now finish successfully with the actual reason in the status
  message. A run still FAILS when the failure is real: TripAdvisor blocked us, the proxy
  could not connect, or a saved GraphQL query rotated — each with its own message, and
  nothing is charged in any of those cases.
- **The status message stopped guessing.** Every empty run used to say "attractions and
  tours are not available", whatever the input was. The reason is now the real one:
  unmatched query, unreachable site, blocked requests, or a page type nobody can list.
- **A typo no longer costs money.** TripAdvisor's typeahead always answers with its closest
  guess — `zzzqqq nowhere` came back as "The Tunnel To Nowhere", which the Actor scraped and
  charged for. A guess that doesn't resemble the query is now reported as "no match" instead.

**Fixed — language settings that never worked**

- **Thai (`th`) runs failed 100% of the time.** The Actor pointed at
  `www.tripadvisor.com.th`, a host that does not exist — every Thai run died on DNS before
  reaching TripAdvisor. It now uses the Thai site's real address. All 38 language hosts are
  checked by the test suite from now on.
- **Searching in Japanese returned nothing.** TripAdvisor's localized typeahead indexes are
  not equal to the .com one: on the Japanese site 東京, 大阪, 京都, 札幌 and 新宿 match nothing
  but tour products. When the localized index has nothing usable, the lookup is now repeated
  against the English index — place and city IDs are the same worldwide, and the data itself
  is still fetched from the localized site, so results stay in your language.
- **A city search no longer returns a single hotel.** On several localized sites, hotels
  named after their city outrank the city itself; typing the city's name now scrapes the
  city's listing.

**Added — pages that used to be refused**

- **Attractions.** `/Attraction_Review-` URLs and searches like `Eiffel Tower` now return a
  record (name, URL, review count) with their full review threads. Price, amenities and
  ranking stay hotel/restaurant-only — TripAdvisor's API doesn't expose them for attractions.
- **City pages.** `/Tourism-g...` URLs (the page TripAdvisor links to when you click a city)
  are scraped as that city's listings.
- **Review pages.** `/ShowUserReviews-...` URLs resolve to the place they belong to.
- **Search URLs.** Pasting `/Search?q=Antalya` runs that search instead of failing.
- Anything else carrying TripAdvisor ids is routed by them: a `-d` place id resolves through
  the review API, a bare `-g` city id becomes that city's listing. A URL from another site is
  now named as such, instead of "unsupported page".

**Clearer**

- The log says which proxy the run is on, and names it again if the run had to fall back.
- Reviews are off by default and that is now stated at the start of every run, with a warning
  when 'Include reviews' is on but the per-place cap is 0 (which silently returned no reviews).
- Input field descriptions list exactly which TripAdvisor URL types are supported and what an
  attraction record does and does not contain.

### 1.1 — hotfix (2026-08-14)

**Fixed**

- Hotel listings work again. TripAdvisor changed the schema behind its saved hotel-list
  query, so every city listing came back as `Cannot query field "primaryMedia"` — an
  HTTP 200 error envelope, which the actor correctly turned into a failed run rather than
  an empty dataset. The query has been re-captured from a live session. Restaurants,
  search, reviews and place lookups were unaffected.

### 1.1 (2026-08-06)

**Fixed**

- A rotated TripAdvisor query id no longer looks like success. TripAdvisor answers a
  rotated persisted query with HTTP 200 and an `errors` array, which the actor read as
  "no results" — a run could finish green with an empty dataset (this is what made
  `"Antalya"` report "no geographic match" on 2026-08-04). Such responses now raise, and
  the run **fails** with the reason in its status message.
- Any run that scrapes 0 places now fails with a specific reason (unknown location,
  unsupported page type, or POI not found) instead of exiting 0 with an empty dataset.
- Place pages whose URL carries the wrong city are now resolved correctly. The geo in a
  detail URL is user-supplied and can be stale (`d197751` is slugged
  "Hotel\_Regina\_Louvre-Paris" but is actually in Chester) — the POI's real parent geo is
  now looked up first, so the search happens in the right city.
- Search phrases people actually type now work: `"hotels in Antalya"`, `"Antalya hotels"`,
  `"top 10 restaurants in Dubai"` used to return nothing at all (TripAdvisor's typeahead
  matches place names only). The category is taken from the phrase. Place names containing
  a category word (`"Hotel Astra Opera Astotel"`, `"Bar Harbor"`) are left untouched.
- `category: "all"` with an item limit now returns a mix: the limit is split evenly between
  hotels and restaurants instead of being spent entirely on hotels.
- Searches typed in other languages no longer fail the run. `"Antalya otelleri"` (Turkish for
  "Antalya hotels") matched no place name and returned nothing; the query is now retried
  without its trailing / leading word, which recovers the place name in any language. The
  retry only happens after a query came back empty, so a normal search still costs one request.

**Input — simpler**

- **No more `mode`.** One `search` field (cities, regions, geo IDs or place names) plus
  `startUrls`; both are scraped when both are filled. Previously two of the three input
  fields were silently ignored, which is how a filled-in form could still scrape nothing.
  Inputs that still send `mode` keep their exact old behaviour and log what is being ignored.
- `maxPois` moved to Advanced; `maxRetries` default lowered from 5 to 3.

**Less proxy traffic**

- Listing pages are requested at the size actually needed — a 10-place run pulls a 10-place
  page (~24 KB) instead of a 30-place one (~58 KB).
- Several place URLs from the same city are resolved in ONE listing pass instead of one scan
  per URL, and the scan stops as soon as every wanted POI is found.
- The per-POI listing scan is capped at 120 entries (was 300). A POI ranked below that is
  fetched from the review API (~1 KB, `isPartialRecord: true`) instead of being dropped
  after ten billed pages.
- Retrying no longer multiplies: the outer retry wrapper is gone, so a request is tried
  `maxRetries + 1` times (each on a fresh IP) instead of up to 75 times, with shorter waits.
- Reviews are no longer fetched beyond the item limit: enrichment slots are reserved before
  any review request goes out, so concurrent batches can't double-spend the budget.
- Every run logs its traffic (`22 request(s), 0.47 MB ...` broken down per endpoint).

**Faster**

- Listing pages are enriched and pushed as they arrive, so review fetching overlaps
  pagination instead of waiting for the whole city to be listed first.

### 1.0 (2026-07-07)

- Initial release: scrape TripAdvisor hotels, restaurants, and things-to-do in one Actor.
- Input by start URL, search keyword, or city/location name; filter by category.
- Place records include name, rating, review count, ranking, GPS, address, phone, official website, and discovered contact emails.
- Type-specific fields: hotels (star rating, price/deals, amenities, languages), restaurants (cuisines, meal types, features, opening hours), attractions (categories, duration, ticket price).
- Full reviews embedded inline per place (title, text, rating, dates, trip type, reviewer, sub-ratings, photos, owner responses) via the review GraphQL API with HTML pagination fallback.
- Awards, breadcrumbs, and image galleries captured; four dataset views (overview, hotels, restaurants, attractions).
- Pay-per-event pricing: charged per place result and per review.
