OpenTable Reviews Scraper – Restaurants, Ratings & Diner Data
Pricing
from $1.00 / 1,000 review scrapeds
OpenTable Reviews Scraper – Restaurants, Ratings & Diner Data
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.
Pricing
from $1.00 / 1,000 review scrapeds
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
1
Bookmarked
38
Total users
7
Monthly active users
13 days ago
Last modified
Categories
Share
OpenTable Reviews Scraper: Review Text, Ratings & Diner Data
OpenTable Reviews Scraper turns OpenTable restaurant pages into structured review data. Search by keyword and location, or paste restaurant profile links, and collect every review OpenTable has for each restaurant: full review text, the overall rating plus separate food, service, ambience, value and noise ratings, the dined date, diner details, restaurant replies and review photos, with the parent restaurant's own name, cuisine, neighborhood and rating carried on every row. Export straight to JSON, CSV or Excel, or read the dataset through the API.
Why This Scraper?
- Two ways to find restaurants. Search by keyword and location, or paste OpenTable restaurant links directly. No need to look up restaurant IDs by hand.
- Full review depth on every row. Review text plus five per-category ratings, food, service, ambience, value and noise, not just the single overall star score.
- Deep filtering. Minimum rating per category, cuisine, neighborhood, VIP reviewers only, photos only, and a dined date window, so you only pay for reviews that match.
- Restaurant context on every row. Cuisine, neighborhood, restaurant level rating and total review count are carried on each review, so no extra join is needed.
- Built to run at scale. Resume an interrupted pull without paying twice, and cap reviews per restaurant independently from the run wide total.
- Built for schedules. Incremental mode returns only new, updated and reappeared reviews on repeat runs, classified per review.
- Fails loudly, not quietly. A run that could not reach OpenTable stops with a clear message instead of returning a misleading empty dataset.
Use Cases
- Reputation management: track new reviews and rating trends for your own restaurants as they come in.
- Competitive research: compare food, service, ambience and value ratings, and cuisine mix, across nearby restaurants.
- Market research: build a dataset of diner sentiment and review volume across neighborhoods or cities.
- Lead generation: find highly rated restaurants and their OpenTable profile links for outreach or listing sites.
- AI and NLP: feed review text into sentiment analysis, review summarization, or an AI agent answering "where should I eat" questions.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
reviewId | "OT-1000001-2100000001-100000000001" |
reviewText | "Great spot for a birthday dinner, the staff were attentive all night." |
ratingOverall / ratingFood / ratingService | 5 / 5 / 4 |
ratingAmbience / ratingValue | 5 / 4 |
ratingNoise | "MODERATE" |
dinedDate / submittedDateTime | "2026-06-09" / "2026-06-10T16:02:39Z" |
reviewerName / reviewerLocation | "Jordan K." / "New York City" |
reviewerIsVip | false |
reviewerApprovedReviewCount | 3 |
reviewHelpfulUp / reviewHelpfulDown / reviewHelpfulScore | 2 / 0 / 2 |
restaurantReplyText | null |
reviewPhotoUrls / reviewPhotoCount | array of photo URLs / 1 |
restaurantName | "Sample Sushi House" |
restaurantUrl | OpenTable link to the restaurant profile |
restaurantPrimaryCuisine / restaurantCuisines | "Japanese" / ["Japanese", "Sushi"] |
restaurantNeighborhood | "Tribeca" |
restaurantRating / restaurantTotalReviewCount | 4.7 / 5397 |
sourceMode / sourceQuery | "search" / "sushi" |
scrapedAt | timestamp of when this row was scraped |
Every row also carries the restaurant's internal id, metro area and broader region, price band and currency symbol, and its profile photo; the review's own type flag and exact dined timestamp; the reviewer's initials, profile color and photo when OpenTable provides them; and the source URL when the row came from URL mode. Incremental mode (below) adds changeType, changedFields, firstSeenAt and lastSeenAt to every row.
How to Use
- Pick a mode: Keyword search (restaurants around a location) or Restaurant URLs (paste OpenTable profile links).
- For keyword search, add search terms and a location, or set exact latitude and longitude; for URL mode, paste one or more restaurant profile links.
- Optionally narrow results with the rating, cuisine, neighborhood, VIP, photo, and dined date filters, and set Max items to control run size and cost.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Keyword search:
{"mode": "search","keywords": ["sushi", "steakhouse"],"location": "New York","maxItems": 20}
Keyword search, filtered to highly rated reviews with photos:
{"mode": "search","keywords": ["rooftop"],"location": "Miami","minOverallRating": 4,"requireReviewPhotos": true,"maxItems": 50}
Restaurant URLs:
{"mode": "url","startUrls": [{ "url": "https://www.opentable.com/r/yakiniku-shodai-san-francisco" }],"maxReviewsPerRestaurant": 200}
Recurring monitoring of the same search:
{"mode": "search","keywords": ["steakhouse"],"location": "Chicago","incrementalMode": true,"stateKey": "chicago-steakhouses"}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/opentable-reviews-scraper").call(run_input={"mode": "search", "keywords": ["sushi"], "location": "New York"})for review in client.dataset(run["defaultDatasetId"]).iterate_items():print(review["restaurantName"], review["ratingOverall"], review["reviewText"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/opentable-reviews-scraper').call({ mode: 'search', keywords: ['sushi'], location: 'New York' });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every review it already collected there, so you don't pay twice. - Incremental mode (
incrementalMode) is for recurring monitoring of the same search or URLs. Each review is classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEARED, orEXPIRED(only after a run that scanned every tracked restaurant in full, and only withemitExpired).stateKeynames or shares the stored state; leave it empty to derive one automatically from the search or URLs and the active filters. Combining Resume with Incremental mode is only allowed before that state key has saved data; once it has, remove Resume or pick a different State key.
How Max restaurants and Max items interact (keyword search)
Keyword search first collects up to Max restaurants per keyword restaurants around your coordinates, then pulls reviews from each one until Max items is reached. A keyword that returns exactly the restaurant cap may have more restaurants available; raise Max restaurants per keyword to check. Max items is the hard stop on total review rows across every keyword and restaurant in the run.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (keyword and location discovery) or url (paste restaurant profile links). |
keywords | array | (none) | Restaurant, cuisine, or dining terms to search (search mode). |
location | string | New York | City, neighborhood, or address to search around (search mode); resolved to coordinates automatically. |
latitude | number | (none) | Optional. Exact latitude; together with Longitude it overrides Location. |
longitude | number | (none) | Optional. Exact longitude; together with Latitude it overrides Location. |
startUrls | array | (none) | OpenTable restaurant profile URLs to scrape (url mode). |
maxItems | integer | 20 | Maximum review rows saved across the whole run. The run can return fewer when OpenTable has fewer matching reviews. |
maxReviewsPerRestaurant | integer | (none) | Optional cap on reviews collected per restaurant. Leave empty to collect every available review for each restaurant, bounded only by Max items. |
maxRestaurants | integer | 50 | Keyword search only. How many restaurants around the coordinates to pull reviews from, per keyword. |
reviewSort | string | highestRated | Order reviews are returned in, per restaurant: highestRated, newest, or lowestRated. |
minOverallRating | number | (none) | Only keep reviews with at least this overall rating. |
minFoodRating | number | (none) | Only keep reviews with at least this food rating. |
minServiceRating | number | (none) | Only keep reviews with at least this service rating. |
minAmbienceRating | number | (none) | Only keep reviews with at least this ambience rating. |
minValueRating | number | (none) | Only keep reviews with at least this value rating. |
cuisines | array | (none) | Only keep restaurants whose primary or secondary cuisine matches at least one value. |
neighborhoods | array | (none) | Only keep restaurants whose neighborhood matches at least one value. |
fromDinedDate | string | (none) | Only keep reviews with a dined date on or after this YYYY-MM-DD date. |
toDinedDate | string | (none) | Only keep reviews with a dined date on or before this YYYY-MM-DD date. |
vipOnly | boolean | false | Only keep reviews where OpenTable marks the reviewer as VIP. |
requireReviewPhotos | boolean | false | Only keep reviews that include diner photos. |
resumeFromRunId | string | (none) | Continue one interrupted run: paste its run or dataset ID and already collected reviews are skipped and not re-charged. |
incrementalMode | boolean | false | Turn on for recurring monitoring of the same search or URLs. Classifies each review as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED against the previous run for this state. |
emitUnchanged | boolean | false | Also return, and bill, reviews unchanged since the last run (incremental mode only). |
emitExpired | boolean | false | Also return, and bill, reviews that were tracked before but are no longer found (incremental mode only). Only produced when a run fully scans every tracked restaurant or search. |
stateKey | string | (none) | Name or share an incremental mode monitoring state. Leave empty to derive one automatically from the search or URLs and the active filters. |
proxy | object | Apify Proxy | Connection routing for the run. |
mcpConnectors | array | (none) | Connector selection for exporting results to connected apps. Not active in this build; selecting a connector currently has no effect on the run or the dataset. |
notionParentPageUrl | string | (none) | Notion page under which exported rows would be created. Not active in this build (see mcpConnectors). |
maxNotifyListings | integer | 50 | Cap on rows sent to each connector per run. Not active in this build (see mcpConnectors); does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"reviewId": "OT-1000001-2100000001-100000000001","reviewText": "Great spot for a birthday dinner, the staff were attentive all night and the sushi was excellent.","ratingOverall": 5,"ratingFood": 5,"ratingService": 4,"ratingAmbience": 5,"ratingValue": 4,"ratingNoise": "MODERATE","reviewType": "OPENTABLEVERIFIED","submittedDateTime": "2026-06-10T16:02:39Z","dinedDateTime": "2026-06-09T18:45:00-04:00","dinedDate": "2026-06-09","reviewHelpfulUp": 2,"reviewHelpfulDown": 0,"reviewHelpfulScore": 2,"restaurantReplyText": null,"reviewerName": "Jordan K.","reviewerInitials": "JK","reviewerIsVip": false,"reviewerLocation": "New York City","reviewerApprovedReviewCount": 3,"reviewerProfileColor": "#3E7C59","reviewerPhotoUrl": null,"reviewPhotoUrls": ["https://www.opentable.com/example-assets/photos/sample-1.jpg"],"reviewPhotoCount": 1,"restaurantId": 1000001,"restaurantName": "Sample Sushi House","restaurantUrl": "https://www.opentable.com/r/sample-sushi-house-new-york","restaurantMetro": "New York City","restaurantMacro": "Northeast","restaurantNeighborhood": "Tribeca","restaurantPrimaryCuisine": "Japanese","restaurantCuisines": ["Japanese", "Sushi"],"restaurantPriceBand": 3,"restaurantCurrencySymbol": "$","restaurantRating": 4.7,"restaurantPhotoUrl": "https://www.opentable.com/example-assets/restaurants/sample-profile.jpg","restaurantTotalReviewCount": 5397,"sourceMode": "search","sourceQuery": "sushi","sourceUrl": null,"scrapedAt": "2026-06-15T09:12:00.000000+00:00"}
Plan Requirement
The default proxy setting works out of the box on every Apify plan, and the actor rotates connections automatically when one is refused. For larger or more frequent pulls, the residential proxy group gives more headroom; pick it under Connection in the input.
FAQ
How much does it cost?
You pay per review row returned; runs that find no matching reviews are not charged for that event. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape OpenTable reviews?
This actor collects only publicly available review data. You are responsible for how you use it: follow OpenTable's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Review text and ratings belong to the diners and restaurants that posted them.
Can I get only new reviews on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then classifies every review as new, updated, unchanged, reappeared, or expired against the previous run for that search, and unchanged reviews are not billed unless you turn that on.
Why do I get one row with "error": true instead of an empty dataset?
When a run finds zero matching reviews, whether from an unmatched search, an unresolved restaurant URL, or filters that exclude everything, the actor pushes a single row shaped like {"error": true, "code": "ZERO_RESULTS", "message": "..."} instead of leaving the dataset empty, so "nothing matched" is never confused with a run that silently produced nothing. Try broader keywords, nearby coordinates, fewer filters, or different restaurant URLs.
Why did my run fail instead of returning results?
A run fails, rather than returning an empty or ZERO_RESULTS dataset, when the actor could not establish a working connection to OpenTable at all, when it lost that connection partway through, could not recover, and had not saved any reviews yet (if some reviews were already saved, the run finishes with those partial results), when resumeFromRunId points at a run or dataset ID it cannot read, or when Incremental mode already has saved state for a search and resumeFromRunId is set at the same time. Each of these stops the run with a clear status message describing what to check or retry.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration, or from an AI agent through Apify's MCP server.
🔗 More scrapers you might like
Pair this actor with these related scrapers from the same team:
| 🛒 Zomato Restaurants & Reviews Scraper Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes... | 🧩 Resy Scraper Scrape Resy restaurants and live reservation availability by city or URL. Get bookable... |
| 🛒 GrabFood Restaurant Scraper Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH... | 🏨 Caterer.com Scraper Scrape UK hospitality jobs from Caterer.com, including chef, hotel, restaurant, bar, and... |
| 🍷 Wolt.com From $0.8/1K. Scrape Wolt restaurants and full menus at the city scale. Extract 60+... | ✈️ Naver Map Scraper From $1/1K. Scrape Naver Map places by keyword or URL: names, categories, ratings... |
💬 Support & custom scrapers
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.