# Tripadvisor Scraper (`bgfc97/tripadvisor-scraper`) Actor

🔥 Up to 30% OFF! Scrape TripAdvisor — no login. Hotels, restaurants & attractions: rating, reviews count, ranking, price level, address, coordinates, photos, sub-ratings + reviews. Fast & reliable, pay per item.

- **URL**: https://apify.com/bgfc97/tripadvisor-scraper.md
- **Developed by:** [Bruno](https://apify.com/bgfc97) (community)
- **Categories:** Travel, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 item scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 🔥 TripAdvisor Scraper (Real Browser)

Gets **real hotels, restaurants and attractions** (plus their reviews) from TripAdvisor by driving
an actual headless browser (Playwright/Chromium) through an **Apify residential proxy** — the same
way a person would browse the site. A plain HTTP request to TripAdvisor gets a 403/challenge page;
this Actor renders the real page, waits for content to actually appear, and reads it off the live
DOM **plus TripAdvisor's own public structured data** (schema.org JSON-LD + Open Graph tags).

**Public data only, no login. Honesty first:** if TripAdvisor shows a CAPTCHA/"access denied"
challenge, or simply doesn't render the expected cards/detail page in time, this Actor reports
exactly what it saw (with a raw page-text snippet) instead of guessing or inventing data. It never
fabricates a rating, a review or a place that isn't really on the page.

### What you get per place

- **Identity & location:** name, category, type(s), TripAdvisor `locationId` & `geoId`, canonical URL.
- **Scores:** overall rating, review count, aggregate rating (from JSON-LD), **ranking** parsed into
  position / total / category (e.g. `#12` of `419` `Hotels in Rome`), and a **rating distribution**
  (Excellent / Very good / Average / Poor / Terrible counts) when shown.
- **Contact:** phone, alternate phone, **website**, full street **address** plus a structured
  `addressDetails` (street / city / region / postalCode / country) and **GPS latitude/longitude**.
- **Commercial:** price range, price level, hotel class (stars), **cuisines** (restaurants),
  amenities, **opening hours**.
- **Media:** main image + a gallery of canonical photo URLs (and card thumbnails on listings).
- **Reviews:** title, rating, sub-ratings, text, date, **date of stay**, **trip type**, author +
  profile URL, review permalink, contribution & helpful-vote counts.

### Input

```json
{
  "searchQuery": "hotels in Rome",
  "category": "hotels",
  "urls": [],
  "maxItems": 30,
  "maxReviews": 0,
  "maxReviewPages": 0,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] },
  "timeoutSecs": 60
}
```

- `searchQuery` — free-text TripAdvisor search, e.g. `"hotels in Rome"`, `"restaurants in Lisbon"`,
  `"things to do in Paris"`. Optional, but you must supply this and/or `urls`.
- `category` — `hotels` / `restaurants` / `attractions`. Controls which TripAdvisor URL pattern is
  searched/scraped for `searchQuery` (and for any `urls` that are listing pages). Default `hotels`.
- `urls` — TripAdvisor URLs to scrape directly instead of / in addition to searching:
  - **Detail pages** (a `Hotel_Review-`, `Restaurant_Review-` or `Attraction_Review-` URL) are
    always opened for full details (+ reviews if `maxReviews` > 0), regardless of `category`.
  - **Listing pages** (a `Hotels-g...`, `Restaurants-g...` or `Attractions-g...` URL) are scraped
    like a search result page — their cards are extracted the same way.
- `maxItems` — max places returned, default `30`, max `300`.
- `maxReviews` — reviews per place (title, rating, sub-ratings, text, date, date of stay, trip type,
  author, permalink). Default `0`, max `500`. When `0`, places found via `searchQuery`/listing pages
  are returned with just their **card** data (name, rating, review count, price, location, thumbnail,
  detail URL) — the individual detail page isn't opened, to keep the run fast and cheap. Explicit
  **detail** URLs in `urls` are always opened regardless, just without reviews if `maxReviews` is `0`.
  When `maxReviews` is larger than one page, the Actor **automatically walks TripAdvisor's review
  pagination** (`-or10-`, `-or20-`, …) to collect that many.
- `maxReviewPages` — optional hard cap on extra review pages opened per place. `0` = automatic.
- `proxyConfiguration` — defaults to Apify **RESIDENTIAL** proxy, strongly recommended: TripAdvisor
  blocks datacenter IPs hard.
- `timeoutSecs` — how long to wait for a page to render before giving up, default `60`.

### Output

One item per place. From a **search/listing page with `maxReviews: 0`**:

```json
{
  "category": "hotels",
  "name": "Hotel Artemide",
  "rating": 4.5,
  "reviewCount": 3120,
  "priceRange": "$180 - $260",
  "priceLevel": null,
  "location": "Rome, Italy",
  "detailUrl": "https://www.tripadvisor.com/Hotel_Review-g187791-d229722-Reviews-...html",
  "listPosition": 1,
  "locationId": "229722",
  "geoId": "187791",
  "thumbnail": "https://dynamic-media-cdn.tripadvisor.com/.../photo.jpg",
  "rawText": "Hotel Artemide 4.5 ... 3,120 reviews $180 - $260 ...",
  "scrapedAt": "2026-10-05T12:00:00.000Z"
}
```

From a **detail page** (explicit `urls`, or `maxReviews` > 0):

```json
{
  "category": "hotels",
  "name": "Hotel Artemide",
  "rating": 4.5,
  "reviewCount": 3120,
  "ranking": "#12 of 419 Hotels in Rome",
  "rankingPosition": 12,
  "rankingTotal": 419,
  "rankingCategory": "Hotels in Rome",
  "priceRange": "$180 - $260",
  "priceRangeDetail": "$$$",
  "priceLevel": null,
  "hotelClass": 4,
  "location": "Rome, Italy",
  "address": "Via Nazionale, 22, 00184 Rome Italy",
  "addressDetails": { "street": "Via Nazionale, 22", "city": "Rome", "region": "Lazio", "postalCode": "00184", "country": "Italy" },
  "latitude": 41.9015,
  "longitude": 12.4953,
  "phone": "+39 06 489911",
  "website": "https://www.hotelartemide.it/",
  "cuisines": null,
  "amenities": ["Free Wifi", "Pool", "Restaurant", "..."],
  "openingHours": null,
  "aggregateRating": 4.5,
  "aggregateReviewCount": 3120,
  "ratingDistribution": { "excellent": 2500, "veryGood": 450, "average": 120, "poor": 30, "terrible": 20 },
  "mainImage": "https://.../photo.jpg",
  "images": ["https://.../1.jpg", "https://.../2.jpg"],
  "types": ["Hotel"],
  "description": "Set in the heart of Rome...",
  "locationId": "229722",
  "geoId": "187791",
  "canonicalUrl": "https://www.tripadvisor.com/Hotel_Review-...html",
  "detailUrl": "https://www.tripadvisor.com/Hotel_Review-...html",
  "reviewsCount": 25,
  "reviews": [
    {
      "title": "Wonderful stay",
      "rating": 5,
      "author": "janedoe123",
      "date": "August 2026",
      "dateOfStay": "August 2026",
      "tripType": "Couples",
      "subRatings": [5, 5, 4, 5],
      "contributions": 42,
      "helpfulVotes": 3,
      "profileUrl": "https://www.tripadvisor.com/Profile/janedoe123",
      "reviewUrl": "https://www.tripadvisor.com/ShowUserReviews-...html",
      "text": "..."
    }
  ],
  "scrapedAt": "2026-10-05T12:00:00.000Z",
  "bodyTextSnippet": "...first 1500 chars of the rendered page text, as a verification backstop..."
}
```

`rawText` / `bodyTextSnippet` are kept so you can verify or re-parse anything the regex-based field
extraction missed — nothing is hidden. Some heuristic fields (`location` on cards, `title`/`author`
on reviews, `ratingDistribution`) can come back `null` if TripAdvisor's current markup doesn't
expose them — the structured JSON-LD fields and the raw text are always there as fallbacks.

If a page couldn't be read, you get a single honest item instead, e.g.:

```json
{ "error": "BLOCKED: TripAdvisor served an anti-bot/challenge page instead of content at ...", "url": "..." }
```

Other possible `error` values: `NO_ITEMS_MATCHED` / `NO_ITEMS_PARSED` (TripAdvisor changed its
markup or showed an unrelated page), `TIMEOUT` (nothing rendered in time), `NAVIGATION_FAILED`
(after retries), or a plain input-validation message.

### How it works

1. Launches real Chromium (Playwright) through Apify's **RESIDENTIAL** proxy with realistic headers
   and viewport, rotating browser/proxy sessions so one soured IP can't poison a whole run.
2. For `searchQuery`, opens `https://www.tripadvisor.com/Search?q=...`; for `urls`, opens each URL
   directly, classified as a **detail** page or a **listing** page by TripAdvisor's own stable URL
   patterns.
3. Dismisses TripAdvisor's cookie-consent (OneTrust) banner and common interstitials.
4. Detects an anti-bot/CAPTCHA challenge page by its text and **retries with a fresh proxy
   session** (up to 4 attempts) before giving up and reporting `BLOCKED`.
5. On a **search/listing page**: scrolls to trigger lazy loading, waits for real category result
   links to render, then extracts each card (name, rating, review count, price, location, thumbnail,
   position) anchored on those links — de-duplicated by TripAdvisor's own numeric place id. If no
   cards render, it follows the category's "see all" listing-page link once before giving up honestly.
6. On a **detail page**: waits for the `<h1>`, then mines both the rendered DOM **and TripAdvisor's
   schema.org JSON-LD + Open Graph tags** for name, rating, review count, ranking, structured
   address, GPS coordinates, phone, website, price, cuisines, hotel class, amenities, hours and
   photos — and, if `maxReviews` > 0, walks review pagination to collect enriched reviews.

### Notes

- TripAdvisor's markup is obfuscated/React-rendered and changes over time; extraction combines
  stable URL-pattern anchors, schema.org JSON-LD and regex/heuristics over rendered text and
  `aria-label`s. `rawText`/`bodyTextSnippet` are always included as a source-of-truth backstop.
- Residential proxy IPs and TripAdvisor's own bot defenses mean an occasional run may still hit a
  challenge page after retries — when that happens the Actor says so plainly rather than pretending
  to have data.
- Setting `maxReviews: 0` for a search/listing run is the cheap/fast mode (one page load covers up
  to `maxItems` places). Setting `maxReviews` > 0 opens one extra page load per place (plus review
  pagination pages for deep review pulls).

### ⭐ Enjoying this Actor?

A quick **rating/review** helps others find it. Found a query pattern TripAdvisor didn't render
right? Open a ticket on the **Issues** tab.

# Actor input Schema

## `searchQuery` (type: `string`):

Free-text TripAdvisor search, e.g. "hotels in Rome", "restaurants in Lisbon", "things to do in Paris". Either this or "urls" (or both) must be provided.

## `category` (type: `string`):

Which kind of TripAdvisor listing to search/scrape when using "searchQuery". Ignored for URLs that are already TripAdvisor detail pages (their own type is used instead).

## `urls` (type: `array`):

TripAdvisor URLs to scrape directly. Can be detail pages (e.g. a Hotel_Review-/Restaurant_Review-/Attraction_Review- page — opened for full details + reviews) and/or listing pages (e.g. a Hotels-g.../Restaurants-g.../Attractions-g... page — its cards are scraped like a search result). Either this or "searchQuery" (or both) must be provided.

## `maxItems` (type: `integer`):

Maximum number of places (hotels/restaurants/attractions) to return.

## `maxReviews` (type: `integer`):

How many reviews to extract per place (title, rating, sub-ratings, text, date, date of stay, trip type, author, permalink). The Actor automatically walks TripAdvisor's review pagination to reach this number. Set 0 to skip opening detail pages / skip reviews entirely for places found via search — explicit detail URLs in "urls" are still opened for full details even at 0, just without reviews.

## `maxReviewPages` (type: `integer`):

Hard cap on how many extra review-pagination pages to open per place while collecting reviews. 0 = automatic (derived from "Max reviews", capped for safety). Raise it only if you need very deep review histories.

## `proxyConfiguration` (type: `object`):

TripAdvisor aggressively blocks datacenter IPs — RESIDENTIAL proxy is strongly recommended (and used by default).

## `timeoutSecs` (type: `integer`):

How long to wait for a TripAdvisor page to actually render results before giving up.

## Actor input object example

```json
{
  "searchQuery": "hotels in Rome",
  "category": "hotels",
  "maxItems": 30,
  "maxReviews": 0,
  "maxReviewPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "timeoutSecs": 60
}
```

# Actor output Schema

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "hotels in Rome",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bgfc97/tripadvisor-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "hotels in Rome",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("bgfc97/tripadvisor-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "hotels in Rome",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call bgfc97/tripadvisor-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bgfc97/tripadvisor-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gTBrUcsUnZmkqaRSL/builds/hRo6RpFy854d7OD8n/openapi.json
