OpenTable Restaurant Scraper – Reviews, Ratings & Phone Numbers
Pricing
from $0.50 / 1,000 restaurant records
OpenTable Restaurant Scraper – Reviews, Ratings & Phone Numbers
Scrape OpenTable restaurants in any city, by area, address or lat-lng, with an optional cuisine keyword. Each row: name, profile URL, cuisines, price band, rating, review count, neighbourhood, address, coordinates. Opt in for phone, website, hours, menu, popular dishes and diner reviews.
Pricing
from $0.50 / 1,000 restaurant records
Rating
0.0
(0)
Developer
R.L.
Maintained by CommunityActor stats
1
Bookmarked
13
Total users
8
Monthly active users
3 days ago
Last modified
Categories
Share
OpenTable Restaurant Scraper — Reviews, Ratings & Phone Numbers
Scrape restaurants from OpenTable (opentable.com) in any city OpenTable covers. Search by city, area or address — or by exact coordinates — with an optional cuisine keyword, and export clean records: name, profile URL, cuisines, price band, star rating, review count, neighbourhood, full address and coordinates. Switch on the optional deep scrape for phone numbers, website, opening hours, full menus (every section, item, description and price), popular dishes, reservation availability (open tables by date and party size) and full diner reviews. Or skip the search: give it the restaurants you care about by URL, ID or name and pick a single-purpose mode, so a scheduled run tracking menu changes fetches nothing else. It reads OpenTable's own mobile API directly, so it's fast and complete — no flaky HTML parsing, no headless browser. Export to JSON, CSV, Excel, or an API.
Why use this OpenTable scraper?
- No code required — set your input, click Start, download your data.
- Fast & accurate — reads OpenTable's own mobile API and returns structured JSON, not scraped HTML.
- Rich, structured data — 25 fields from search alone, 41 with the deep scrape on.
- Full pagination — 30 restaurants per page, collected up to your
maxItemscap, with cross-page duplicates removed. - Transparent pay-per-result pricing — pay only for the rows you get.
- Export anywhere — JSON, CSV, Excel, XML, or pull it live via the Apify API.
What can you do with OpenTable data?
- Restaurant market & competitor research — compare cuisines, price bands and ratings across a city or a neighbourhood.
- Demand & popularity tracking —
resosInLastDay(bookings in the last 24 hours) andfirstNextAvailableTimeshow how booked a restaurant is, sampled over time. - Review mining — per-category diner scores for food, service, ambience, value and noise, with the review text.
- Lead generation — restaurant phone numbers, websites and addresses for a whole metro.
- Data science — clean, flat datasets for pricing, rating and cuisine analysis.
What data does the OpenTable scraper extract?
Each row is one restaurant. From search alone you get: id, name, url, cuisines, priceBand, priceBandId, rating, reviewCount, neighborhood, neighborhoodId, metroId, address, city, state, postalCode, country, latitude, longitude, images, photosCount, description, features, firstNextAvailableTime, reviews, reviewsFetched. The deep-scrape options add 16 more. See the full Data table below.
How do I scrape OpenTable restaurants? (3 steps)
- Set your search area — a
locationstring (city, area or address), orlat+lon. Addqfor a cuisine or keyword. - Pick options —
maxItemsto cap the run, and the two deep-scrape toggles if you need phones or reviews. - Run & export — click Start, then download the dataset as JSON/CSV/Excel or fetch it via the API.
Can I get phone numbers, menus and diner reviews?
Yes — all four deep-scrape options are off by default and each is billed as its own pay-per-event surcharge, so a plain search run stays cheap. Current prices are on the Pricing tab.
| Option | Adds |
|---|---|
| Include restaurant details | Phone number (raw digits + formatted), website, opening hours, dining style, dress code, parking, payment options, menu link, popular dishes, max party size, booking window, currency, takeout flag, gender-neutral restroom flag, bookings in the last 24h |
| Include full menu | Every OpenTable-hosted menu: sections, dishes, prices, descriptions and dish photos, with the menu provider's attribution preserved |
| Include diner reviews | Review text, author and their home metro, posted/reservation dates, VIP and loyalty tier, and per-category ratings — overall, food, service, ambience, value, noise |
| Include availability | Bookable time slots for a date and party size, or OpenTable's own reason there are none |
Reviews are paged automatically to reviewsPerRestaurant and can be ordered by
newest, highest rated, lowest rated or recommended. Enrichment runs in
parallel (detailConcurrency, default 8) — 30 restaurants with both options take roughly
25 seconds rather than several minutes.
Details need a US proxy
OpenTable geo-gates the restaurant-profile route. It does not return an error from a non-US IP — it simply hangs until the request times out. Measured on the Apify platform, same input, 5 restaurants:
| Proxy | Phones returned | Run time |
|---|---|---|
| Apify Proxy (default datacenter) | 3 / 5 | 133 s |
| Apify Proxy — RESIDENTIAL, country US | 5 / 5 | 24 s |
Residential US is therefore the default — details work out of the box, no configuration needed. It is not a speed penalty: a 100-record search-only run took 18.5 s on residential vs 16.1 s on datacenter, and used only 0.00012 GB of residential transfer (~860k records per 10 GB).
Search and reviews are not geo-gated, so a datacenter group works for search-only jobs if you prefer one; keep residential whenever details, menus or availability are on, because those routes are US-gated and hang rather than erroring from elsewhere. Either way the proxy transfer is not billed to you on top of the event charges.
Modes
One run does one job. mode defaults to search, so anything that worked before still works unchanged.
| mode | needs | returns |
|---|---|---|
search (default) | location or lat+lon | restaurants by city, area or coordinates — plus whichever include* extras you switch on |
restaurants | restaurantUrls / restaurantNames | phone, hours, website, popular dishes |
menus | restaurantUrls / restaurantNames | every menu, section, item and price |
reviews | restaurantUrls / restaurantNames | diner reviews with category ratings |
availability | restaurantUrls / restaurantNames | open tables by date and party size |
An enrichment mode fetches only its own data, so a monitoring run that wants menus does not pay for reviews — it sets its own include* flag and clears the other three. It ignores the search inputs (location, q, sort, the filters) and logs which ones it dropped, rather than leaving you to wonder why q did nothing.
In search mode the include* flags are additive and behave exactly as they always have.
{ "mode": "menus","restaurantUrls": ["1230112", "https://www.opentable.com/restaurant/profile/42265"] }
{ "mode": "availability","restaurantNames": ["Brooklyn Chop House Times Square, New York"],"daysAhead": 7, "time": "19:00", "partySize": 2 }
What can I filter on?
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | search | What the run does: search, or restaurants / menus / reviews / availability to fetch one kind of data for restaurants you list. See Modes. |
restaurantUrls | array | – | Restaurants to fetch, for every mode except search: a numeric ID (42265), a profile URL, or any opentable.com restaurant URL — slug URLs are resolved automatically. |
restaurantNames | array | – | Restaurants by name, one per line as Name, City — e.g. Brooklyn Chop House Times Square, New York. An ambiguous name is reported with the near-misses rather than guessed. |
location | string | New York, NY | City, area, or address to search around (e.g. "New York, NY", "San Francisco"). Resolved to coordinates through OpenTable's own autocomplete at run time. |
q | string | – | Optional cuisine or keyword (e.g. "sushi", "Indian"). |
lat | number | – | Optional latitude override; skips the location lookup. |
lon | number | – | Optional longitude override; skips the location lookup. |
maxItems | integer | 100 | Maximum number of restaurants to return (0 = all pages). |
includeDetails | boolean | false | Fetch each restaurant's full profile: phone number, website, opening hours, dining style, dress code, parking, payment options, menu link and popular dishes. One extra request per restaurant, billed as its own event. Requires a US proxy — this route is geo-gated and simply hangs from non-US IPs. |
includeReviews | boolean | false | Fetch diner reviews per restaurant — text, author, dates and the per-category ratings (food, service, ambience, value, noise). Billed per review returned. |
includeMenu | boolean | false | Fetch the complete OpenTable-hosted menu per restaurant: every menu, section, item, description, price and size variation, plus the menu's currency and data provider. One extra request per restaurant. Separate from the menuUrl field, which links to the menu on the restaurant's own website — that one comes from the profile, so it needs includeDetails. A restaurant can have either source, both or neither; enable both options to capture both. Not currently billed as a separate event. |
includeAvailability | boolean | false | Fetch bookable reservation slots per restaurant: time, seating type, dining area, loyalty points, and whether a card is required (hold vs deposit). One extra request per restaurant. Needs a US proxy — like the profile route, this hangs from non-US IPs rather than erroring. |
date | string | tomorrow | Reservation date, YYYY-MM-DD. |
daysAhead | integer | – | Check this many days from the run date (0 today, 7 a week out). Use instead of date for scheduled monitoring — a relative target stays correct every run. Setting both is rejected. |
time | string | 19:00 | Reservation time, HH:MM 24-hour. |
partySize | integer | 2 | Number of diners. |
cuisines | array | – | Filter by cuisine, applied upstream by OpenTable. Display names (Italian, Steakhouse) are resolved to OpenTable's ids automatically; raw UUIDs also work. An unknown name is reported and ignored. |
neighborhoods | array | – | Filter by neighbourhood (Chelsea, Astoria). Names resolved to ids (n_hood:195, c_hood:292), which you may pass directly. |
priceBands | array | – | OpenTable price bands: 2 = $30 and under, 3 = $31 to $50, 4 = $50 and over. (1 returns nothing.) |
awardTags | array | – | Award holders, e.g. MICHELIN, MICHELIN;1 Star, 2025. |
contextualPrompts | array | – | Occasion prompts, e.g. ROMANTIC, BRUNCH, FUN. |
wheelchairAccessible | boolean | false | Only restaurants flagged wheelchair accessible. |
searchRadiusMiles | integer | – | Radius around the location. Widens as well as narrows — a New York search is 637 at the default, 985 at 2 miles, 63 at half a mile. Ignored when q is set, because a term search runs nationwide. |
sort | string | – | best / rating / distance / newestArrivals. |
reviewsPerRestaurant | integer | 20 | How many reviews to fetch for each restaurant (paged automatically). Only used when reviews are enabled. |
reviewsSort | enum | newestReview | Which reviews come first — one of newestReview, highestRating, lowestRating, recommended. |
detailConcurrency | integer | 8 | How many restaurants to enrich in parallel (1-20). Raise for speed, lower to be gentler on the API and your proxy. |
proxyConfiguration | object | Apify Proxy, RESIDENTIAL, US | Route requests through Apify Proxy. Search works from any egress, but restaurant details are geo-gated to US IPs, so the default is RESIDENTIAL with country US. |
Which cities and countries can I search?
Any place OpenTable's own autocomplete recognises — US metros and international markets alike. Verified live against this Actor, 12-30 restaurants each:
| Location input | Returns restaurants in | country |
|---|---|---|
New York, NY | Manhattan and the outer boroughs | United States |
Chicago, IL | Chicago | United States |
Los Angeles, CA | Los Angeles | United States |
Miami, FL | Miami | United States |
London, United Kingdom | London | United Kingdom |
Paris, France | Paris | France |
Berlin, Germany | Berlin | Germany / Deutschland |
Tokyo, Japan | Chuo-ku, Minato-ku, Shibuya-ku | Japan / 日本 |
Sydney, Australia | Sydney, Surry Hills, Barangaroo, Drummoyne | Australia |
Toronto, Canada | Toronto, North York, Don Mills | Canada |
Mexico City, Mexico | Ciudad de México | Mexico / México |
Dubai, United Arab Emirates | Dubai, Business Bay | United Arab Emirates |
Both "Boise, Idaho" and "Boise, ID" forms resolve, as do plain city names and street
addresses. Note that country (and sometimes city) can come back in the local language —
Tokyo rows carry either Japan or 日本 — so normalise it if you group by country.
Example input
{"location": "New York, NY","maxItems": 100,"includeDetails": false,"includeReviews": false,"reviewsPerRestaurant": 20,"reviewsSort": "newestReview","detailConcurrency": 8,"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"],"apifyProxyCountry": "US"}}
What does one row look like?
A row from a run with both deep-scrape options on (the detail and review fields are absent when they are off):
{"id": "108541","name": "Sofi Restaurant","url": "https://www.opentable.com/restaurant/profile/108541","cuisines": ["Italian"],"priceBand": "$31 to $50","priceBandId": "3","rating": 4.7,"reviewCount": 159,"neighborhood": "South Loop","neighborhoodId": "82","metroId": "3","address": "616 South Dearborn","city": "Chicago","state": "IL","postalCode": "60605","country": "United States","latitude": 41.8737957,"longitude": -87.629455,"images": ["https://resizer.otstatic.com/v4/photos/67602167-3?width=105&height=105","https://resizer.otstatic.com/v4/photos/67602167-3?width=1280&height=1280","…+6 more sizes of the same photo"],"photosCount": 0,"description": "Born in Milan to a renowned restaurant family, Chef/Owner Maurizio Michi has always had a passion for cooking.\nThis Northern Italian Restaurant features clas…","features": ["RESERVATION","ONLINE_WAITLIST","…+2 more"],"firstNextAvailableTime": "2026-08-22T16:30","phone": "3127535502","phoneFormatted": "(312) 753-5502","website": "http://www.sofichicago.com/","menuUrl": null,"hours": "Mon–Thu, Sun 4:00 pm–10:00 pm\nFri, Sat 4:00 pm–10:30 pm","diningStyle": "Casual Elegant","dressCode": "Business Casual","parkingInfo": "Street Parking","paymentOptions": ["AMEX","Discover","…+2 more"],"popularDishes": [{"name": "Gnocchi Genovese","description": "Potato dumplings, basil pesto, green beans","photoUrl": "https://resizer.otstatic.com/v4/photos/…","reviewMentions": 11,"photoMentions": 3},"…+7 more"],"popularDishNames": ["Gnocchi Genovese", "Tagliatelle Bolognese", "…+6 more"],"maxPartySize": 8,"maxAdvanceDays": 90,"isTakeoutEnabled": true,"hasGenderNeutralRestroom": true,"currencyCode": "USD","resosInLastDay": 24,"reviewsFetched": 3,"reviews": [{"…": "16 fields"},{"…": "16 fields"},"…+1 more"]}
How much of the city did this run cover?
A search run reports its own coverage, so a short dataset is never ambiguous. The run's status message says it outright — "Returned 50 of 440 matching restaurants (11%). Raise maxItems to collect the rest." — and a SEARCH_COVERAGE record lands in the key-value store:
{ "returned": 50, "totalMatching": 440, "totalAvailable": 440,"complete": false, "truncatedBy": "maxItems" }
truncatedBy distinguishes a maxItems cut from a spend-cap cut, so you can tell "that is every Italian restaurant in Brooklyn" from "that is the first 50 of 440".
Data table
| Field | Type | From | Description |
|---|---|---|---|
id | text | search | OpenTable restaurant ID |
name | text | search | Name |
url | link | search | Profile URL |
cuisines | array | search | Cuisine labels |
priceBand | text | search | Price band label, localised (e.g. $31 to $50, £26 to £40) |
priceBandId | text | search | Price band number — the market-independent one |
rating | number | search | Overall star rating, 1-5; null with no reviews |
reviewCount | number | search | How many diner reviews OpenTable holds |
neighborhood | text | search | Neighbourhood |
neighborhoodId | text | search | OpenTable neighbourhood ID |
metroId | text | search | OpenTable metro (market) ID |
address | text | search | Street address |
city | text | search | City |
state | text | search | State / region |
postalCode | text | search | Postal code |
country | text | search | Country, sometimes in the local language |
latitude | number | search | Latitude |
longitude | number | search | Longitude |
images | array | search | Profile photo URLs — one photo in 8 render sizes, 105px to 1280px |
photosCount | number | search | Raw API counter; in practice 0 — count images instead |
description | text | search | Blurb (longer version when details are on) |
features | array | search | Booking features, e.g. RESERVATION, ONLINE_WAITLIST, DIRECT_MESSAGING, DINER_AI, TAKEOUT_TIPS_ENABLED |
firstNextAvailableTime | text | search | Next bookable slot, local time |
phone | text | details | Phone, digits only |
phoneFormatted | text | details | Phone, formatted |
website | link | details | Website |
menuUrl | link | details | External menu link |
hours | text | details | Opening hours |
diningStyle | text | details | e.g. Casual Dining, Casual Elegant |
dressCode | text | details | e.g. Casual Dress, Business Casual, Resort Casual |
parkingInfo | text | details | e.g. Valet, Street Parking, None |
paymentOptions | array | details | e.g. AMEX, Visa, Mastercard, Discover, Diners Club |
popularDishes | array | details | One object per dish: name, description, photoUrl, reviewMentions, photoMentions |
popularDishNames | array | details | The same dish names as a flat list, for simpler exports |
menuPhotosCount | number | details | Diner-uploaded menu photos on the profile |
maxPartySize | number | details | Largest party bookable online |
maxAdvanceDays | number | details | How far ahead bookings open |
isTakeoutEnabled | boolean | details | Takeout available |
hasGenderNeutralRestroom | boolean | details | Gender-neutral restroom |
currencyCode | text | details | e.g. USD |
resosInLastDay | number | details | Reservations made in the last 24 hours |
menus | array | menu | The full OpenTable-hosted menu: menu → sections → items → variations, with prices, currency and the data provider |
menuCount | number | menu | How many menus this restaurant has |
menuItemCount | number | menu | Total items across all its menus |
availabilitySlots | object | availability | Bookable slots plus minPartySize, maxPartySize, maxDaysInAdvance and noTimesReasons |
reviewsFetched | number | reviews | How many reviews this row carries |
reviews | array | reviews | The reviews themselves — 16 fields each, below |
What is in each review?
reviews[] carries these 16 fields: id, author, authorMetro (the reviewer's home
market), postedDate, reservationDate, text, overallRating, food, service,
ambience, value, noise, positiveFeedback, negativeFeedback, dinerIsVIP and
loyaltyTier.
Which cuisines does OpenTable use?
cuisines carries OpenTable's own labels, and a restaurant can have several. An eight-city
sample (New York, Chicago, Los Angeles, Miami, London, Paris, Tokyo, Sydney and Toronto)
returned 68 distinct labels. The most common:
Italian · American · Japanese · French · Steakhouse · Mediterranean · Contemporary American · Korean · Steak · Chinese · Cocktail Bar · Seafood · Sushi · Mexican · Greek · Modern Australian · Spanish · Bar / Lounge / Bottle Service · Café · European · Tapas / Small Plates · Australian · Contemporary Italian · Thai · Argentinean · Bistro · Brazilian · British · Comfort Food · Fusion / Eclectic · Lebanese · Middle Eastern · Pizzeria · Pizza Bar · Pub · Gastro Pub · Southeast Asian · Vietnamese · Teppanyaki · Yakiniku · Izakaya · Sichuan · Persian · Latin American · Caribbean · Cuban · Ethiopian · German · Irish · Southern · Soul food · Vegetarian / Vegan
Set q to any of these, or to a dish (ramen) or a restaurant name (Nobu), to narrow a
search. Restaurants OpenTable has not classified come back as Unspecified. Note that a
brand-name q can pull in matches outside your location — searching Nobu around New
York also returns Nobu Miami and Nobu Las Vegas — so filter on city or metroId if you
need the results strictly local.
What do these OpenTable terms mean?
- Price band (
priceBand/priceBandId) — OpenTable's per-person spend bracket. The label is localised to the market: band3is$31 to $50in the US,£26 to £40in London,€31 to €50in Paris,¥5,001 to ¥10,000in Tokyo andCAN$31 to CAN$50in Toronto. Compare across countries onpriceBandId, never on the label. - Metro (
metroId) — OpenTable's market ID, the grouping the app calls a "metro". Every restaurant in one search area shares it, so it is a stable key for "this city":3is Chicago,6Los Angeles,8New York,17Miami,72London,74Toronto,201Tokyo,255Sydney,3408Paris. firstNextAvailableTime— the earliest table OpenTable can seat, as local time at the restaurant. It is a snapshot at the moment of the run, not a schedule.resosInLastDay— reservations booked through OpenTable in the previous 24 hours. The closest thing the API gives to a popularity signal.- Noise (
noiseon a review) — diners rate how loud the room was on the same 1-5 scale as food, service, ambience and value; a low number means quiet. features— which booking mechanics the restaurant has switched on, not amenities:RESERVATION(bookable),ONLINE_WAITLIST,DIRECT_MESSAGING,DINER_AI,TAKEOUT_TIPS_ENABLED.- Dining style vs dress code —
diningStyleis the format (e.g. Casual Dining, Casual Elegant);dressCodeis what to wear (e.g. Casual Dress, Business Casual, Resort Casual). maxAdvanceDays— the booking window: how many days ahead the restaurant opens tables. 14, 30, 42 and 90 are all common.
Pricing — pay per result
This actor uses pay-per-event pricing: you pay for the data it actually returns, and nothing for a slice that came back empty.
See the Pricing tab for the current per-event prices. They vary by your Apify plan, so the tab shows what you would pay — which a table here could not.
What gets billed:
- the restaurant record — one charge per restaurant returned by a search
- the profile — one charge per restaurant whose own profile page was read
- the menus — one charge per restaurant that actually has an OpenTable-hosted menu
- the reviews — charged per review returned, so you pay for the number you asked for
- the availability check — one charge per completed check for a restaurant, date and party size
Until 2026-10-26, menus and availability are returned at no charge and reviews are billed once per restaurant rather than per review. The Pricing tab shows both the current prices and the scheduled change.
A plain search bills only the first of those. Each deep-scrape option is off by default and costs one extra request per restaurant, so you opt in to what you need.
Use the OpenTable scraper via API
Run it programmatically with the Apify API:
curl -X POST "https://api.apify.com/v2/acts/rl1987~opentable-api-scraper/runs?token=YOUR_TOKEN" \-H 'Content-Type: application/json' \-d '{"location": "New York, NY","maxItems": 100,"includeDetails": false,"includeReviews": false,"reviewsPerRestaurant": 20,"reviewsSort": "newestReview","detailConcurrency": 8,"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"],"apifyProxyCountry": "US"}}'
Or with the Apify CLI: apify call rl1987/opentable-api-scraper --input-file=input.json -o.
FAQ
Do I need an OpenTable account or API key? No. The Actor talks to OpenTable's own mobile API as an anonymous client — there is nothing to register. An Apify account is all you need.
Can I get restaurant phone numbers? Yes, with Include restaurant details on: phone (digits) and phoneFormatted. That route is geo-gated, which is why the proxy defaults to Apify Residential with country US — leave it as it is and phones work out of the box.
Does it cover restaurants outside the US? Yes. London, Paris, Berlin, Tokyo, Sydney, Toronto, Mexico City and Dubai were all verified live. Price-band labels come back in the local currency, so compare markets on priceBandId.
How many restaurants can one run return, and how do I cap the cost? The API pages 30 at a time and the Actor follows pagination until OpenTable's own total is reached. maxItems caps it (default 100, 0 = everything), and because billing is per row, that cap is also your budget cap.
Can I schedule it to track ratings or availability over time? Yes — use Apify Schedules. rating, reviewCount, resosInLastDay and firstNextAvailableTime are all snapshots at run time, so a daily run gives you the series.
What happens if my search returns nothing, or OpenTable blocks the run? Neither is treated as a crash. An empty search, an unusable input and a search OpenTable itself refuses each finish with a status message explaining what to change. If OpenTable blocks every exit IP after retries, the run says so plainly in its status rather than handing you a silent empty dataset, and if a later page fails the Actor keeps the records it already collected.
Are the images the full photo gallery? No. images is the restaurant's profile photo in eight render sizes (105px up to 1280px), not eight different photos — and photosCount from the API is 0 in practice, so ignore it.
Is scraping OpenTable legal? You are responsible for how you use the data. Collect only public restaurant data and comply with OpenTable's Terms and applicable law (e.g. GDPR/CCPA where reviews and reviewer names count as personal data).
How it works
The Actor impersonates OpenTable's official app and calls its mobile API the same way the app does: a token request, the app's autocomplete to turn your location into the coordinates OpenTable itself would search, then the paged personalizer search — plus the profile and reviews endpoints when you ask for them. Each response is normalised into the flat record above. No browser, no HTML scraping.
Good to know
- Collect only public restaurant data and respect OpenTable's Terms of Service and robots policy.
- Reviews carry reviewer names and home metros. For personal data, comply with GDPR/CCPA and applicable law — you are the data controller.
- Fields can be
nullwhen OpenTable doesn't expose them for a given restaurant —ratingisnullbefore the first review, andmenuUrlis often empty.
Found this useful?
Give the OpenTable Restaurant Scraper a star on Apify and check out my other apify-*-api-scraper actors.