# Zomato Reviews Scraper — No Login, Owner Replies & Dish Tags (`yugenox/zomato-reviews-scraper`) Actor

Scrape Zomato restaurant reviews by restaurant link or id — no login. Star rating and label, dining vs delivery, full text, date, likes, owner replies, comments, photos, and liked / disliked dish tags. Newest, oldest, highest, lowest or combined coverage.

- **URL**: https://apify.com/yugenox/zomato-reviews-scraper.md
- **Developed by:** [Yugenox Corp](https://apify.com/yugenox) (community)
- **Categories:** E-commerce, Travel, Social media
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Zomato Reviews Scraper

Scrape **Zomato restaurant reviews** from any restaurant link — no Zomato account, no login, no cookies.
Each review comes back as one clean row: star rating and label, dining or delivery, the full review text,
the date, likes, **the restaurant's reply**, comments, photos, and the **dishes the reviewer liked or disliked**.

Works for every Zomato city — Delhi NCR, Mumbai, Bangalore, Pune, Hyderabad, Chennai, Kolkata, and the rest.

### What you get

| Field | Example |
|---|---|
| `restaurantName`, `restaurantId`, `restaurantUrl`, `city`, `locality` | My Bar Headquarters · 311057 · New Delhi · Connaught Place |
| `rating` / `ratingText` | 5 / "Excellent" |
| `reviewType` / `experience` | DINING or DELIVERY / dining_order, o2_delivery |
| `reviewText` | Full text, line breaks kept, no HTML |
| `date` / `dateIsApproximate` / `dateText` | 2026-09-18 / true / "6 days ago" |
| `likeCount`, `commentCount` | 3, 1 |
| `ownerReplies[]` | `{ text, date, dateText, repliedBy }` — the restaurant's public reply |
| `comments[]` | `{ text, date, dateText }` |
| `photos[]` | `{ url, thumbUrl }` |
| `positiveTags[]` / `negativeTags[]` | \["pizza yummy", "sitting arrangements are nice"] / \["dimsum was not good"] |
| `reviewId`, `reviewUrl` | 515830205 · https://www.zoma.to/eDwRKZn |
| `sortSource`, `filterSource`, `scrapedAt` | which list the review came from, and when |

Reviewer names and profiles are **not** included by default. Turn on **Reviewer details** if you need them.

#### Sample row

```json
{
  "dataType": "review",
  "restaurantId": 311057,
  "restaurantName": "My Bar Headquarters",
  "restaurantUrl": "https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi",
  "city": "New Delhi",
  "locality": "Connaught Place, New Delhi",
  "reviewId": 35005622,
  "reviewUrl": "https://www.zoma.to/xPegpZ",
  "rating": 3,
  "ratingText": "Average",
  "reviewType": "DINING",
  "experience": "dining",
  "reviewText": "food: average food and drinks. great hookah flavors. palatable food.\n\nambience: modern n chic decor and pumped with great music.\n\nservice: good.",
  "date": "2018-05-01",
  "dateIsApproximate": false,
  "dateText": "May 01, 2018",
  "likeCount": 0,
  "commentCount": 1,
  "ownerReplies": [
    {
      "text": "Thank you for you valuable feedback. We are glad to hear that you had a good time at My Bar. We hope you visit again in the future :)\n\nRegards\nTeam My Bar",
      "dateText": "May 02, 2018",
      "repliedBy": "My Bar Headquarters",
      "date": "2018-05-02"
    }
  ],
  "comments": [],
  "photos": [
    {
      "url": "https://b.zmtcdn.com/data/reviews_photos/dd7/221636334ee4d3347343d037c9869dd7_1568994908.jpg",
      "thumbUrl": "https://b.zmtcdn.com/data/reviews_photos/dd7/221636334ee4d3347343d037c9869dd7_1568994908.jpg?fit=around%7C200%3A200&crop=200%3A200%3B%2A%2C%2A"
    }
  ],
  "positiveTags": ["courteous staff", "live music"],
  "negativeTags": ["white sauce pasta", "expensive beer"],
  "sortSource": "lowest",
  "filterSource": "all",
  "scrapedAt": "2026-09-24T07:53:36.226Z"
}
```

### How to use

1. Paste one or more **restaurant links** — any tab works: the overview, `/info`, `/order`, `/reviews` or `/photos`,
   a `zoma.to` share link, or a review link. Restaurant ids (e.g. `311057`) work too.
2. Pick **how many reviews per restaurant**, the **sort** (newest, oldest, highest or lowest rated, or max coverage)
   and the **review type** (all, dining only, delivery only).
3. Run. Reviews stream into the dataset as they are collected; export to JSON, CSV, Excel or via API.

You can also paste a **city, area or collection listing** (e.g. `https://www.zomato.com/bangalore/indiranagar-restaurants`)
and the scraper takes the top restaurants from it (10 by default — see *Restaurants per listing link*).

Calling it from code? The usual names work as aliases: `url` / `urls` / `restaurantUrls` for `startUrls`, `restaurantIds` /
`resId` for `resIds`, `maxResults` / `limit` for `maxItems`, `maxReviews` for `maxReviewsPerRestaurant`, `sortBy` for `sort`
(`latest`, `top`, `worst`, `all` are understood), `type` for `reviewType` (`dine-in`, `online`), `since` / `newerThan` for
`sinceDate`. Links can also be one comma- or newline-separated string.
For large restaurant lists (whole cities, phones, menus), use the companion **Zomato Restaurants Scraper** and feed its
`resId` column into *Restaurant ids*.

#### Input example

```json
{
  "startUrls": [
    "https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi",
    "https://www.zomato.com/mumbai/jumboking-burgers-1-matunga-east/order"
  ],
  "maxReviewsPerRestaurant": 200,
  "sort": "newest",
  "reviewType": "all",
  "includeOwnerReplies": true
}
```

### Use cases

- **Reputation monitoring** — run daily or weekly with *Reviews newer than* = "7 days" and *Newest first*: the scraper
  stops as soon as it reaches older reviews, so each run costs a few cents.
- **Menu and dish analytics** — `positiveTags` / `negativeTags` name the exact dishes and aspects diners loved or complained about.
- **Response-rate audits** — `ownerReplies` shows which reviews a restaurant (or a competitor) answers, how fast, and how.
- **Dining vs delivery quality** — `reviewType` splits in-restaurant experience from delivery orders.
- **Sentiment and NLP datasets** — clean text with a 1–5 label, across cities and cuisines.
- **Competitor benchmarking** — collect the same window of reviews for every restaurant on a street or in a chain.

### How many reviews can I get?

Zomato shows visitors who are not logged in a limited window of each review list:

- **One sort order** (newest, oldest, highest or lowest): up to **~500 reviews reliably**, and up to **~1,000** when
  Zomato answers the larger pages in time (it did in all our test runs).
- **Max coverage** combines the four orders, plus the dining-only and delivery-only lists, and removes duplicates.
  When the newest-first and oldest-first windows meet, the restaurant is **complete** — every review Zomato shows was
  collected — and the scraper moves on. That is the case for most restaurants with up to ~2,000 reviews. For very busy
  restaurants, measured examples: **2,923** unique reviews for a Delhi restaurant listing 6,430, and **2,793** for a
  Bangalore restaurant listing 21,542 (the newest ~1,000, the oldest ~1,000, and the lowest-rated ones in between).

Zomato's own review count sometimes includes reviews it no longer displays (one Pune restaurant says 1,979 but shows
1,073 — all of which the scraper collects), so "complete" can be below the number on the page.

Every run saves a free **SUMMARY** record in its key-value store with, for each restaurant, the number of reviews on
Zomato, the number collected, `coverage` (complete / partial), and how each list ended — so you always know how complete your data is. The run's status
message says so too when a restaurant has more reviews than Zomato shows.

### Dates

Zomato displays recent reviews as "5 hours ago", "12 days ago" or "7 months ago", and older ones as "May 07, 2019".
The scraper turns every one into a calendar date (`date`, `YYYY-MM-DD`):

- exact dates and "hours / days ago" are accurate to the day;
- "N months ago" is estimated to within a few weeks (using the order in which Zomato issued the reviews) and flagged
  with `dateIsApproximate: true`. The original text is always kept in `dateText`.

### Pricing

Pay per result: you pay for each review saved to the dataset, plus a tiny fixed fee when a run starts. Duplicates (the
same review found in two sort orders) are never saved twice, and skipped reviews (older than your date, or rating-only
when you ask for text only) are never charged. The optional restaurant summary row counts as one result. Blocked
requests, retries, bad links and restaurants that are not found are free — a run that finds nothing costs only the
start fee. Set a spending limit on the run and the scraper stops cleanly when it is reached.

### FAQ

**Do I need a Zomato account or cookies?** No. Nothing to log in, nothing to refresh.

**Is it legal to scrape Zomato?** This scraper collects only public data — the restaurant pages and reviews that
Zomato shows to any visitor who is not logged in. Reviews are written by people, and with *Reviewer details* turned on
a row can also include a reviewer's public Zomato name and profile link, which counts as personal data. If you collect
it, make sure you have a legitimate reason and follow the privacy rules that apply to you (GDPR, PIPEDA, CCPA, India's
DPDP Act) and Zomato's terms. If you are unsure, check with a lawyer. More background:
[Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**Does it access any private data?** No. The scraper never logs in and uses no account, cookie or password — it reads
the same public pages anyone can open without signing in. It cannot see private profiles, orders, phone numbers,
e-mail addresses or anything else behind Zomato's login. Reviewer names and profile links are left out entirely
unless you turn on *Reviewer details*.

**Which proxy should I use?** The default (Apify datacenter proxy) works and is the cheapest. If Zomato ever starts
blocking it, the scraper switches to residential India proxies automatically.

**A link was skipped as "not found".** The restaurant page no longer exists on Zomato (closed or renamed). Open the
link in a browser to check; the rest of the run continues normally.

**Can a run fail?** It is built not to. Blocked or slow requests are retried on fresh IPs with backoff, then on
residential proxies; a run that hits its time limit or your spending limit stops early, keeps every review saved so
far, finishes as *Succeeded* and says why in its status message ("Partial — …"). Bad links or an empty input end
with 0 reviews and a hint, not an error. You only ever pay for rows in the dataset (plus the small start fee).

**Why are there fewer reviews than the restaurant's rating count?** Zomato's rating count (e.g. "6,861 Dining Reviews")
includes ratings that never appear in the review list. `reviewsOnZomato` in the SUMMARY is the number of reviews in the list.

**Some reviews have no text.** Those are rating-only reviews (common for delivery orders): `reviewText` is null but the
rating, date and type are there. Turn on *Only reviews with text* to skip them — skipped reviews are not charged.

**Can I get reviewer names?** Yes — turn on *Reviewer details*. It is off by default because most analyses don't need
personal details.

**How fast is it?** About 100 reviews every 5–7 seconds per restaurant, with 5 restaurants processed in parallel
by default (up to 10). A 20-review check of one restaurant finishes in a few seconds.

# Actor input Schema

## `startUrls` (type: `array`):

Restaurant pages on zomato.com — any tab works (overview, /info, /order, /reviews, /photos), plus zoma.to share links and review links. You can also paste a city, area or collection listing (e.g. https://www.zomato.com/bangalore/indiranagar-restaurants) to take the top restaurants from it.

## `resIds` (type: `array`):

Optional: Zomato restaurant ids (e.g. 311057), for example from the Zomato Restaurants Scraper's resId column.

## `maxReviewsPerRestaurant` (type: `integer`):

How many reviews to collect for each restaurant. 0 = as many as Zomato shows (see "How many reviews can I get?" in the README).

## `sort` (type: `string`):

Which reviews come first. "Max coverage" combines newest, oldest, lowest- and highest-rated (and dining / delivery lists) without duplicates, to reach the most reviews of a busy restaurant.

## `reviewType` (type: `string`):

All reviews, or only dining-in or only delivery reviews.

## `sinceDate` (type: `string`):

Optional. Only reviews from this date on — e.g. 2026-01-31, or a relative period like "30 days" / "3 months". With "Newest first" the scraper stops as soon as it reaches older reviews, which makes daily or weekly monitoring fast and cheap. Zomato shows reviews younger than a year as "N months ago", so this filter is precise to the day for recent weeks and to about a month for older reviews.

## `includeOwnerReplies` (type: `boolean`):

The restaurant's public replies to each review (text, date, who replied).

## `includeComments` (type: `boolean`):

Comments left under each review (text and date).

## `includePhotos` (type: `boolean`):

Links to the photos attached to each review.

## `includeReviewerInfo` (type: `boolean`):

Adds the reviewer's public Zomato name, profile link, review and follower counts (and commenter names). Off by default — most analyses don't need personal details.

## `includeRestaurantRow` (type: `boolean`):

Adds one row per restaurant (dataType = "restaurant"): dining and delivery rating, review counts, address, GPS, cuisines, and how many of its reviews were collected. A summary is always saved to the run's key-value store as SUMMARY, free.

## `skipRatingOnly` (type: `boolean`):

Skip rating-only reviews (a star rating with no written text — common for delivery orders). Skipped reviews are not charged.

## `maxRestaurantsPerListing` (type: `integer`):

Only for city / area / collection links: how many restaurants to take from each listing, in Zomato's own order.

## `maxItems` (type: `integer`):

Optional cap for the whole run across all restaurants.

## `maxConcurrency` (type: `integer`):

How many restaurants are scraped at the same time (each restaurant's pages are fetched one after another).

## `proxyConfiguration` (type: `object`):

Apify Proxy is required. The default (datacenter) works for Zomato and is the cheapest; the scraper switches to residential India proxies by itself if it gets blocked.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi"
  ],
  "maxReviewsPerRestaurant": 20,
  "sort": "newest",
  "reviewType": "all",
  "includeOwnerReplies": true,
  "includeComments": true,
  "includePhotos": true,
  "includeReviewerInfo": false,
  "includeRestaurantRow": false,
  "skipRatingOnly": false,
  "maxRestaurantsPerListing": 10,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped reviews.

## `summary` (type: `string`):

Reviews saved vs. listed on Zomato for each restaurant, and how each sort order ended.

## `run` (type: `string`):

Status and statistics for this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi"
    ],
    "maxReviewsPerRestaurant": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/zomato-reviews-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi"],
    "maxReviewsPerRestaurant": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/zomato-reviews-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.zomato.com/ncr/my-bar-headquarters-connaught-place-new-delhi"
  ],
  "maxReviewsPerRestaurant": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/zomato-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/zomato-reviews-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ThBTXq3JEBmWOC3XA/builds/gvWDvGcUsXhtWgklw/openapi.json
