# Zomato Scraper — Restaurants, Ratings & Reviews (`memo23/zomato-scraper`) Actor

Scrape Zomato restaurants and reviews across India and Dubai. Get names, ratings, votes, cuisines, cost for two, GPS, phone numbers and full addresses — plus paginated reviews with text, rating, date and reviewer profile. Search any city or paste restaurant URLs. JSON, CSV or Excel.

- **URL**: https://apify.com/memo23/zomato-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** E-commerce, Travel, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.00 / 1,000 restaurant results

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?

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 Scraper — Restaurants, Ratings & Reviews (India & Dubai)

> **$0.002 per restaurant · $0.001 per review · $0.0002 per menu item** · no browser, pure HTTP · restaurants, menus *and* reviews in one Actor

Scrape **Zomato restaurants and their reviews** across **India and Dubai**. Give a city, a search keyword, or paste any Zomato URL, and get clean rows with name, rating, votes, cuisines, cost for two, opening hours, full address, GPS coordinates and phone numbers — plus, optionally, **paginated reviews** with the full review text, rating, date and reviewer profile.

### Why use this scraper

- **Restaurants, menus and reviews in one Actor.** Most Zomato scrapers do one of the three; here menus and reviews are toggles on the same run, so everything about a restaurant comes from a single job.
- **Reviews priced to actually use at scale.** $0.001 per review, versus $0.00299 plus a $0.09 per-run start fee elsewhere. A 100-review pull costs about **$0.11 instead of ~$0.39**.
- **Contact-grade detail.** Phone numbers, GPS latitude/longitude, postcode and full street address on every restaurant — not just names and ratings.
- **No browser.** Pure HTTP against Zomato's own JSON routes, so runs are fast and cheap. It works without a proxy; plug your own residential proxy in only if you hit rate limits at scale.
- **Honest coverage.** Zomato has exited most international markets. This Actor targets what is actually live — India and Dubai — instead of advertising dead cities.

### Supported inputs

| Input | Example | Notes |
|---|---|---|
| City slug | `mumbai`, `ncr`, `bangalore`, `kolkata`, `dubai` | `ncr` is Delhi NCR |
| Search keyword | `pizza`, `biryani` | Searched within each city |
| City listing URL | `https://www.zomato.com/mumbai/restaurants` | Expanded to restaurants |
| Restaurant URL | `https://www.zomato.com/mumbai/leopold-cafe-colaba` | Scraped directly; `/order`, `/reviews` and `/info` tails are fine |

**Coverage note:** Zomato operates in **India (all cities) and Dubai**. Other countries — UK, Singapore, Australia, Turkey, Indonesia, Malaysia, Qatar, Sri Lanka, the Philippines, New Zealand, South Africa — were shut down by Zomato and return no data anywhere.

### Use cases

- Track competitor ratings, review sentiment and cost positioning across a city.
- Build restaurant lead lists with phone numbers and exact coordinates.
- Feed review text into sentiment analysis, LLM summarisation or RAG pipelines.
- Monitor how a chain's outlets are rated across localities.
- Market research on cuisine mix and price bands by neighbourhood.

### How it works

1. **Resolve** — city slugs and keywords become Zomato listing paths; pasted URLs are classified as listing or restaurant.
2. **Expand** — each listing is read through Zomato's page JSON route and its restaurant cards are collected (cost for two and cuisines ride along from the card).
3. **Detail & reviews** — every restaurant page is fetched for ratings, contact and location; with reviews enabled, the review route is paged until the per-restaurant cap is reached. Rate limits (HTTP 429) trigger exponential backoff, and a fresh exit IP when a proxy is configured.

### Input configuration

| Field | Type | Default | Description |
|---|---|---|---|
| `cities` | array | `["mumbai"]` | Zomato city slugs |
| `queries` | array | — | Keywords searched per city |
| `startUrls` | array | — | Any Zomato listing or restaurant URLs |
| `maxItems` | integer | `100` | Hard cap on restaurant rows |
| `scrapeMenu` | boolean | `false` | Also fetch the menu (billed per item; no prices — see below) |
| `scrapeReviews` | boolean | `false` | Also fetch reviews (billed per review) |
| `maxReviewsPerRestaurant` | integer | `20` | Review cap per restaurant |
| `reviewsSort` | string | `newest` | `newest`, `popular`, `highest`, `lowest` |
| `maxConcurrency` | integer | `5` | Keep low — Zomato rate-limits hard |
| `proxy` | object | none | Optional — add your own proxy only if rate-limited |

Minimal input:

```json
{
    "cities": ["mumbai"],
    "maxItems": 50
}
```

### Output — key fields

**Restaurant row**

| Field | Description |
|---|---|
| `resId`, `name`, `url` | Zomato restaurant id, name and canonical URL |
| `rating`, `ratingText`, `votes`, `reviewCount` | Aggregate rating, its label and vote count |
| `cuisines`, `costForTwo` | Cuisine list and typical spend for two |
| `address`, `locality`, `city`, `country`, `zipcode` | Full location |
| `latitude`, `longitude` | GPS coordinates |
| `phones` | Published phone numbers |
| `timings`, `statusText` | Opening hours and current open/closed state |
| `isPermanentlyClosed`, `isTemporarilyClosed`, `isDeliveryOnly` | Status flags |
| `chain`, `thumbnail` | Chain hint and image |

**Menu items** (when `scrapeMenu` is on) — attached to the restaurant row as `menuItems[]`

| Field | Description |
|---|---|
| `itemId`, `name`, `description` | The dish |
| `category`, `menuName` | Where it sits in the menu |
| `isVeg`, `dietaryTags` | Vegetarian flag and dietary labels |
| `imageUrl` | Dish photo when Zomato has one |
| `menuPricesAvailable` | Always `false` — see the price note below |

> **Menu prices are not included.** Zomato withholds menu prices from signed-out visitors (its payload sets `price_login_blocker` and carries no price field). This Actor does not bypass logins, so menu items ship with names, descriptions, categories and dietary tags — but no prices. If you need priced menus, a Talabat scraper is the better fit for that market.

**Review row** (when `scrapeReviews` is on)

| Field | Description |
|---|---|
| `reviewId`, `resId`, `restaurantName`, `restaurantUrl` | Review and its restaurant |
| `rating`, `reviewText`, `timestamp`, `reviewUrl` | The review itself |
| `userName`, `userProfileUrl`, `userProfilePic` | Reviewer identity |
| `userReviewCount`, `userFollowers` | Reviewer credibility signals |
| `likesCount`, `commentsCount`, `photos` | Engagement and attached photos |

#### Output samples

```json
{
    "resId": 20597081,
    "name": "Fish Curry Rice",
    "url": "https://www.zomato.com/mumbai/fish-curry-rice-dadar-shivaji-park",
    "rating": 4.6,
    "ratingText": "Very Good",
    "votes": 1651,
    "cuisines": ["Maharashtrian", "Goan", "Seafood", "Malwani"],
    "costForTwo": "₹1,000 for two",
    "address": "Shop 3, First Floor, Kohinoor Square, NC Kelkar Marg, Dadar Shivaji Park, Mumbai",
    "city": "Mumbai",
    "country": "India",
    "latitude": 19.024895,
    "longitude": 72.841919,
    "phones": ["+919028057082"],
    "timings": "11am – 11pm (Today)",
    "statusText": "Open now"
}
```

```json
{
    "reviewId": 498815038,
    "resId": 20597081,
    "restaurantName": "Fish Curry Rice",
    "rating": 4,
    "reviewText": "The food was decent. Starters were really good …",
    "timestamp": "5 days ago",
    "userName": "User",
    "userProfileUrl": "https://www.zomato.com/users/user-405968657",
    "reviewUrl": "https://www.zoma.to/gPazlYR"
}
```

### Pricing

Pay-per-event: **$0.002 per restaurant**, **$0.001 per review** and **$0.0002 per menu item**, plus a $0.005 actor-start fee per GB of run memory. Menus and reviews are only billed when you enable them.

| Job | Cost |
|---|---|
| 100 restaurants, no reviews | $0.20 |
| 100 restaurants + 20 reviews each (2,000 reviews) | $2.20 |
| 1 restaurant + 100 reviews | $0.10 |
| 1 restaurant + a 350-item menu | $0.08 |

### FAQ

**Which countries does Zomato cover?** India and Dubai. Zomato shut down its other international markets, and those city pages return nothing — for UAE beyond Dubai or the wider Gulf, a Talabat scraper is the right tool.

**Do I need a browser or a proxy?** Neither. Zomato answers over plain HTTP and the Actor runs proxy-free by default. If you push volume and start seeing rate limiting (HTTP 429), add your own proxy in the input — the Actor backs off and rotates the exit IP automatically.

**How many reviews can I get per restaurant?** As many as Zomato exposes — the route is paged five at a time and the Actor keeps going until your `maxReviewsPerRestaurant` cap.

**Can I scrape one specific restaurant?** Yes. Put its URL in `startUrls`; `/order`, `/reviews` and `/info` variants all work.

**Why is `photos` sometimes empty?** Most reviews have no attached photos. The field populates when the reviewer uploaded images.

**Are menus included?** Yes, via `scrapeMenu` — item names, descriptions, categories and veg/dietary tags. Prices are the one thing Zomato does not publish to signed-out visitors, so they are not included; this Actor does not bypass logins.

### Support

Found a bug or need another field? Open an issue on the Actor's Issues tab — it is monitored.

### 🤖 For AI Agents & LLM Apps

**Purpose:** Turn a Zomato city, keyword or URL into structured restaurant rows and, optionally, review rows. Browser-free.

**Minimal input (tested):**

```json
{ "cities": ["mumbai"], "maxItems": 20, "scrapeReviews": true, "maxReviewsPerRestaurant": 10 }
```

**Behaviors agents should know:**

- Two row types share the dataset: restaurant rows have `resId` without `reviewId`; review rows always have `reviewId`. Menu items are nested on the restaurant row as `menuItems[]`, not separate rows.
- `menuItems[].price` does not exist — Zomato gates menu prices behind login. Do not expect it.
- Billing is `restaurant-result` ($0.002) and `review-result` ($0.001); bound spend with `maxItems` and `maxReviewsPerRestaurant`.
- Only India and Dubai return data; other cities yield nothing.
- `rating` on a review is 1–5; on a restaurant it is the aggregate (often fractional).

### ⚠️ Disclaimer

This Actor collects only publicly available restaurant and review pages from Zomato — no logins, paywalls or access controls are bypassed. Review text and reviewer profiles are personal data: use them in compliance with Zomato's Terms of Service, GDPR/DPDP and applicable law, and do not republish reviews as your own. You are responsible for how you use the output.

### SEO Keywords

zomato scraper, zomato api, scrape zomato, zomato restaurant scraper, zomato reviews scraper, zomato data extraction, restaurant reviews scraper, india restaurant data, mumbai restaurant scraper, delhi ncr restaurants, bangalore restaurant data, dubai restaurant scraper, food delivery data, restaurant ratings dataset, review sentiment data, restaurant lead generation, zomato json, apify zomato.

# Actor input Schema

## `cities` (type: `array`):

Zomato city slugs to browse, e.g. "mumbai", "ncr" (Delhi NCR), "bangalore", "kolkata", "dubai". Zomato operates in India and Dubai only.

## `queries` (type: `array`):

Keywords searched within each city, e.g. "pizza", "biryani". Leave empty to take the city's main restaurant listing.

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

Any Zomato URLs — a city listing (https://www.zomato.com/mumbai/restaurants) or a restaurant page (https://www.zomato.com/mumbai/leopold-cafe-colaba). Mixed freely with the city/keyword inputs.

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

Hard cap on restaurant rows for the whole run. Reviews are capped separately.

## `scrapeMenu` (type: `boolean`):

Also fetch each restaurant's menu — item names, descriptions, category and veg/non-veg tags. NOTE: Zomato hides menu PRICES from signed-out visitors, so menu items do not include prices. Billed per menu item.

## `scrapeReviews` (type: `boolean`):

Also fetch each restaurant's reviews (text, rating, date, reviewer profile). Billed per review — off by default.

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

Upper bound on reviews fetched for each restaurant. Zomato returns 5 reviews per page.

## `reviewsSort` (type: `string`):

Which reviews to take first.

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

Parallel requests. Zomato rate-limits aggressively (HTTP 429), so keep this low.

## `proxy` (type: `object`):

Zomato is reachable without a proxy, so this is off by default. If you hit rate limiting (HTTP 429) at scale, plug in your own residential proxy here — the scraper backs off and rotates the exit IP automatically.

## Actor input object example

```json
{
  "cities": [
    "mumbai"
  ],
  "maxItems": 100,
  "scrapeMenu": false,
  "scrapeReviews": false,
  "maxReviewsPerRestaurant": 20,
  "reviewsSort": "newest",
  "maxConcurrency": 5,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "cities": [
        "mumbai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/zomato-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 = { "cities": ["mumbai"] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/zomato-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 '{
  "cities": [
    "mumbai"
  ]
}' |
apify call memo23/zomato-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/zomato-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/ZqEfH6T70llCWvFyf/builds/XzhH4mQJ1N0u9m0ks/openapi.json
