# OpenTable Reviews Scraper – Restaurants, Ratings & Diner Data (`abotapi/opentable-reviews-scraper`) Actor

Scrape full OpenTable.com restaurant reviews by keyword, area, or restaurant profile URLs. Collect every available review with text, ratings, diner profile data, tags, photos, restaurant metadata, cuisines, neighborhood, and profile links in structured rows.

- **URL**: https://apify.com/abotapi/opentable-reviews-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Automation, Developer tools, Other
- **Stats:** 30 total users, 3 monthly users, 80.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 review scrapeds

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

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenTable Reviews Scraper

Scrape full OpenTable restaurant reviews by keyword and area or by pasted restaurant profile URLs. The actor collects every available review for each restaurant and returns structured rows with review text, ratings, diner profile data, review tags, review photos, restaurant metadata, cuisines, neighborhood, and profile links.

### What You Get

Each dataset row is one OpenTable review. A keyword search collects the full review history for every restaurant it discovers around your coordinates; URL mode collects every review for each restaurant link you paste.

```json
{
  "reviewId": "OT-4528-2110885052-140154862704",
  "reviewText": "Great place for dinner! The bar is beautiful and casual while the dining space is really nice!",
  "ratingOverall": 5,
  "ratingFood": 5,
  "ratingService": 5,
  "ratingAmbience": 5,
  "ratingValue": 5,
  "ratingNoise": "MODERATE",
  "reviewType": "OPENTABLEVERIFIED",
  "submittedDateTime": "2026-06-10T16:02:39Z",
  "dinedDateTime": "2026-06-09T18:45:00-04:00",
  "dinedDate": "2026-06-09",
  "reviewHelpfulUp": 0,
  "reviewHelpfulDown": 0,
  "reviewHelpfulScore": 0,
  "restaurantReplyText": null,
  "reviewerName": "Lynn",
  "reviewerLocation": "New York City",
  "reviewerApprovedReviewCount": 3,
  "reviewPhotoUrls": ["https://resizer.otstatic.com/v4/photos/94818492-1?width=160&height=160"],
  "reviewPhotoCount": 1,
  "restaurantId": 4528,
  "restaurantName": "Nobu Downtown",
  "restaurantUrl": "https://www.opentable.com/r/nobu-downtown-new-york",
  "restaurantPrimaryCuisine": "Japanese",
  "restaurantRating": 4.7,
  "restaurantTotalReviewCount": 5397
}
```

### Input Modes

#### Keyword Search

Use keyword search when you want discovery by restaurant, cuisine, or dining phrase around a latitude and longitude.

```json
{
  "mode": "search",
  "keywords": ["sushi", "steakhouse"],
  "location": "New York",
  "maxItems": 20
}
```

`location` accepts a city, neighborhood, or address (for example `New York`, `Chelsea Manhattan`, or `90210`) and is resolved to map coordinates automatically. For precise control you can instead set `latitude` and `longitude`, which override `location` when both are provided.

#### Restaurant URLs

Use URL mode when you already have OpenTable restaurant profile links.

```json
{
  "mode": "url",
  "startUrls": [
    { "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco" }
  ],
  "maxItems": 10
}
```

### Filters

Filters apply to both input modes:

| Field | Type | Description |
| --- | --- | --- |
| `minOverallRating` | number | Keep reviews with this overall rating or higher. |
| `minFoodRating` | number | Keep reviews with this food rating or higher. |
| `minServiceRating` | number | Keep reviews with this service rating or higher. |
| `minAmbienceRating` | number | Keep reviews with this ambience rating or higher. |
| `minValueRating` | number | Keep reviews with this value rating or higher. |
| `cuisines` | array | Keep restaurants matching at least one cuisine value. |
| `neighborhoods` | array | Keep restaurants matching at least one neighborhood value. |
| `fromDinedDate` | string | Keep reviews dined on or after this YYYY-MM-DD date. |
| `toDinedDate` | string | Keep reviews dined on or before this YYYY-MM-DD date. |
| `vipOnly` | boolean | Keep reviews from VIP reviewers only. |
| `requireReviewPhotos` | boolean | Keep reviews with diner photos only. |

### Output Controls

| Field | Type | Description |
| --- | --- | --- |
| `maxItems` | integer | Primary cap on total review rows saved across the run. |
| `maxReviewsPerRestaurant` | integer | Optional cap per restaurant. Leave empty to collect every review (bounded only by `maxItems`). |
| `maxRestaurants` | integer | Keyword search only. How many restaurants per keyword to collect reviews from. |
| `reviewSort` | string | Review order per restaurant: `highestRated` (default), `newest`, or `lowestRated`. |

### Resume and Recurring Updates

Two different features, both optional:

| Field | Type | Description |
| --- | --- | --- |
| `resumeFromRunId` | string | Continue ONE interrupted crawl. Paste a previous run ID or dataset ID and this run skips reviews it already collected there. |
| `incrementalMode` | boolean | For recurring monitoring of the SAME search or URLs (e.g. daily). Off by default. The first run returns every matching review as `NEW`. Later runs return only `NEW`, `UPDATED`, and `REAPPEARED` reviews by default. |
| `emitUnchanged` | boolean | Incremental mode only. Also return reviews unchanged since the last run, marked `UNCHANGED`. Off by default — turning it on returns and bills extra rows you already have. |
| `emitExpired` | boolean | Incremental mode only. Also return reviews from a previous run that are no longer found, marked `EXPIRED`. Only produced once a run fully scans every tracked restaurant/search — never when `maxItems` capped it, `resumeFromRunId` was used, or a keyword search may have been truncated by `maxRestaurants`. Off by default. |
| `stateKey` | string | Incremental mode only. Optional. Names a monitoring campaign so its state stays stable, or so two differently-configured runs deliberately share it. Leave empty to derive a key automatically from the search/URL and filter settings. |

Incremental mode adds `changeType` (`NEW`/`UPDATED`/`UNCHANGED`/`REAPPEARED`/`EXPIRED`), `changedFields`, `firstSeenAt`, and `lastSeenAt` to every row. State is tracked separately per search/URL and filter combination (mode, keywords, location/coordinates, startUrls, `reviewSort`, and every filter above) — changing `maxItems`, `maxReviewsPerRestaurant`, `maxRestaurants`, or the proxy configuration never starts a new baseline.

### MCP Connectors

Use `mcpConnectors` to send selected results into connected apps through Apify integrations. Supported connector servers include Notion, Linear, Airtable, and Apify. Set `notionParentPageUrl` when exporting to Notion. `maxNotifyListings` controls how many rows are sent to each connector and does not change the dataset.

### Notes

OpenTable can return fewer rows than `maxItems` when the selected keyword, area, URLs, or filters match fewer current reviews. For URL mode, paste OpenTable restaurant profile links and the actor returns the matching restaurant's review where available.

# Actor input Schema

## `mode` (type: `string`):

Search mode discovers restaurants around the selected coordinates and collects their full review history. URL mode takes pasted restaurant profile links and collects every review for each one.

## `keywords` (type: `array`):

Restaurant, cuisine, or dining terms to search. Examples: sushi, steakhouse, rooftop, vegan.

## `location` (type: `string`):

City, neighborhood, or address to search around (keyword search mode). For example: New York, Chelsea Manhattan, or 90210. The actor resolves this to map coordinates automatically.

## `latitude` (type: `number`):

Optional. Exact latitude to search around. When both latitude and longitude are set they override Location.

## `longitude` (type: `number`):

Optional. Exact longitude to search around. When both latitude and longitude are set they override Location.

## `startUrls` (type: `array`):

Paste OpenTable restaurant profile URLs such as https://www.opentable.com/r/yakiniku-shodai-san-francisco.

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

Maximum review rows to save across the whole run. The run can return fewer rows when OpenTable has fewer matching reviews.

## `maxReviewsPerRestaurant` (type: `integer`):

Optional cap on how many reviews to collect from each restaurant. Leave empty to collect every available review for each restaurant (bounded only by Max items).

## `maxRestaurants` (type: `integer`):

Keyword search only. How many restaurants around the coordinates to collect reviews from per keyword.

## `reviewSort` (type: `string`):

Order of reviews returned for each restaurant.

## `minOverallRating` (type: `number`):

Only keep reviews with at least this overall review rating.

## `minFoodRating` (type: `number`):

Only keep reviews with at least this food rating.

## `minServiceRating` (type: `number`):

Only keep reviews with at least this service rating.

## `minAmbienceRating` (type: `number`):

Only keep reviews with at least this ambience rating.

## `minValueRating` (type: `number`):

Only keep reviews with at least this value rating.

## `cuisines` (type: `array`):

Only keep restaurants whose primary or secondary cuisine contains at least one value.

## `neighborhoods` (type: `array`):

Only keep restaurants whose neighborhood contains at least one value.

## `fromDinedDate` (type: `string`):

Only keep reviews with a dined date on or after this YYYY-MM-DD date.

## `toDinedDate` (type: `string`):

Only keep reviews with a dined date on or before this YYYY-MM-DD date.

## `vipOnly` (type: `boolean`):

Only keep reviews where OpenTable marks the reviewer as VIP.

## `requireReviewPhotos` (type: `boolean`):

Only keep reviews that include diner photos.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large review pull without returning or charging for reviews already collected there. Use this after an interrupted run, or when continuing a large restaurant's review history in another run. For recurring monitoring of the same search or restaurants, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching reviews as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED reviews. Turn on "Emit unchanged" or "Emit expired" only when you also want those reviews returned (and billed). State is kept separately for each search/URL and filter setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return reviews that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return reviews that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked restaurants — not when Max items capped it, when Resume was used, or when a keyword search may have been truncated. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/URL and filter settings — different searches then never mix state with each other.

## `proxy` (type: `object`):

Connection routing for the run. The default is the right one for OpenTable and works on every Apify plan; only change it if your environment requires a specific route.

## `mcpConnectors` (type: `array`):

Optionally send scraped review rows into connected apps via Model Context Protocol connectors. Authorize a connector under Apify > Settings > API & Integrations, then select it here. Supported: Notion, Linear, Airtable, and Apify.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which review pages are created. Required for Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on how many review rows are written to each selected connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "sushi"
  ],
  "location": "New York",
  "startUrls": [
    {
      "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco"
    }
  ],
  "maxItems": 20,
  "maxRestaurants": 50,
  "reviewSort": "highestRated",
  "vipOnly": false,
  "requireReviewPhotos": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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 = {
    "mode": "search",
    "keywords": [
        "sushi"
    ],
    "location": "New York",
    "startUrls": [
        {
            "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco"
        }
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/opentable-reviews-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 = {
    "mode": "search",
    "keywords": ["sushi"],
    "location": "New York",
    "startUrls": [{ "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco" }],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
}

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

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

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

```

## CLI example

```bash
echo '{
  "mode": "search",
  "keywords": [
    "sushi"
  ],
  "location": "New York",
  "startUrls": [
    {
      "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco"
    }
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/opentable-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/opentable-reviews-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/Kb2o1nGdqfFWhkafQ/builds/dimniAgevRZ3eYFhg/openapi.json
