TripAdvisor scraper and unofficial TripAdvisor API: hotels, restaurants and attractions with prices, partner offers and booking links, star class, per-category subratings, amenities, cuisines, hours, reservation slots, GPS, phones, emails and full review threads. From $0.25/1k reviews.
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.
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.