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
42
Total users
7
Monthly active users
3 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. When the restaurant page can be read, each row also carries the restaurant's address, phone, website, opening hours, dining style, dress code, parking, payment options, public transit, coordinates, photo count, review summary and rating distribution.
- 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 |
restaurantAddressLine1 / restaurantCity / restaurantState / restaurantPostalCode | "100 Example St" / "New York" / "NY" / "10007" |
restaurantPhone / restaurantWebsite / restaurantHours | "(212) 555-0100" / link / opening hours text |
restaurantDiningStyle / restaurantDressCode / restaurantParkingInfo | "Casual Elegant" / "Business Casual" / "Public Lot" |
sourceMode / sourceQuery | "search" / "sushi" |
scrapedAt | timestamp of when this row was scraped |
Restaurant detail columns are best effort. The address, phone, website, hours, dining style, dress code, description, parking, payment options, public transit, cross street, executive chef, amenities (restaurantFeatures, restaurantAdditionalDetails), popular dishes, delivery partners, photo count, coordinates, country, metro and neighborhood ids, review summary, rating basis, rating distribution, review categories, awards, and, on the restaurants whose page carries them, accessibility details, gender-neutral restroom, grocery store and call-for-delivery flags, currency code, counts of diners who recommend and do not recommend, affiliated-restaurants flag and network listing status (restaurant* columns, see the Restaurant details view) come from the restaurant's public OpenTable page. That page loads on only some connections, so the actor tries a few times per restaurant; when it still cannot be read those columns are null on that restaurant's rows, and the review rows are never held back or billed differently. After several restaurants in a row come back without details, the run stops trying for the rest of that run.
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}
Supported URL shapes (url mode):
| Shape | Example | Behaviour |
|---|---|---|
| Restaurant page | https://www.opentable.com/r/yakiniku-shodai-san-francisco | That restaurant's reviews. Matched by profile slug; an unmatched slug logs a warning and is skipped, never replaced by another restaurant. |
| Search page | https://www.opentable.com/s?term=sushi&metroId=4&covers=2 | Search by term; lat/lng parameters set the location; other parameters such as covers are ignored. A metroId=4 link is mapped to San Francisco; any other metroId alone cannot be mapped, so a warning is logged and the latitude/longitude input (default New York) is used. |
| City listings page | https://www.opentable.com/metro/san-francisco-restaurants | Restaurants around that city. The older /<city>-restaurant-listings form (for example /new-york-restaurant-listings) is still accepted, although OpenTable itself no longer serves that address. |
| Numeric restaurant id | https://www.opentable.com/restaurant/profile/4528 or ...restref/client?rid=4528 | Reviews for that restaurant id. Name, link, cuisine and the price band are filled only when the same run also meets that restaurant through a restaurant page, search or city source; the address, phone, hours and the other page details are read from the id link itself when the page loads. |
Localized hosts (for example opentable.co.uk) and any other link shape are skipped with a warning in the run log.
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.
Output views and run summary
The Output tab has four views over the same review rows: Overview (everything), Restaurant details (address, phone, website, hours and the other restaurant page columns), Ratings & restaurant (the five per-category ratings next to the restaurant's own rating, review count, cuisine and price band) and Changes (incremental) (change type, changed fields and first/last seen timestamps, filled when incremental mode is on). When a run covers more than one keyword or URL, a run summary is saved to the key-value store under OUTPUT with the rows and status per source.
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. What counts as a change. A review isUPDATEDwhen its own content changes (review text, any rating, dined or submitted date, restaurant reply, reviewer details, photo count, restaurant name, cuisine or location);changedFieldsnames exactly what moved. These never count, though they stay in every row:scrapedAt, the helpful counters (reviewHelpfulUp,reviewHelpfulDown,reviewHelpfulScore), the restaurant counters (restaurantRating,restaurantTotalReviewCount) the photo links (restaurantPhotoUrl,reviewerPhotoUrl,reviewPhotoUrls) and every restaurant detail column from the restaurant page (address, phone, hours and the rest), which can be empty on one run and filled on the next. UseignoreFieldsForChangesto exclude more. Reviews saved by an earlier version are compared with the same rules, so upgrading does not mark everything UPDATED. 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 | 3 example URLs (one per shape below) | OpenTable links to scrape (url mode). Behaviour change: omitting startUrls in url mode now runs the three example URLs instead of nothing. |
maxItems | integer | 20 | Maximum review rows saved across the whole run. The run can return fewer when OpenTable has fewer matching reviews. |
maxReviewsPerRestaurant | integer | 1000000 | Cap on reviews collected per restaurant. The default is effectively no per-restaurant limit, so collection is bounded only by Max items (default 20). |
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. |
fromReviewDate | string | (none) | Only keep reviews posted (submitted) on or after this YYYY-MM-DD date. |
toReviewDate | string | (none) | Only keep reviews posted (submitted) 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. |
ignoreFieldsForChanges | array | (none) | Incremental mode only. Changes to only these fields do not mark a review UPDATED; the fields stay in the output. Plain names or dotted paths for nested fields. Added on top of the built-in ignore list. |
proxy | object | Apify Proxy | Connection routing for the run. |
mcpConnectors | array | (none) | Connectors to send the saved review rows to after the run (Notion, Linear, Airtable, Apify). Best-effort; never changes the dataset. |
notionParentPageUrl | string | (none) | Notion page under which one page per review is created. Needed only for Notion export. |
maxNotifyListings | integer | 50 | Cap on rows sent to each connector per run. Does not affect the dataset. |
Send results into your apps (MCP connectors)
Optionally pipe the review rows saved in this run into apps you have authorized under Apify > Settings > API & Integrations. Select them in mcpConnectors; for Notion also set notionParentPageUrl, and cap the rows sent with maxNotifyListings (default 50). The export is best-effort: a failure logs a warning and never changes the dataset or billing.
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,"restaurantAddressLine1": "100 Example St","restaurantCity": "New York","restaurantState": "NY","restaurantPostalCode": "10007","restaurantCountryCode": "US","restaurantPhone": "(212) 555-0100","restaurantWebsite": "https://www.example.com/sample-sushi-house","restaurantDiningStyle": "Casual Elegant","restaurantDressCode": "Business Casual","restaurantPaymentOptions": ["AMEX", "Visa"],"sourceMode": "search","sourceQuery": "sushi","sourceUrl": null,"scrapedAt": "2026-06-15T09:12:00.000000+00:00"}
Plan Requirement
OpenTable guards its pages with a strict bot filter, and a plain datacenter connection is not reliable: it is often refused outright or served an empty challenge page instead of content. A paid Apify plan with proxy access is recommended, and the residential group under Connection gives the most headroom. The actor rotates connections automatically when one is refused.
- Free plan: Apify free plans include no proxy and no residential access. The run logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and continues on a backup connection, logging "Served over the backup connection (the default connection was unavailable)." when it works. Expect lower success rates than on a paid plan.
- Failure messages: if your account has no proxy access, the run stops with "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required". If residential is unavailable and the backup connection could not load the pages, it stops with "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested OpenTable pages." If no page could be loaded at all, it stops with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."
- Your own proxy: you can supply your own proxy URLs instead.
FAQ
How much does it cost?
You pay per review row returned; runs that find no matching reviews are not charged for that event. Review rates fall with your Apify plan tier. There is also an apify-actor-start event, billed per GB of memory at the start of each run. The Pricing tab always shows the current rates. Use Max items to cap the cost of any run. Max items defaults to 20 and is the hard ceiling for the whole run: Max reviews per restaurant can only lower it, so a run never saves (or bills) more review rows than Max items.
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 Scraper Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes... | 🛒 GrabFood Restaurant Scraper Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH... |
| 🧩 Resy Scraper Scrape Resy restaurants and live reservation availability by city or URL. Get bookable... | 🛒 Wolt.com From $0.8/1K. Scrape Wolt restaurants and full menus at the city scale. Extract 60+... |
| 🛒 DoorDash Scraper Extract structured doordash.com data at scale. Search by keyword or paste store URLs to... | 🏠 Apartments.com Scraper From $1.50/1K. Scrape Apartments.com rentals by city, ZIP, neighborhood, address or URL... |
💬 Support
- 🐞 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 contact@abotapi.com.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.