# Zomato Scraper - Restaurant Ratings & Prices by City (`superslowsloth/zomato-restaurants`) Actor

Zomato scraper: city slugs (Mumbai, Bangalore, Delhi NCR, Dubai...) to one row per restaurant with overall, dining and delivery ratings and review counts, cost for two, cuisines, locality, address, newly-opened and promoted flags, link and photo. For new-opening and rating watches.

- **URL**: https://apify.com/superslowsloth/zomato-restaurants.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** Travel, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 restaurant scrapeds

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 scraper: restaurant ratings, prices and new openings by city

This Zomato scraper reads [Zomato](https://www.zomato.com)'s restaurant list for
each city you name (Mumbai, Bangalore, Delhi NCR, Pune, Dubai...) and returns
one flat row per restaurant: the overall, **dining and delivery ratings** with
their review counts, **cost for two** (in INR, or AED in the UAE), cuisines,
locality and address, whether Zomato badges it **Newly Opened**, whether the
listing is a paid placement, the link and the photo. Run it on a schedule and
diff the dataset to get a **per-city restaurant feed**: which restaurants opened
this week, which ratings moved, which places changed price band. Built for F\&B
suppliers, delivery and market analysts, and anyone tracking a city's dining
scene.

No account, no API key, no login, no browser. One request returns 12
restaurants.

### Input

| Field | Notes |
|---|---|
| `cities` | Zomato city slugs, one per line, as in the site's links (`zomato.com/mumbai`): `mumbai`, `bangalore`, `ncr` (Delhi NCR), `pune`, `hyderabad`, `chennai`, `kolkata`, `goa`, `jaipur`, `ahmedabad`, `chandigarh`, `lucknow`, `kochi`, `indore`, `dubai`, `abudhabi` and others. A zomato.com city link works too. Case does not matter. A slug Zomato does not know (`delhi`, `delhi-ncr`: Delhi is `ncr`) fails for that city at no charge and the run goes on. |
| `maxItems` | Restaurants to read per city (default 100, up to 500), in Zomato's own Popularity order. See "How deep it goes" below for why 500. |
| `onlyNewlyOpened` | Return only restaurants Zomato badges "Newly Opened" (`is_newly_opened`). The others are read, not returned and not charged, and do not count toward `maxItems`. Expect around a dozen per city. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input:

```json
{
  "cities": ["mumbai", "bangalore", "ncr"],
  "maxItems": 500,
  "onlyNewlyOpened": false
}
```

### Output

| Field | Notes |
|---|---|
| `city`, `city_name`, `country` | The slug you asked for, and the city and country as Zomato names them (`ncr` is `Delhi NCR`, `bangalore` is `Bengaluru`). |
| `position` | Where the restaurant sat in the city's list, 1 being first, counted over every listed entry of the pages read (promoted entries included, in place). |
| `res_id` | Zomato's restaurant id, a number, **the unique key**. A restaurant listed twice is returned once. |
| `name`, `url` | As shown, and the link to the restaurant's Zomato page. |
| `locality`, `address` | The locality (`Lower Parel, Mumbai`) and street address as shown. |
| `cuisines` | Cuisine names in Zomato's order (empty list if none). |
| `cost_for_two`, `cost_for_one` | Zomato's cost estimates as whole numbers in `currency`, from text such as `₹1,900 for two`. |
| `currency` | `INR` for Indian cities, `AED` for Dubai and Abu Dhabi. |
| `cost_text` | The cost-for-two text exactly as shown. |
| `rating`, `rating_label` | The overall rating (1 to 5) and Zomato's word for it (`Excellent`, `Very Good`). **`rating` is null for a restaurant Zomato shows as "Not rated"**: it sends 0, which is not a rating of 0. |
| `votes` | How many reviews the overall rating is from: the dining and delivery counts added up. |
| `dining_rating`, `dining_reviews` | The dine-in rating and its review count. |
| `delivery_rating`, `delivery_reviews` | The delivery rating and its review count. A rating is **null** when Zomato shows none (too few reviews, or a place that does not deliver or seat); its count is still the real count, often 0. |
| `rounded_counts` | Which of `votes`, `dining_reviews`, `delivery_reviews` Zomato sent abbreviated. Popular places show `10.8K`, which is stored as 10800 and is accurate only to the digits shown; this field says so instead of passing the rounding off as an exact count. Empty when every count was exact. |
| `is_newly_opened` | True when Zomato badges the restaurant "Newly Opened". |
| `is_promoted` | True for a paid placement ("Promoted"). Zomato mixes them into the list, most heavily on the first pages (11 of the first 12 in Mumbai, 50 of 500 overall). |
| `image_url` | The restaurant's featured photo. |

Example row (real values, Mumbai, 2026-10-21):

```json
{
  "city": "mumbai",
  "city_name": "Mumbai",
  "country": "India",
  "position": 15,
  "res_id": 39528,
  "name": "Cream Centre - Vegetarian Legacy Since 1958",
  "url": "https://www.zomato.com/mumbai/cream-centre-vegetarian-legacy-since-1958-lower-parel/info",
  "locality": "Lower Parel, Mumbai",
  "address": "3rd Floor, Phoenix Palladium, Opposite PVR, Senapati Bapat Marg, Lower Parel, Mumbai",
  "cuisines": ["Street Food", "North Indian", "Chinese", "Biryani", "Mexican", "Pizza", "Pasta", "Italian"],
  "cost_for_two": 1900,
  "cost_for_one": 800,
  "currency": "INR",
  "cost_text": "₹1,900 for two",
  "rating": 4.3,
  "rating_label": "Very Good",
  "votes": 10800,
  "dining_rating": 4.4,
  "dining_reviews": 8237,
  "delivery_rating": 4.2,
  "delivery_reviews": 2570,
  "rounded_counts": ["votes"],
  "is_newly_opened": false,
  "is_promoted": true,
  "image_url": "https://b.zmtcdn.com/data/pictures/8/39528/aa1c00e0b7ce2d0a3c011cab92293462_featured_v2.jpg"
}
```

#### Honest nulls

A field Zomato did not send is `null`, never `0` or `""`. A count Zomato
sends as 0 is 0. `cuisines` and `rounded_counts` are empty lists when there are
none.

- **No opening hours, phone, menu or reviews text.** The city list does not
  carry them; this is the list, one row per restaurant.
- **No opening date.** Zomato's "Newly Opened" badge is a flag with no date; diff
  scheduled runs to date an opening yourself.
- **No distance.** The list's distance is measured from the city's default
  point, not from anywhere you chose, so it is left out.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00049** per restaurant row written (`restaurant-scraped`), i.e. $0.49 per
  1,000 restaurants. A row that is not written is not charged: restaurants
  filtered by `onlyNewlyOpened`, repeats, and cities Zomato does not know are free.

### How it fetches

It reads the same web routes zomato.com's own city page uses: the city page
data (for the city's location), a csrf token, then the restaurant search, 12 at
a time, in Zomato's default Popularity order. Measured 2026-10-21: HTTP 200 JSON
from Apify datacenter addresses (3 of 3) and from a plain connection, no account
needed. A refusal (403, 429, 5xx), a 200 without the list data, a rejected token
(401) or a dropped connection is treated as the address being blocked: the actor
rotates to a fresh address, fetches a new token (the token is tied to the
session's cookies) and retries.

Things worth knowing, all measured:

- **How deep it goes: about 500 restaurants per city.** Zomato says Mumbai has
  21,637 restaurants and Bangalore 19,128, but its list stops after about 500
  however it is paged (Mumbai and Shimla both ended after the 501st entry), so
  at most 500 per city are offered. Reading a whole city's list (42 pages)
  took 26 seconds from a test machine. The site's own total is logged, not used.
- **The end of the list.** The page after the last answers with no restaurants
  while still saying there are more, so the actor stops on the empty page.
- **No sort.** Zomato's sort options sort only the first 12 and then return
  nothing, so none is offered; the order is Zomato's Popularity order.
- **Unknown cities.** For a slug it does not know, Zomato answers its "404"
  page carrying the location of the previous city the session looked at. The
  actor recognises that page and fails the city rather than return another
  city's restaurants under its name.
- **Duplicates.** One Mumbai walk listed one restaurant twice in 501 entries;
  it is returned once.

If Zomato changes its page data, the run fails with a message instead of
returning empty rows.

# Actor input Schema

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

Zomato city slugs, one per line, as in the site's links (zomato.com/mumbai): for example mumbai, bangalore, ncr (Delhi NCR), pune, hyderabad, chennai, kolkata, goa, jaipur, dubai or abudhabi. A zomato.com city link is accepted too. A slug Zomato does not know (for example delhi; Delhi is ncr) fails for that city at no charge and the run goes on. A restaurant listed twice is returned once.

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

Read at most this many restaurants for each city, in Zomato's own Popularity order, 12 per page. Zomato lists at most about 500 restaurants per city however many it says it has, so up to 500 are accepted.

## `onlyNewlyOpened` (type: `boolean`):

Return only restaurants that Zomato badges "Newly Opened" (the is_newly_opened field). Other restaurants are read but not returned and not charged, and do not count toward Max restaurants. When this actor was built 11 of the 501 listed in Mumbai and 12 of the 501 in Shimla carried the badge, so a city yields around a dozen.

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

Use Apify's automatic datacenter proxy (the default). zomato.com answered Apify datacenter addresses with HTTP 200 when this actor was built (3 of 3), while the residential group was refused once in three by Zomato's firewall. A refused request is retried from a fresh address.

## Actor input object example

```json
{
  "cities": [
    "mumbai"
  ],
  "maxItems": 100,
  "onlyNewlyOpened": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/zomato-restaurants").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"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/zomato-restaurants").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"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/zomato-restaurants --silent --output-dataset

```

## MCP server setup

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

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/FfhWMN5lc7mH4u6jf/builds/k8CnJQneF7zipbfDy/openapi.json
