OpenTable Scraper — Reviews, Restaurants & Availability avatar

OpenTable Scraper — Reviews, Restaurants & Availability

Pricing

from $4.85 / 1,000 restaurants

Go to Apify Store
OpenTable Scraper — Reviews, Restaurants & Availability

OpenTable Scraper — Reviews, Restaurants & Availability

Scrape OpenTable restaurants AND individual reviews (reviewer, overall + food/service/ambience sub-ratings, text, dined date, helpful votes), plus availability and listings. By city, cuisine, or URL. Two PPE events: per restaurant + per review.

Pricing

from $4.85 / 1,000 restaurants

Rating

0.0

(0)

Developer

Vitalii Bondarev

Vitalii Bondarev

Maintained by Community

Actor stats

0

Bookmarked

14

Total users

4

Monthly active users

a day ago

Last modified

Share

OpenTable API Scraper — Reviews, Restaurants & Availability

Extract structured restaurant data and individual diner reviews from OpenTable, plus availability and listings. Works in two modes:

  1. City search — give a city slug (san-francisco, new-york, chicago) and optional cuisine keyword; the actor returns up to N restaurants with full profile data.
  2. URL list — supply a list of OpenTable restaurant profile URLs for targeted scraping.

Why this scraper is best-in-class

Most OpenTable scrapers give you either reviews or listings. This one gives you all three in one run:

  • Individual reviews — reviewer, overall rating, food / service / ambience / value sub-ratings, noise level, full text, dined date vs review date, and helpful-vote counts. (Most competitors only return the review text and a single overall star — we return the full breakdown.)
  • Restaurant listings — name, address, phone, cuisine, price band, geo, hours, aggregate rating + review count.
  • Availability & reservations — reservation acceptance + slots.

Reviews are extracted from the same page as the restaurant record, so they add no extra requests and don't slow the run down.


Output shape

Every run produces two kinds of rows, distinguished by record_type, and accessible via two dataset views (Restaurants and Reviews):

  • record_type: "restaurant" — one row per restaurant. Also carries a nested reviews[] array and a reviews_extracted count, so you can consume reviews either as flat rows or nested.
  • record_type: "review" — one flat row per individual review, linked to its parent via restaurant_id + restaurant_name.

What you get per restaurant

FieldExample
nameThirsty Bear
slugthirsty-bear-san-francisco
urlhttps://www.opentable.com/r/thirsty-bear-san-francisco
descriptionFull restaurant description text
cuisine["Spanish", "Tapas / Small Plates", "Brewery"]
cuisine_strSpanish, Tapas / Small Plates, Brewery
price_range$30 and under
rating3.9
review_count615
accepts_reservationsfalse
street_address661 Howard St.
citySan Francisco
stateCA
zip_code94105
countryUnited States
address661 Howard St., San Francisco, CA, 94105
latitude37.78565
longitude-122.399734
phone+1 415-974-0905
opening_hours["Monday 11:30am–10:00pm", ...]
menu_urlhttps://www.opentable.com/...
reviews_extracted10
reviewsnested array of review objects (see below)
restaurant_id2
scraped_at2026-06-07T12:00:00Z

What you get per review

Each review is emitted both as a nested item inside the restaurant record and as its own flat row (record_type: "review"):

FieldExample
restaurant_id2
restaurant_nameThirsty Bear
restaurant_slugthirsty-bear-san-francisco
review_idOT-2-1804022929-150018263055
reviewer_nameJoeF
reviewer_initialsJF
reviewer_metroSan Francisco
reviewer_review_count12
overall_rating5
food_rating5
service_rating5
ambience_rating4
value_rating5
noise_levelMODERATE
review_textGreat craft beers. Tapas and paella were very good.
dined_date2018-05-25
review_date2018-05-27
helpful_count0
helpful_up / helpful_down0 / 0
has_photosfalse
party_sizenull*
occasion""*
scraped_at2026-06-07T12:00:00Z

* party_size and occasion are not exposed in OpenTable's public review payload; the columns are kept for a stable schema and returned empty.

Coverage note: OpenTable embeds roughly the 10 most-recent reviews on each restaurant page. The scraper reads those in the same request used for the restaurant record (no extra cost). Set maxReviewsPerRestaurant to cap how many are returned (default 20 = all embedded). Set includeReviews: false to skip reviews entirely.


Inputs

City search mode (most common)

{
"city": "san-francisco",
"cuisine": "italian",
"maxItems": 100,
"includeReviews": true,
"maxReviewsPerRestaurant": 20
}
  • city — City slug matching OpenTable's URL format. Examples: san-francisco, new-york, chicago, los-angeles, miami, seattle, boston, denver, austin.
  • cuisine — Optional filter keyword. Examples: italian, sushi, french, mexican, thai, indian, american.
  • maxItems — Maximum number of restaurant records to return (default 10, max 5000).
  • includeReviews — Also extract individual reviews (default true).
  • maxReviewsPerRestaurant — Cap on reviews per restaurant (default 20; OpenTable embeds ~10 per page).

URL list mode

{
"restaurantUrls": [
"https://www.opentable.com/r/thirsty-bear-san-francisco",
"https://www.opentable.com/r/foreign-cinema-san-francisco"
]
}

Supply any list of OpenTable /r/{slug} URLs. The actor scrapes each one and returns the full record.


How it works

OpenTable uses a behavioral JavaScript challenge. This actor uses a managed proxy layer to reliably access OpenTable pages without any setup on your end.

Restaurant discovery (city search mode): OpenTable publishes a public sitemap index containing ~90,000 restaurant URLs. Each URL encodes the city name in the slug (e.g., thirsty-bear-san-francisco). The actor filters this list by your city input, then fetches each matching profile page.

Data source: Every OpenTable restaurant profile page embeds (a) a JSON-LD @type=Restaurant block and (b) an inline application-state blob containing the most-recent reviews with full sub-ratings. Both are maintained by OpenTable, parsed from the same page in a single request — the most reliable and stable extraction method, with no fragile CSS class selectors.


Pricing

Two pay-per-event prices:

  • Per restaurant record delivered.
  • Per review record delivered.

Reviews come from the same page as the restaurant (no extra request), so review pricing reflects the data value, not extra compute. No per-query or setup fees.

You do not need to supply your own proxy or API key. Access is fully handled by the actor.


Usage examples

Lead generation for restaurant industry vendors

Scrape all Italian restaurants in NYC to build a targeted outreach list for a restaurant POS system or food delivery service:

{
"city": "new-york",
"cuisine": "italian",
"maxItems": 500
}

Restaurant data aggregation

Build a dining guide for a specific city by scraping all available restaurants:

{
"city": "chicago",
"maxItems": 1000
}

Monitoring specific restaurants

Track data for a known set of competitor restaurants:

{
"restaurantUrls": [
"https://www.opentable.com/r/nobu-new-york",
"https://www.opentable.com/r/eleven-madison-park-new-york",
"https://www.opentable.com/r/per-se-new-york"
]
}

Supported cities

Any city where OpenTable has restaurant listings. The city input must match the format used in OpenTable slugs. Common formats:

  • san-francisco
  • new-york
  • los-angeles
  • chicago
  • miami
  • seattle
  • boston
  • denver
  • austin
  • las-vegas
  • washington-dc (use washington)
  • portland
  • atlanta
  • philadelphia

Technical notes

  • Access: handled via managed-access (baked in, no buyer setup needed).
  • Data stability: JSON-LD @type=Restaurant + inline application-state reviews — maintained by OpenTable, much more stable than scraped HTML. Reviews use the rich state blob (sub-ratings, dined date, helpfulness) with a JSON-LD review fallback.
  • Reviews: ~10 most-recent reviews per restaurant, extracted from the same page (no extra request).
  • Rate: ~1–2 restaurants/second (network-bound by managed proxy response times).
  • Coverage: ~90,000 restaurant URLs across all US cities in sitemap.

Output example

Restaurant row (record_type: "restaurant", with nested reviews[]):

{
"record_type": "restaurant",
"restaurant_id": "2",
"name": "Thirsty Bear",
"slug": "thirsty-bear-san-francisco",
"url": "https://www.opentable.com/r/thirsty-bear-san-francisco",
"description": "ThirstyBear is the first & only Certified Organic brewery in San Francisco...",
"cuisine": ["Spanish", "Tapas / Small Plates", "Brewery"],
"cuisine_str": "Spanish, Tapas / Small Plates, Brewery",
"price_range": "$30 and under",
"rating": 3.9,
"review_count": 615,
"accepts_reservations": false,
"street_address": "661 Howard St.",
"city": "San Francisco",
"state": "CA",
"zip_code": "94105",
"country": "United States",
"address": "661 Howard St., San Francisco, CA, 94105",
"latitude": 37.78565,
"longitude": -122.399734,
"phone": "+1 415-974-0905",
"opening_hours": ["Monday 11:30am–10:00pm", "Friday 11:30am–11:00pm"],
"menu_url": null,
"reviews_extracted": 10,
"reviews": [ { "...": "see review row below" } ],
"scraped_at": "2026-06-07T12:00:00Z"
}

Review row (record_type: "review"):

{
"record_type": "review",
"restaurant_id": "2",
"restaurant_name": "Thirsty Bear",
"restaurant_slug": "thirsty-bear-san-francisco",
"review_id": "OT-2-1804022929-150018263055",
"reviewer_name": "JoeF",
"reviewer_initials": "JF",
"reviewer_metro": "San Francisco",
"reviewer_review_count": 12,
"overall_rating": 5,
"food_rating": 5,
"service_rating": 5,
"ambience_rating": 4,
"value_rating": 5,
"noise_level": "MODERATE",
"review_text": "Great craft beers. Tapas and paella were very good. Good service.",
"dined_date": "2018-05-25",
"review_date": "2018-05-27",
"party_size": null,
"occasion": "",
"helpful_count": 0,
"helpful_up": 0,
"helpful_down": 0,
"has_photos": false,
"scraped_at": "2026-06-07T12:00:00Z"
}

FAQ

Do I need to provide my own proxy or API key? No. Access is fully handled by the actor. No external credentials needed.

How much does it cost to scrape 1,000 restaurants? At the standard rate, 1,000 restaurant records cost $X. Compute costs are charged to your Apify account at standard Apify platform rates.

Is this legal? Does it violate OpenTable's Terms of Service? This actor scrapes publicly accessible restaurant profile pages. The data extracted (name, address, phone, cuisine, hours) is factual business information. Users are responsible for complying with OpenTable's Terms of Service and applicable data protection laws in their jurisdiction. The actor should not be used to spam restaurants, harvest personal data, or automate reservations.

Which cities are supported? Any city where OpenTable has restaurant listings. The city input must match OpenTable's URL slug format (e.g., san-francisco, new-york, los-angeles). See the Supported cities section above.

What if my city returns 0 results? Check that the city slug matches exactly what OpenTable uses in URLs. For example, Washington D.C. uses washington not washington-dc. If unsure, look up any restaurant in that city on OpenTable and copy the city portion of the URL slug.

Can I request custom data fields or bulk pricing? Yes — contact us via the actor Issues tab.

More scrapers from our toolkit

Building a data pipeline? These actors pair well with this one — each runs on your own Apify account with the same pay-per-result pricing, no subscription:

Chain any of them together from the Integrations tab (the Run succeeded trigger) to build a multi-step workflow — one actor's output feeds the next.

Usage statistics

This Actor creates a small, content-free summary at the end of each run. It is used only to monitor reliability and improve this Actor. A copy is saved as USAGE_STATS in your own Apify key-value store, so you can see the exact record created for your run.

Set disableUsageStats to true in the input to opt out. Nothing is sent then; your USAGE_STATS record only says that statistics were disabled.

Only these fields are recorded:

  • schema version, Actor name and build number;
  • UTC start and finish hour (not a precise timestamp);
  • run duration, number of results and time to the first result, each as a coarse range;
  • whether the result was empty, the end status, and an error type from a fixed list;
  • memory setting and counts of charged events;
  • names of the input fields you set, never their values;
  • the selected option for input fields that offer a fixed list of choices (for example a sort order).

We do not collect input text, search terms, URLs, domains, usernames, email addresses, names, proxy credentials, tokens, scraped records, output items, raw error messages, stack traces, or hashes of any of those values. Records are kept for no longer than 13 months, used only as aggregated operational statistics, and never sold or shared.

Additional fields (Phase 2)

This Actor also records your Apify user ID, whether Apify marks the account as paying, the size range of list inputs, the selected country when the input offers a fixed list of countries, and one category from a fixed Actor taxonomy. We use these fields only for aggregate reliability, repeat-use and cross-Actor analysis; reports suppress any cell with fewer than five distinct users.

The same disableUsageStats: true input flag turns these fields off too. The user ID is removed after 13 months; we do not export, sell, share, or attempt to re-identify this data.

Run-outcome signals (v2)

To learn whether a run did what it was asked to do, the record also holds a few more coarse ranges and yes/no flags. None of them contains content:

  • the result limit you asked for (a range, when the input has one) and what share of it was delivered;
  • results delivered per input item you listed (a range);
  • output quality as ranges: how fully the result fields were filled, the share of rows that look like errors, the share of duplicate rows, and how many different fields appeared. These are counted in memory while results are saved; no result content is kept;
  • how the run was started (console, API, schedule, webhook, another Actor);
  • how it ended: stopped by you, timed out, reached the requested limit, stopped by the charge limit, and how many times the platform moved the run;
  • if this Actor reports it: how many items to process worked or failed (ranges) and one failure reason from a fixed list;
  • a short code made from the names of the input fields you set, never their values.

Repeat-run fingerprint (v2)

When your Apify user ID is recorded (see above), the record also holds an 8-character one-way code made from your input (proxy settings left out) and this Actor's name. It only lets us see that the same account ran the same input again soon after an unsatisfying run; we never see the input itself. It is stored only in the database, never published, and reports use it in aggregate with the same five-user minimum. It is the one exception to the statement above that no hashes are collected, and disableUsageStats: true turns it off.