# TripAdvisor Restaurant Scraper - Ratings, Prices, Phone & GPS (`s-r/tripadvisor-restaurants`) Actor

Scrape one TripAdvisor city list per run and get every restaurant as a row: name, rating, review count, price band, cuisines, phone number, full address and GPS coordinates. For restaurant market research, local lead lists and location data, with no TripAdvisor account or API key needed.

- **URL**: https://apify.com/s-r/tripadvisor-restaurants.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Travel, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 run start fees

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Restaurant Scraper - City Restaurant Lists as JSON

The TripAdvisor restaurant scraper reads one city's restaurants page on tripadvisor.com and
returns every restaurant as a row: name, rating, review count, price band, cuisines, phone
number, full address and GPS coordinates. Paste a list URL, choose how many restaurants you
need, and download the rows as JSON, CSV or a spreadsheet. No TripAdvisor account and no API
key are involved — the actor reads the same public list page you would open in a browser.
Built for restaurant market research, local lead lists and location data.

### What the TripAdvisor restaurant scraper returns

TripAdvisor publishes each city list as a structured `ItemList`, and the actor turns it into
one flat row per restaurant:

| Field | What it is |
|---|---|
| `detail_id` | TripAdvisor's own id from the URL. Stable, so you can join runs on it |
| `name` | Restaurant name |
| `rating`, `review_count` | Average out of 5, and how many reviews it is based on |
| `price_range` | Price band as TripAdvisor states it, for example `$$ - $$$` |
| `cuisines` | Cuisine types, as a list; `null` when TripAdvisor states none |
| `telephone` | Phone number |
| `street`, `postal_code`, `country` | Address lines |
| `latitude`, `longitude` | GPS coordinates |
| `accepts_reservations` | Whether reservations are taken |
| `has_menu` | Menu link when published; a URL, `null` or an empty string |
| `opening_hours` | Opening hours specification, one entry per weekday |
| `description`, `locality`, `region` | Present in the schema, `null` on list pages |
| `image` | Restaurant photo URL(s), passed through as published: a list in practice, a single string or `null` |
| `url` | The TripAdvisor page for this restaurant |
| `position` | Row number in this run, counted after de-duplication |
| `fetched_at` | When the row was read |
| `_identity` | Stable identity of the record, `place:<detail_id>` |

The coordinates are worth calling out. Most restaurant data sources hand you an address
string and leave geocoding to you, which costs money and introduces errors. These come with
the row. In a real 30-restaurant run for London, all 30 rows carried a rating and
coordinates and 29 carried a phone number; the run summary reports those counts for every
run, so you can see the coverage of your own data instead of assuming it.

Cuisine data is not published for every listing on every page. Those rows return `null`
rather than an empty list, because "not stated" and "no cuisine" are different facts.

### TripAdvisor restaurant scraper input

| Field | Type | Default | What it does |
|---|---|---|---|
| `listing_url` | string | Paris restaurants list | A TripAdvisor restaurants list page for one city |
| `limit` | integer | `60` | Restaurants to return, 1 to 1000 |
| `retries` | integer | `4` | Attempts per page, 1 to 8 |

To get a listing URL, browse to a city's restaurants on TripAdvisor and copy the address.
They look like
`https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html`, where the `g`
number identifies the city. The default input already points at Paris, so a first run works
without editing anything.

Filters you apply in the browser — cuisine, price range, rating, open now — travel in the
same URL and paginate normally. Filtering on TripAdvisor before the run is cheaper than
filtering the output afterwards.

TripAdvisor paginates by rewriting the **path**, not by adding a query parameter: page two
of the Paris list is `Restaurants-g187147-oa30-Paris_Ile_de_France.html`. Appending
`?page=2` does nothing at all and quietly returns page one forever, which is an easy way to
collect the same 30 rows several times over without noticing. The actor rewrites the path
correctly, and the tests pin that behaviour.

Each page holds 30 restaurants, so a `limit` of 60 costs two pages. The actor reads at most
20 pages per run, which makes 600 restaurants the practical ceiling even though the input
accepts up to 1000. Ask for 600, not 1000, if you want one run to finish in one pass.

Example input:

```json
{
  "listing_url": "https://www.tripadvisor.com/Restaurants-g186338-London_England.html",
  "limit": 30,
  "retries": 4
}
```

### TripAdvisor restaurant scraper output

The default dataset holds one record per restaurant, in page order, with `position` running
from 1 across every page the run collected. Fields the list page does not publish stay
`null`; nothing is derived for you. A real row from the London run above, with `opening_hours`
and `image` left out for brevity:

```json
{
  "detail_id": "12392823",
  "name": "Leicester Square Kitchen",
  "rating": 4.8,
  "review_count": 3069,
  "price_range": "$$ - $$$",
  "cuisines": ["Mexican", "Peruvian"],
  "telephone": "+44 20 7666 0902",
  "street": "31-36 Leicester Square, London WC2H 7LH England",
  "postal_code": "WC2H 7LH",
  "country": "United Kingdom",
  "latitude": 51.509975,
  "longitude": -0.129723,
  "accepts_reservations": true,
  "has_menu": "",
  "url": "https://www.tripadvisor.com/Restaurant_Review-g186338-d12392823-Reviews-Leicester_Square_Kitchen-London_England.html",
  "position": 1
}
```

The key-value store holds three records. `summary` — the record name the original actor
used — reports `restaurantsReturned`, `pagesFetched`, `withRating`, `withCoordinates`,
`withPhone`, `withCuisines`, `refused`, `fetchedInSeconds` and `errorCount`, and on a run
that could not read a page also `lastStatus` and `attempts`, plus a few technical run
notes (`accessNote`, `exitCountry`, `proxyConfigured`, `proxyPinnable`) that existing
integrations keep reading. `OUTPUT` carries the same run summary under the record name
many integrations read it from; the one number that can differ is `itemCount`, which
counts rows collected before the free-plan cap in `summary` and rows actually delivered
in `OUTPUT`. `errors` is written when the run had any, and lists `{code, message}` pairs
with the code `bad_input` (the input is not a TripAdvisor list URL), `no_results` (the
page loaded but holds no restaurants), `fetch_failed` (the page could not be read) or
`no_restaurants` (a page type without restaurant entries).

Soft problems never fail a run. A page that loads but carries no restaurant list, a city
that returns nothing, or a page the site refuses all produce a **successful run with an
empty dataset**, and `summary.refused`, `summary.lastStatus` and `summary.attempts` say
whether the cause was a refusal or a genuinely empty city. An empty city and a blocked run
never look the same. A 403 is never parsed as an empty listing.

If a later page fails after earlier pages succeeded, the rows already collected stay in the
dataset and `errors` describes what happened.

### Use cases for restaurant data

- **Restaurant market research.** Every restaurant in a city with rating, review volume,
  price band and cuisine. Group by cuisine to see which segments a market is saturated with,
  or compare review counts between two cities.
- **Location intelligence.** Coordinates come with the row, so a restaurant dataset maps
  directly without a separate geocoding step.
- **Restaurant leads.** Name, phone number and address per restaurant, filterable by rating,
  price band and reservation support — the fields a local sales list needs.
- **Competitive benchmarking.** Track a set of `detail_id` values over time and watch
  ratings, review counts and price bands move for a competing group.
- **Food and beverage market research.** Combine the lists of several cities into one
  restaurant dataset for entry studies, expansion planning or supply-side analysis.

### How this TripAdvisor restaurant scraper compares

- **The official TripAdvisor API** is aimed at partners who display TripAdvisor content in
  their own product. It needs an approved use and an API key, and it is not built for
  exporting a whole city list into a spreadsheet. This actor reads the public list page and
  hands you the rows.
- **Other TripAdvisor actors in the Store** mostly target reviews. If you want full review
  text, owner responses or hotel pages as well, look at the wider TripAdvisor scraper from
  the same account, which covers restaurants, hotels and reviews. This actor is the
  restaurants list specialist: one input, one page type, one predictable output.
- **Scraping it yourself** from a "scrape Tripadvisor" tutorial means maintaining parsers,
  pagination rules and retries, and rewriting them whenever the page changes. Here you set a
  URL and a limit, and the field names stay stable between runs.
- **Browser extensions** export what you are looking at, page by page. This actor collects up
  to 600 restaurants per run and can be scheduled or called through the Apify API.
- **A generic restaurant scraper** usually targets delivery platforms or a chain's own site.
  This one is built for TripAdvisor list pages, where rating, review count and price band
  come from the same source diners actually read.

### Pricing and free plan

Pricing is unchanged from the actor you may already be using. A run-start event costs
**$0.001** and is charged once per run, including runs that return nothing. Each restaurant
delivered to the dataset costs **$0.002**. A run with `limit: 60` therefore costs
$0.001 + 60 × $0.002 = **$0.121**, and a run whose city turns out to be empty costs only the
start fee. The event names and amounts on the actor page are the ones applied to your run.

Users on a free Apify plan receive at most **10 rows per run**. The actor clamps `limit` to
that cap, the run output reports `freeTierRunCap` (the cap itself) and `freeTierCapped`
(whether it bit), so a short dataset on a free plan is never mistaken for a city with ten
restaurants. Paid users keep the configured `limit`.

### FAQ

#### Which URL should I paste?

A TripAdvisor restaurants list page for one city, for example
`https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html`. Open the city on
TripAdvisor, choose Restaurants, copy the address from the browser. Filters you set before
copying stay in the URL and are applied during the run.

#### Do I need a TripAdvisor account or an API key?

No. The actor reads public list pages only. You need an Apify account to run it; there is
nothing to configure on the TripAdvisor side.

#### How many restaurants can one run return?

30 restaurants per page, up to 20 pages, so 600 restaurants in one run. Set `limit` to the
number you need; the default of 60 collects two pages.

#### What happens when TripAdvisor refuses a page?

The actor retries the page up to `retries` times. If it is still refused, the run finishes
successfully with whatever rows it collected, and the dataset stays empty in that case.
`summary.refused`, `summary.lastStatus` and `summary.attempts` in the key-value store tell
you it was a refusal rather than a city without restaurants.

#### Can I scrape restaurants in other countries?

Any `tripadvisor.` list URL is accepted, so regional sites work as input. The field names
are identical everywhere; only wording and price-band symbols follow the local site. Coverage
of the regional sites has not been measured as thoroughly as tripadvisor.com, so check the
first rows of a run before you build on it.

#### Can I export the results?

Yes. The dataset supports Apify's usual JSON, CSV and spreadsheet exports plus the dataset
API. Keep `detail_id` when you join repeated runs — restaurant names and even page order can
change between runs, while that identifier does not.

# Actor input Schema

## `listing_url` (type: `string`):

A TripAdvisor restaurants listing page, for example https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html. Browse to the city on TripAdvisor and copy the address.

## `limit` (type: `integer`):

How many restaurants to return. TripAdvisor returns 30 per page.

## `retries` (type: `integer`):

How many times to re-request a refused or short page before giving up on it. Each retry draws a new exit address.

## Actor input object example

```json
{
  "listing_url": "https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html",
  "limit": 60,
  "retries": 4
}
```

# Actor output Schema

## `restaurants` (type: `string`):

One row per restaurant.

## `summary` (type: `string`):

Counts and coverage of ratings, coordinates and phone numbers.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# 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 = {
    "listing_url": "https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html",
    "limit": 60,
    "retries": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/tripadvisor-restaurants").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 = {
    "listing_url": "https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html",
    "limit": 60,
    "retries": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/tripadvisor-restaurants").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 '{
  "listing_url": "https://www.tripadvisor.com/Restaurants-g187147-Paris_Ile_de_France.html",
  "limit": 60,
  "retries": 4
}' |
apify call s-r/tripadvisor-restaurants --silent --output-dataset

```

## MCP server setup

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

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/fSGf8i7qGFPrhZ8WV/builds/XL15OWXfVdbQRTkI6/openapi.json
