Zomato Scraper - Restaurants, Ratings & Reviews avatar

Zomato Scraper - Restaurants, Ratings & Reviews

Pricing

from $1.00 / 1,000 restaurant scrapeds

Go to Apify Store
Zomato Scraper - Restaurants, Ratings & Reviews

Zomato Scraper - Restaurants, Ratings & Reviews

Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes, cost for two, address and coordinates, phone, per-vertical ratings, delivery info, plus per-review rating, author, date, text, photos and tags. Search a city feed or paste URLs.

Pricing

from $1.00 / 1,000 restaurant scrapeds

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

15

Total users

2

Monthly active users

4 days ago

Last modified

Share

Zomato Scraper: Restaurant Profiles, Ratings & Reviews

Zomato Scraper turns Zomato into a clean restaurant data API. Search a city's Delivery or Dining Out feed, or paste restaurant, feed, or keyword-search links, and get structured JSON for every restaurant: cuisines, ratings, cost, address, coordinates, phone numbers, delivery status, and full review text. Add incremental mode for recurring monitoring, or export straight into Notion, Linear, or Airtable.

Why This Scraper?

  • Two ways to find restaurants. Search one or more city slugs on the Delivery or Dining Out feed, with an optional keyword, or paste restaurant, feed, and keyword-search links directly.
  • Full restaurant profiles. Cuisines, cost for two, per-vertical (Dining and Delivery) ratings, address, coordinates, phone numbers, chain name, and open/closed status, all on by default.
  • Reviews included. Attach up to 2000 reviews per restaurant with rating, author, date, like count, tags, and full text, sorted the way you want.
  • Offers filter. Narrow the Dining Out feed and keyword search to restaurants currently running a dining offer.
  • Built for schedules. Incremental mode returns only new and changed restaurants on recurring runs, with a resume option for one interrupted run.
  • Automatic mid-run checkpointing. A run that gets migrated or restarted by the platform picks up where it stopped instead of re-collecting and re-charging restaurants already pushed.
  • Cost control. Cap the number of restaurants and the number of feed pages walked per run, and turn detail or review fetching off entirely if you only need the feed listing.

Use Cases

  • Food delivery market research: compare cuisines, cost for two, and delivery times across a city or neighborhood.
  • Reputation and review analysis: pull full review text and ratings to track sentiment for specific restaurants or chains.
  • Restaurant discovery apps: feed structured listings into your own search, map, or recommendation tool.
  • Competitive monitoring: schedule the actor and use incremental mode to catch new listings, rating changes, or closures over time.
  • Local business directories: collect address, phone, and status data for restaurants in a city or region.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
id"18685577"
name"Olive Bar & Kitchen"
url"https://www.zomato.com/ncr/olive-bar-kitchen-mehrauli-new-delhi"
cuisines / cuisineString["Italian", "Mediterranean"] / "Italian, Mediterranean"
locality"Mehrauli, New Delhi"
costForTwoText"₹4,000 for two"
rating{ "aggregate": 4.6, "text": "4.6", "subtitle": "Very Good", "votes": 3049 }
ratingsByVertical{ "DINING": { "rating": 4.6, "reviewCount": 3049 }, "DELIVERY": { "rating": 4.1, "reviewCount": 1200 } }
timing{ "text": "12noon to 1am", "isOpenNow": true }
thumbnailimage URL
deliveryTime"30-35 mins"
hasOnlineOrdering / isServiceabletrue / true
hasPromo / promoOffertrue / "20% OFF"
address"One Style Mile, Kalka Das Marg, Mehrauli, New Delhi" (with Fetch full details)
latitude / longitude28.5245 / 77.1855 (with Fetch full details)
city / country / zipcode"New Delhi" / "India" / "110030" (with Fetch full details)
phones["+911234567890"] (with Fetch full details)
chainName"Olive Group" (with Fetch full details)
statusText / isDeliveryOnly / isPermanentlyClosed"Opens at 12noon" / false / false (with Fetch full details)
establishments["Fine Dining", "Bar"] (with Fetch full details)
reviewsCount1 (number of reviews captured in this record, not Zomato's total; with Fetch reviews)
source / detailScraped"search" / true
changeType"NEW" (incremental mode only)

With Fetch reviews on (the default), each restaurant also carries a reviews array. Each entry has its own id, numeric rating and ratingText, full text, author, authorProfileUrl, authorReviewsCount, authorFollowersCount, timestamp, likeCount, commentCount, dining or delivery experience, photos, tags, reviewUrl, and, when the site returns them, textShort (currently always null: the site ships its short text as an empty string), authorId, authorPhotoUrl, ratingColor, ratingBgColor, comments and ownerReplies (the reader comments and the restaurant's replies, each a list of objects with id, text, author, authorId, authorProfileUrl, authorPhotoUrl and timestamp), isEditable, externalUrl, externalHost, and externalHostText. This is one array field on the restaurant record, not a separate record type: reviews never appear as their own top-level dataset rows.

How to Use

  1. Pick a mode: Search a city (city slugs and a feed) or Paste URLs (restaurant, feed, or keyword-search links).
  2. For search mode, add one or more cities (a name such as Delhi NCR, Mumbai or Dubai, or a city slug such as ncr), choose Delivery or Dining Out, and optionally a keyword.
  3. Leave Fetch full details and Fetch reviews on for full profiles, or turn either off to save on cost.
  4. Set Max restaurants to control run size and cost, then click Start.

Search a city's delivery feed:

{
"mode": "search",
"cities": ["ncr"],
"context": "delivery",
"maxItems": 20
}

Search a keyword on the Dining Out feed, offers only:

{
"mode": "search",
"cities": ["mumbai"],
"context": "dine-out",
"query": "pizza",
"offersOnly": true,
"maxItems": 30
}

Paste restaurant and feed URLs:

{
"mode": "url",
"urls": [
"https://www.zomato.com/ncr/olive-bar-kitchen-mehrauli-new-delhi",
"https://www.zomato.com/mumbai/dine-out"
],
"fetchReviews": true,
"maxReviews": 50
}

Supported URL shapes (url mode): zomato.com, www.zomato.com and m.zomato.com all work.

ShapeExample
Delivery city feedhttps://www.zomato.com/ncr/delivery
Dine-out city feedhttps://www.zomato.com/ncr/dine-out
Restaurants city feed (walked as the dine-out feed)https://www.zomato.com/ncr/restaurants
Keyword search feedhttps://www.zomato.com/ncr/restaurants/pizza
Keyword search as a query string (?q=; scraped as the keyword feed above)https://www.zomato.com/ncr/restaurants?q=pizza
Delivery dish listhttps://www.zomato.com/ncr/delivery/dish-biryani
Locality delivery listhttps://www.zomato.com/ncr/delivery-in-connaught-place
City without a delivery list (warns that the site serves no list there; try dine-out)https://www.zomato.com/dubai/delivery
Dine-out offers filter (turns on the Offers filter)https://www.zomato.com/ncr/dine-out?dining_payment_enabled_flag=true
Paginated feed (?page=N; the feed is still walked from page 1, logged as a warning, and the URL counts as a partial scope for EXPIRED)https://www.zomato.com/ncr/restaurants?page=2
Bare city page (walked as /<city>/restaurants)https://www.zomato.com/mumbai
Restaurant page, base / order / reviews / info / menu / photoshttps://www.zomato.com/ncr/kfc-connaught-place-new-delhi/order
Mobile and apex hostshttps://m.zomato.com/ncr/kfc-connaught-place-new-delhi/order, https://zomato.com/ncr/delivery

A URL the actor cannot handle is skipped with a warning in the run log naming the URL and the reason, never silently: another site, a non-city path, an unknown delivery sub-path, and the locality landing pages /<city>/dine-out-in-<locality> and /<city>/<locality>-restaurants (the site lists the whole city there, not the locality; use /<city>/delivery-in-<locality> instead). ?q= is not honoured on delivery feeds (warned); a restaurant page that cannot be read produces a warning and no row.

Default URLs: if you run url mode with the URLs field untouched (or omit urls through the API), the default list holds one example URL of each shape above except the near-duplicate paginated form (18 URLs, the restaurant pages all resolve to the same restaurant). The list is walked in order until Max restaurants (default 20) is reached, so with the default settings only the first feed (or feeds) is scraped and the later example URLs are not reached; raise Max restaurants to walk more of them. Pass your own list, or an empty list, to change that. restaurantUrls is accepted as an alias of urls, and proxyConfiguration as an alias of proxy.

Listing only, no detail or review fetch (lowest cost per restaurant):

{
"mode": "search",
"cities": ["mumbai", "dubai"],
"context": "dine-out",
"fetchDetails": false,
"fetchReviews": false,
"maxItems": 100
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/zomato-scraper").call(run_input={"mode": "search", "cities": ["ncr"], "context": "delivery"})
for restaurant in client.dataset(run["defaultDatasetId"]).iterate_items():
print(restaurant["name"], restaurant["rating"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/zomato-scraper').call({ mode: 'search', cities: ['ncr'], context: 'delivery' });
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 this run returns only restaurants not already collected there. A separate, automatic mid-run checkpoint also protects against platform migrations without any input needed, so an interrupted run never re-pushes (or re-charges) a restaurant it already collected.
  • Incremental mode (incrementalMode) is for scheduled runs over the same city, feed, or URL set. The first run returns everything as NEW. Later runs classify each restaurant as NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), or EXPIRED (only after a run that scanned the full tracked scope without a cap or a resume, and only with emitExpired on). REAPPEARED is produced only for a restaurant that an earlier run marked as expired (which requires emitExpired on and a complete scan in that earlier run) and that then shows up again in any later run; it does not appear on an ordinary recurring run that never turned emitExpired on. stateKey names or shares the stored state; leave it empty and the actor derives one automatically from the city/feed/URL and detail/review settings. Change detection compares the restaurant fields (name, cuisines, address, rating, cost, timings text, phones, review text) and ignores live or rotating ones by default while keeping them in the output: thumbnail, isOpenNow, deliveryTime, promoOffer, the relative review timestamp (such as "15 hours ago"), vote and review counters, and per-review like, comment and author counters plus the per-review reply thread (comments). Add more with ignoreFieldsForChanges (names match at any depth). Changing the ignore list never causes a one-time wave of UPDATED rows. With incremental mode off, output is exactly as before.

Send results into your apps (MCP connectors)

Optionally pipe the scraped restaurants into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each restaurant, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where restaurants should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (city and feed) or url (paste links).
citiesarraynone (prefill: ["ncr"])City names (Delhi NCR, Mumbai, Dubai) resolved with the site's own location lookup, or Zomato city slugs. A name that matches nothing stops the run with an error. If left empty in search mode, the run falls back to ncr.
contextstringdeliverydelivery or dine-out feed, for search mode.
querystringnoneOptional cuisine, dish, or restaurant keyword to narrow the feed.
offersOnlybooleanfalseApply Zomato's own Offers filter. Only affects the Dining Out feed and keyword search; Delivery has no such filter.
urlsarrayone example URL of each supported URL shape (18 URLs, see above), walked in order until Max restaurants is reachedRestaurant, city-feed, bare-city, or keyword-search URLs, for url mode. Alias: restaurantUrls.
fetchDetailsbooleantrueEnrich each restaurant with address, coordinates, phone, per-vertical ratings, and taxonomy from its page.
fetchReviewsbooleantrueAttach reviews to each restaurant.
maxReviewsinteger20Cap on reviews captured per restaurant (0 to 2000). 0 disables reviews.
reviewsSortstringpopularpopular, newest, oldest, rating-high, or rating-low.
maxItemsinteger20Total restaurants to return across all cities or URLs. 0 = no limit, capped at 100000.
maxPagesintegernone (empty/0 = unlimited)Safety bound on feed pages walked per city or URL; the run still stops at Max restaurants or the feed's own end.
resumeFromRunIdstringnoneID of a previous run or dataset. Restaurants already there are skipped this run (a delta).
incrementalModebooleanfalseTurn on for daily or recurring monitoring; returns only new and changed restaurants after the first run.
stateKeystringnoneOptional name for an incremental-mode monitoring campaign, to keep its state stable or deliberately share it.
emitUnchangedbooleanfalseAlso return (and bill) restaurants unchanged since the last run, marked UNCHANGED.
emitExpiredbooleanfalseAlso return (and bill) restaurants no longer found, marked EXPIRED, once a run has fully scanned the tracked search.
ignoreFieldsForChangesarray[]Incremental mode only. Extra field names (any depth) that are not compared when detecting changes; they stay in the output.
proxyobject{ "useApifyProxy": true }Connection settings.
mcpConnectorsarraynoneOptional: send a summary of each restaurant to apps you authorized under Integrations.
notionParentPageUrlstringnoneNotion connector only: page under which restaurant pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run (1 to 1000). Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"id": "18685577",
"name": "Olive Bar & Kitchen",
"url": "https://www.zomato.com/ncr/olive-bar-kitchen-mehrauli-new-delhi",
"cuisines": ["Italian", "Mediterranean", "Continental"],
"cuisineString": "Italian, Mediterranean, Continental",
"locality": "Mehrauli, New Delhi",
"costForTwoText": "₹4,000 for two",
"rating": { "aggregate": 4.6, "text": "4.6", "subtitle": "Very Good", "votes": 3049, "color": "3F7E00" },
"ratingsByVertical": {
"DINING": { "rating": 4.6, "reviewCount": 3049, "subtitle": "Very Good" },
"DELIVERY": { "rating": 4.1, "reviewCount": 1200, "subtitle": "Very Good" }
},
"address": "One Style Mile, Kalka Das Marg, Mehrauli, New Delhi",
"city": "New Delhi",
"country": "India",
"latitude": 28.5245,
"longitude": 77.1855,
"phones": ["+911234567890"],
"chainName": "Olive Group",
"hasPromo": true,
"promoOffer": "20% OFF",
"detailScraped": true,
"reviews": [
{
"id": "r123",
"rating": 5,
"ratingText": "Excellent",
"author": "Jane D.",
"timestamp": "5 days ago",
"text": "Fantastic ambience and food.",
"likeCount": 3,
"commentCount": 0,
"tags": ["Great Ambience"]
}
],
"reviewsCount": 1,
"source": "search"
}

Output views

The dataset has four views in the Console and API (?view=):

  • Restaurants Overview (overview): one row per restaurant with name, cuisines, rating, cost, locality and delivery status.
  • Restaurant details (detail): address, coordinates, phones, chain, status and per-vertical ratings.
  • Reviews, one row per review (reviews): the nested reviews array unwound into one row per review, for spreadsheets. The stored dataset itself still has one record per restaurant.
  • Changes (changes): changeType and changedFields for incremental runs.

A run summary is also saved to the OUTPUT key of the key-value store.

Plan Requirement

Zomato pages were measured on 2026-10-19 over the standard Apify datacenter proxy pool (no challenge, full restaurant, feed and review data), so the default Proxy setting is the intended path and Residential is not required.

On an account without Apify Proxy access (an Apify free plan) the run still works: it logs a boxed notice (see below), continues on a backup connection, logs "Served over the backup connection (the default connection was unavailable)." and returns rows. The run fails only when every connection, the backup included, refuses the tunnel.

What you may see in the run log:

  • If every connection, the backup included, refuses the tunnel the run ends as failed 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 you entered your own proxy URLs, check their credentials.
  • If the default connection is refused (for example on a free plan), the actor escalates to a backup connection and warns: "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." When a backup works it logs: "Served over the backup connection (the default connection was unavailable)."
  • If residential access is unavailable and the backup cannot load the pages, the run fails with: "No results: residential proxies are unavailable on this run ... and the backup connection could not load the requested Zomato pages."
  • If no results page could be loaded at all, the run fails with: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."

A run that loads no page never reports an empty success. A custom proxy URL is used as given, with no backup connections.

FAQ

How much does it cost?

Billing is per event, and the Pricing tab shows the current rates:

EventUnitWhen it is billed
Restaurant result (apify-default-dataset-item)one per restaurant row pushedeach restaurant returned; with incremental mode, unchanged restaurants are not pushed or billed unless Emit unchanged is on
Detail enrichment (detail-enrichment)one per restaurant detail page fetchedonly with Fetch full details on, for restaurants whose details were fetched and delivered
Review enrichment (review-enrichment)one per review deliveredonly with Fetch reviews on; billed per review as it is delivered, capped by Max reviews per restaurant (default 20, up to 2000)

At the defaults (details on, 20 reviews) reviews are the largest part of the bill, so lower Max reviews per restaurant or turn off Fetch reviews for listing-only runs. Nothing is billed for restaurants or reviews that were not delivered.

Use Max restaurants and Max pages per feed to cap the cost of any run.

This actor collects only publicly visible restaurant and review data; login-gated fields are not collected. You are responsible for how you use it: follow Zomato's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Review text and author names are user-generated content and may carry their own rights.

Can I get only new or changed restaurants on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated restaurants, and unchanged ones are not billed. Turn on Emit expired if you also want restaurants that disappeared marked EXPIRED; that only fires once a run has scanned the entire tracked search without a cap or a resume.

Why did my run return fewer restaurants than expected, or none at all?

Most causes return an empty or partial dataset rather than failing the run: a city slug or keyword with no results, a feed page that couldn't be read after retries, or Max restaurants capping the run early. Check the run's status message for the reason. The run fails outright only in two cases: an invalid resumeFromRunId (a run or dataset ID that doesn't resolve), or combining resumeFromRunId with incremental mode on a search that already has saved incremental state.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear, or Airtable.

🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

🧩 OpenTable Reviews Scraper
Scrape full OpenTable.com restaurant reviews by keyword, area, or restaurant profile...
🛒 GrabFood Restaurant Scraper
Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH...
🛒 Wolt.com
Scrape Wolt restaurants and full menus at the city scale. Extract 60+ fields, including...
🛒 DoorDash Scraper
Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...
🧩 Resy Scraper
Scrape Resy restaurants and live reservation availability by city or URL. Get bookable...
🛒 IKEA Scraper
Scrape IKEA products across 50+ markets. Extract names, prices, currencies, ratings, full...

👉 Browse all abotapi scrapers

💬 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.