# Zomato Restaurant Reviews Scraper (`automation-lab/zomato-restaurant-reviews-scraper`) Actor

Export public Zomato restaurant reviews with ratings, review text, reviewer metadata, dates, engagement, and restaurant context.

- **URL**: https://apify.com/automation-lab/zomato-restaurant-reviews-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.96 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Zomato Restaurant Reviews Scraper

Export public **Zomato restaurant reviews** as clean JSON, CSV, Excel, or API-ready dataset rows. Give the Actor restaurant URLs or numeric IDs and receive review text, ratings, reviewer metadata, dates, engagement, restaurant identity, and summary fields.

This Actor is built for recurring reputation analysis and market research. It paginates public Zomato review pages, deduplicates reviews by stable ID, applies rating filters before charging, and preserves source URLs for verification.

### What can you do with this Zomato reviews scraper?

- Build a review dataset for a one-time customer-feedback analysis.
- Schedule recurring runs and compare stable `reviewId` values to find new feedback.
- Route one- and two-star reviews into complaint-triage workflows.
- Compare restaurant ratings, review volume, themes, and reviewer engagement.
- Send normalized review rows to spreadsheets, databases, dashboards, or language models.

The Actor exports source data. It does not perform sentiment classification, alert delivery, or historical change storage by itself; connect the dataset to your preferred analysis workflow.

### Who is this Actor for?

**Restaurant operators** can review recent low-rated feedback without manually paging through Zomato.

**Reputation teams** can schedule a repeatable feed and deduplicate records using `reviewId`.

**Market researchers** can compare public ratings and review themes across supplied restaurants.

**Data teams** can consume a stable typed schema through Apify datasets, webhooks, integrations, or API clients.

**AI teams** can create source-attributed text datasets while retaining reviewer and restaurant context.

### Why use this Actor?

- **Review-level output:** one dataset row per accepted review, not a restaurant-only summary.
- **Useful context:** review text and rating are joined with reviewer, restaurant, date, and engagement fields.
- **URL and ID inputs:** use a restaurant URL directly or resolve a numeric restaurant ID from a supplied Zomato listing page.
- **Bounded pagination:** global and per-restaurant limits stop requests as soon as enough records are saved.
- **Rating filters:** collect all reviews or focus on a rating range such as one to two stars.
- **Stable deduplication:** duplicate review IDs are never saved or charged twice in one run.
- **Clear failures:** blocked, malformed, or non-restaurant pages fail instead of returning a misleading empty success.

### What data does the Actor extract?

| Group | Fields |
| --- | --- |
| Review | `reviewId`, `reviewText`, `rating`, `ratingLabel`, `experience`, `reviewUrl` |
| Date | `reviewDate`, `reviewDateLabel`, `scrapedAt` |
| Reviewer | `reviewerId`, `reviewerName`, `reviewerProfileUrl`, `reviewerProfileImageUrl`, `reviewerReviewCount`, `reviewerFollowerCount` |
| Engagement | `likeCount`, `commentCount`, `photoUrls`, `tags`, `managementResponses` |
| Restaurant | `restaurantId`, `restaurantName`, `restaurantUrl`, `restaurantCuisines`, `restaurantRating`, `restaurantReviewCount` |
| Traceability | `sourcePage`, `scrapedAt` |

Fields not exposed by a particular public review remain absent. `reviewDateLabel` preserves Zomato's displayed label. When the label is relative, `reviewDate` is an approximate date calculated at scrape time.

### How to scrape Zomato restaurant reviews

1. Open the Actor input page.
2. Paste one or more public Zomato restaurant URLs into **Zomato restaurant URLs**.
3. Set **Maximum reviews** and **Maximum reviews per restaurant**.
4. Optionally set a minimum and maximum rating.
5. Keep the default Apify Proxy configuration unless you have tested another route.
6. Click **Start**.
7. Open the **Reviews** dataset view and export JSON, CSV, Excel, XML, or RSS.

A restaurant `/info`, `/menu`, `/order`, or `/reviews` URL is accepted and normalized to the reviews page.

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | prefilled restaurant | Public Zomato restaurant URLs. |
| `restaurantIds` | string array | `[]` | Numeric Zomato restaurant IDs to resolve from `discoveryUrls`. |
| `discoveryUrls` | array | Bengaluru restaurants | Public listing pages containing requested restaurant IDs. Used only for ID input. |
| `maxItems` | integer | `20` | Maximum reviews saved across all restaurants, from 1 to 10,000. |
| `maxReviewsPerRestaurant` | integer | `20` | Maximum reviews saved for each restaurant, from 1 to 5,000. |
| `maxPagesPerRestaurant` | integer | `20` | Safety cap on source pages fetched for each restaurant. |
| `minRating` | number | `0` | Minimum accepted rating. |
| `maxRating` | number | `5` | Maximum accepted rating. |
| `includeEmptyText` | boolean | `false` | Keep rating-only entries with no public text. |
| `proxyConfiguration` | object | SHADER | Apify Proxy configuration. Lower-cost SHADER datacenter proxy is enabled by default. |

At least one restaurant URL or restaurant ID is required. Rating filters must satisfy `0 <= minRating <= maxRating <= 5`.

### URL input example

```json
{
  "startUrls": [
    {
      "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"
    }
  ],
  "maxItems": 25,
  "maxReviewsPerRestaurant": 25
}
```

### Restaurant ID input example

Numeric IDs are resolved from the embedded restaurant cards on the supplied discovery pages.

```json
{
  "restaurantIds": ["21913226"],
  "discoveryUrls": [
    { "url": "https://www.zomato.com/bangalore/restaurants" }
  ],
  "maxItems": 10
}
```

If an ID is not present on the supplied listing page, provide the restaurant URL or another listing page containing that ID. The Actor will not silently substitute another restaurant.

### Low-rating monitoring example

```json
{
  "startUrls": [
    {
      "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"
    }
  ],
  "minRating": 1,
  "maxRating": 2,
  "maxItems": 100,
  "maxReviewsPerRestaurant": 100
}
```

Schedule this input and compare `reviewId` against your previous dataset to identify newly observed low-rated feedback.

### Output example

The default dataset contains one object per review. This representative example is anonymized; live output contains public source values.

```json
{
  "reviewId": "500123456",
  "reviewText": "Example review text about the dining experience.",
  "rating": 2,
  "ratingLabel": "Poor",
  "experience": "dining_order",
  "reviewDate": "2025-01-15",
  "reviewDateLabel": "2 days ago",
  "reviewerId": "12345678",
  "reviewerName": "Example Reviewer",
  "reviewerProfileUrl": "https://www.zomato.com/users/example-reviewer-12345678",
  "likeCount": 1,
  "commentCount": 0,
  "restaurantId": "20012345",
  "restaurantName": "Example Restaurant",
  "restaurantUrl": "https://www.zomato.com/bangalore/example-restaurant/reviews",
  "restaurantCuisines": ["North Indian", "Chinese"],
  "restaurantRating": 4.3,
  "restaurantReviewCount": 1200,
  "sourcePage": 1,
  "scrapedAt": "2025-01-15T12:00:00.000Z"
}
```

### How pagination and limits work

Zomato currently exposes five reviews per public reviews page. The Actor requests pages in recent-first order and stops when any of these conditions is reached:

- `maxItems` is reached across the run;
- `maxReviewsPerRestaurant` is reached for the current restaurant;
- `maxPagesPerRestaurant` is reached;
- Zomato reports the last available page;
- no review entity section remains.

Filters are applied before a record is saved. A restrictive rating range may require more source pages to collect the requested number of matching reviews.

### How much does it cost to scrape Zomato restaurant reviews?

This Actor uses pay-per-event pricing:

- **Start:** $0.025 once per run.
- **Zomato review:** tiered per saved review. The BRONZE price is $0.001592 per review, with lower rates on higher plans.

Examples at BRONZE pricing:

| Saved reviews | Approximate Actor charge |
| ---: | ---: |
| 10 | $0.04092 |
| 100 | $0.18420 |
| 1,000 | $1.61700 |

Only accepted dataset rows trigger the review event. Invalid, duplicate, filtered-out, or failed records are not charged as review items. Platform compute and proxy usage may be billed separately according to your Apify plan and active pricing configuration.

### Schedule recurring reputation analysis

1. Save a Task with stable restaurant URLs and rating filters.
2. Add a daily or weekly schedule.
3. Export the default dataset or trigger a webhook when each run finishes.
4. Upsert records into your database using `reviewId` as the source key.
5. Compare first-seen timestamps and rating changes in your own history table.

Each run is a current public snapshot. The Actor does not maintain a cross-run database, so your destination should store history when change tracking matters.

### Export to spreadsheets and data pipelines

From the dataset page, export the review rows as JSON, CSV, Excel, XML, or RSS. You can also connect the run to:

- Google Sheets for a lightweight review inbox;
- webhooks for immediate downstream processing;
- Make or Zapier for no-code routing;
- cloud object storage or a warehouse for recurring analysis;
- language models for summarization or theme extraction.

Use stable source IDs rather than review text as the deduplication key.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~zomato-restaurant-reviews-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{"url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"}],
    "maxItems": 25
  }'
```

The response contains the run ID. Fetch dataset items after the run reaches `SUCCEEDED`.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/zomato-restaurant-reviews-scraper').call({
  startUrls: [
    { url: 'https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews' },
  ],
  maxItems: 25,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/zomato-restaurant-reviews-scraper').call(run_input={
    'startUrls': [
        {'url': 'https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews'}
    ],
    'maxItems': 25,
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/zomato-restaurant-reviews-scraper"
```

**Claude Desktop**, **Cursor**, and **VS Code** can use this equivalent MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/zomato-restaurant-reviews-scraper"
    }
  }
}
```

Example prompts:

- “Run the Zomato Restaurant Reviews Scraper for this restaurant URL and return the 20 latest reviews.”
- “Collect one- and two-star reviews for these Zomato restaurants and summarize recurring complaints.”
- “Export reviews for restaurant ID 21913226 using the Bengaluru listing page.”

### Proxy behavior and reliability

Zomato may block repeated direct requests. The default input uses Apify's SHADER datacenter proxy group because it returned complete public review state during validation at lower cost than residential traffic.

The Actor reuses one proxy identity during a run, retries only transient network, 408, 429, and selected 5xx failures, and stops after three attempts. Residential fallback is not automatic. This prevents an unexpected higher-cost mode from being triggered silently.

If you override the proxy configuration and extraction fails, restore the default setting before retrying.

### Limitations

- Only public, anonymously visible Zomato restaurant review pages are supported.
- Numeric IDs must be present on a supplied `discoveryUrls` listing page; a URL is more direct.
- Zomato currently displays some dates as relative labels. Normalized dates for those values are approximate.
- Public field availability varies by review. Photos, tags, responses, follower counts, or text may be absent.
- The Actor collects dining-review pagination exposed by the public page; delivery-only or login-only feedback may not be available.
- Source layout or anti-bot changes can temporarily interrupt extraction.
- The Actor does not provide sentiment scores, translations, alerts, or historical storage.

### Responsible use and legality

This Actor extracts publicly visible information. Your use must comply with applicable laws, Zomato's terms, Apify's Acceptable Use Policy, and privacy obligations relevant to your jurisdiction and purpose.

Do not use reviewer data for harassment, identity enrichment, discriminatory profiling, spam, or decisions that materially affect individuals. Collect only the fields and volume needed for a legitimate purpose. Respect deletion, retention, and access-control requirements in downstream systems.

### FAQ and troubleshooting

#### Why did the Actor say no reviews matched?

Check that the restaurant has public dining reviews and that `minRating` and `maxRating` are not too restrictive. Enable `includeEmptyText` only when rating-only entries are useful.

#### Why could a restaurant ID not be resolved?

The numeric ID was not present in the supplied listing page's embedded restaurant cards. Paste the direct restaurant URL or provide another public Zomato listing page containing that restaurant.

#### Why did a public page fail?

Restore the default SHADER proxy configuration. Confirm that the URL opens a restaurant rather than a city, search, profile, or 404 page. The Actor intentionally fails when structured review state is missing.

#### Can I scrape multiple restaurants?

Yes. Add multiple entries to `startUrls`. `maxItems` is shared across the run, while `maxReviewsPerRestaurant` applies separately to each restaurant.

#### How do I find new reviews between runs?

Store prior dataset rows and compare `reviewId`. A review ID first seen in the current run is a newly observed record in your workflow.

### Related Actor

Need restaurant discovery before collecting reviews? Use [Zomato Restaurant Search Scraper](https://apify.com/automation-lab/zomato-restaurant-search-scraper) to export public restaurant IDs, names, ratings, cuisines, localities, and profile URLs from Zomato city and cuisine pages.

# Actor input Schema

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

Public Zomato restaurant info, menu, order, or reviews URLs. The Actor normalizes each URL to its reviews page.

## `restaurantIds` (type: `array`):

Numeric Zomato restaurant IDs. Each ID is resolved from the listing pages in discoveryUrls.

## `discoveryUrls` (type: `array`):

Public Zomato listing pages containing the restaurant IDs above. Used only when restaurantIds is provided.

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

Maximum number of review records saved across all restaurants.

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

Per-restaurant cap. Pagination stops as soon as this cap or maxItems is reached.

## `maxPagesPerRestaurant` (type: `integer`):

Safety cap on source pages fetched per restaurant, especially useful with restrictive rating filters.

## `minRating` (type: `number`):

Keep reviews with this rating or higher.

## `maxRating` (type: `number`):

Keep reviews with this rating or lower.

## `includeEmptyText` (type: `boolean`):

When enabled, keep rating-only entries whose public review text is empty.

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

Apify Proxy is enabled with the lower-cost SHADER datacenter group by default because Zomato can block direct requests.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"
    }
  ],
  "restaurantIds": [],
  "discoveryUrls": [
    {
      "url": "https://www.zomato.com/bangalore/restaurants"
    }
  ],
  "maxItems": 20,
  "maxReviewsPerRestaurant": 20,
  "maxPagesPerRestaurant": 20,
  "minRating": 0,
  "maxRating": 5,
  "includeEmptyText": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "SHADER"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset containing all scraped review records.

# 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": [
        {
            "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/zomato-restaurant-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": [{ "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/zomato-restaurant-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": [
    {
      "url": "https://www.zomato.com/bangalore/cbd-central-bar-district-mg-road-bangalore/reviews"
    }
  ]
}' |
apify call automation-lab/zomato-restaurant-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/zomato-restaurant-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/WM1htlyJMqrSeM0nu/builds/9ZNNShecbeUv3MOCj/openapi.json
