Food Delivery Promotions Data: Track Discounts, BOGO, and Free Delivery on DoorDash, Uber Eats, and Grubhub Get The Full Insight ✕

Web Scraping Zomato: A Step-by-Step Python Guide to Restaurants, Menus & Ratings (2026)

Web Scraping Zomato A Step-by-Step Python Guide to Restaurants, Menus & Ratings (2026)

Zomato web scraping is simpler than most tutorials make it look. To build a Zomato scraper, request a public restaurant page with Python, then read two blocks of structured data that Zomato embeds in the page: a schema.org JSON-LD block with the restaurant’s name, address, cuisine, price range, and rating, and a larger JSON object with the full menu and prices. This works with requests and BeautifulSoup, doesn’t need a headless browser, and breaks far less often than parsing CSS class names.

This guide walks through that method step by step with working code, then covers the questions most tutorials skip: whether Zomato has an API, what’s legal, and what changes when you need thousands of restaurants instead of ten.

Key takeaways

  • Zomato has no public API for restaurant or menu data. Its developer platform today is for restaurant partners integrating their point-of-sale systems.
  • The data is already in the page. Restaurant pages carry JSON-LD and an embedded menu object, so you can parse JSON instead of fragile HTML classes.
  • Scraping HTML class names is how most Zomato tutorials break. Older guides that target class names like .search-result have likely stopped working.
  • Stay with public data, go slowly, and read Zomato’s terms. Automated access is restricted, and personal data is off-limits.
  • Zomato is now an India-focused platform under its parent company, Eternal Ltd, so plan your coverage around Indian cities.

What data can you scrape from Zomato?

When you extract Zomato restaurant data, these are the fields available and where each one sits in the page:

Data typeFieldsWhere it sits in the page
Restaurant profileName, address, locality, cuisines, cost for two / price range, imageJSON-LD block
RatingsAggregate rating, rating count (dining and delivery ratings can differ)JSON-LD and embedded page data
MenuCategories, item names, descriptionsEmbedded page data
PricingItem prices, offers and discounts shown on the pageEmbedded page data
AvailabilityDelivery availability, opening status, delivery-time estimateEmbedded page data
ReviewsPublic review text, reviewer ratings, datesReviews section of the page

Customer contact details, order history, and anything behind a login aren’t public, and they aren’t in scope.

Scraping and importing Zomato reviews

Zomato has no public reviews API. To import Zomato reviews into a spreadsheet, a CRM or a review widget on your website, you first extract them from the public restaurant pages. There are two common reasons to do it:

  • Your own restaurant’s reviews. Owners often want to show or analyse their own Zomato reviews in one place. Export the review text, star rating, and date to CSV, then import that file into your review tool, website widget or spreadsheet. Check first whether your review-management platform already offers a Zomato connector.
  • Competitors’ reviews for analysis. For sentiment and theme analysis across many restaurants, collect review text, ratings, and dates in bulk.

The method is usually the same as for menus: open a restaurant’s reviews page, view the source, and find the embedded data that holds the review text and ratings. The field names differ from the menu’s, so adjust find_menu_items() to look for review fields instead of name and price.

Either way, leave out personal data. Reviewer names, profile links, and photos identify real people, and India’s Digital Personal Data Protection Act applies to them. Review text, rating, and date are all the analysis needs. For a city-scale example, see how to scrape Zomato reviews for every restaurant in Bengaluru. For a ready-made feed, see FoodSpark’s review data API.

Does Zomato have an API?

Not one you can use for restaurant or menu data. Zomato’s old public developer API was retired years ago. What Zomato offers now is a POS integration platform that lets restaurant partners connect their point-of-sale systems to receive orders and manage their own menus. It doesn’t return other restaurants’ data.

That leaves two practical routes to Zomato market data: scrape public pages yourself, or use a Zomato food data scraping API such as FoodSpark’s Zomato API. For more background, see exploring the Zomato API.

Is it legal to scrape Zomato?

It depends on what you collect and how, so treat this as orientation rather than legal advice:

  1. Zomato’s terms restrict automated access such as bots and scrapers, and its robots.txt blocks many automated fetchers. For commercial projects, that’s a contract risk to weigh with your lawyer.
  2. Public business data is the lower-risk zone. Restaurant names, menus, prices, and aggregate ratings are published for anyone to see.
  3. Personal data is where India’s privacy law applies. The Digital Personal Data Protection Act, 2023 governs personal data, so avoid collecting reviewer profiles, phone numbers, or anything tied to an individual.

In practice: public pages only, no logins, slow request rates, and a process for takedown requests.

Where does Zomato operate in 2026?

Zomato is now an India-focused platform. Its parent company renamed itself Eternal Ltd in 2025, and Eternal also runs Blinkit (quick commerce) and District (going out). Zomato has wound down most of its international operations: it closed food delivery in the UAE in November 2022 and later shut down in Indonesia. For current Zomato data, plan coverage around Indian cities such as Delhi NCR, Bengaluru, Mumbai, Hyderabad, Pune and Chennai. For the wider market, see food delivery companies in India.

How Zomato restaurant pages carry their data

Open any Zomato restaurant page and view its source (Ctrl+U). Two things matter:

  1. A JSON-LD block (<script type="application/ld+json">). This is structured data Zomato publishes for search engines, following the schema.org Restaurant format: name, address, cuisine, price range, and aggregate rating, in clean JSON.
  2. An embedded page state (window.__PRELOADED_STATE__ = JSON.parse("...")). This is the data Zomato’s own front end uses to draw the page, including the full menu with item names, descriptions, and prices. It’s stored as an escaped JSON string, so it needs decoding twice.

Reading these blocks is more reliable than scraping visible HTML. Class names change whenever Zomato redesigns, but structured data has to stay machine-readable, so it changes much less often.

Three ways to get Zomato data

ApproachHow it worksStrengthsWeaknessesBest for
Parse HTML classes (older tutorials)Selects elements by CSS class, like .search-resultEasy to understandBreaks on every redesign; misses menusLearning only
Parse embedded JSON (this guide)Fetches the page, then reads the JSON-LD and embedded page stateFull menus and prices; no browser needed; more stableStill gets blocked at volume; needs a list of restaurant URLsResearch on tens to a few hundred restaurants
Managed data feed or APIA provider handles collection, city coverage, retries, change tracking and QAThousands of restaurants across cities, on a schedule, in a clean formatPaidOngoing price and menu monitoring, analytics, AI products

Step-by-step: scrape Zomato restaurants and menus with Python

Step 1 — Install the libraries.

pip install requests beautifulsoup4

Step 2 — Collect restaurant URLs.

Open Zomato, pick your city and area, and copy the restaurant pages you want. Use the page that ends in /order, which is the one that carries the delivery menu. Zomato shows only restaurants in the city you’ve selected, so keep a note of the city for each URL.

Step 3 — Run the script.

Paste your URLs into RESTAURANT_URLS and run it. It saves one row per menu item to zomato_menu.csv, with the restaurant’s details on every row.

import csv
import json
import random
import re
import time

import requests
from bs4 import BeautifulSoup

# Public Zomato restaurant pages, copied from your browser (the "order" page carries the menu).
RESTAURANT_URLS = [
    "https://www.zomato.com/bangalore/your-restaurant-name-area/order",
]

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/126.0 Safari/537.36",
    "Accept-Language": "en-IN,en;q=0.9",
}

# Matches: window.__PRELOADED_STATE__ = JSON.parse("...escaped JSON...")
STATE_RE = re.compile(r'__PRELOADED_STATE__\s*=\s*JSON\.parse\("((?:[^"\\]|\\.)*)"\)', re.S)


def restaurant_info(soup):
    """Read the restaurant's schema.org JSON-LD block (name, address, rating, price range)."""
    for tag in soup.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "")
        except json.JSONDecodeError:
            continue
        for block in data if isinstance(data, list) else [data]:
            if not isinstance(block, dict):
                continue
            kind = block.get("@type")
            kinds = kind if isinstance(kind, list) else [kind]
            if not {"Restaurant", "FoodEstablishment"} & set(kinds):
                continue
            address = block.get("address") or {}
            if isinstance(address, dict):
                address = ", ".join(
                    str(address[k]) for k in ("streetAddress", "addressLocality") if address.get(k)
                )
            rating = block.get("aggregateRating") or {}
            return {
                "restaurant": block.get("name", ""),
                "address": address,
                "cuisine": block.get("servesCuisine", ""),
                "price_range": block.get("priceRange", ""),
                "rating": rating.get("ratingValue", ""),
                "rating_count": rating.get("ratingCount") or rating.get("reviewCount", ""),
            }
    return {}


def preloaded_state(html):
    """Decode the JSON that Zomato embeds in the page for its own front end."""
    match = STATE_RE.search(html)
    if not match:
        return {}
    as_text = json.loads(f'"{match.group(1)}"')  # undo the JavaScript string escaping
    return json.loads(as_text)


def to_price(value):
    """Accept prices stored as numbers or as numeric strings."""
    if isinstance(value, (int, float)) and not isinstance(value, bool):
        return value
    if isinstance(value, str) and re.fullmatch(r"\d+(?:\.\d+)?", value.strip()):
        return float(value)
    return None


def find_menu_items(node, found):
    """Walk the page state and collect anything shaped like a menu item (a name plus a price)."""
    if isinstance(node, dict):
        name, price = node.get("name"), to_price(node.get("price"))
        if isinstance(name, str) and price:
            found.append({
                "item": name.strip(),
                "price": price,
                "description": (node.get("desc") or node.get("description") or "").strip(),
            })
        for value in node.values():
            find_menu_items(value, found)
    elif isinstance(node, list):
        for value in node:
            find_menu_items(value, found)
    return found


def scrape_restaurant(url):
    response = requests.get(url, headers=HEADERS, timeout=30)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, "html.parser")

    info = restaurant_info(soup)
    items = find_menu_items(preloaded_state(response.text), [])

    seen, rows = set(), []
    for item in items:
        key = (item["item"], item["price"])
        if key not in seen:
            seen.add(key)
            rows.append({**info, "url": url, **item})
    return rows


def main():
    all_rows = []
    for url in RESTAURANT_URLS:
        try:
            rows = scrape_restaurant(url)
            print(f"{len(rows)} menu items from {url}")
            all_rows.extend(rows)
        except Exception as exc:
            print(f"Skipped {url}: {exc}")
        time.sleep(random.uniform(5, 10))  # pause between restaurants

    fields = ["restaurant", "address", "cuisine", "price_range", "rating", "rating_count",
              "url", "item", "price", "description"]
    with open("zomato_menu.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(all_rows)
    print(f"Saved {len(all_rows)} rows to zomato_menu.csv")


if __name__ == "__main__":
    main()

Step 4 — What the script is doing:

  • restaurant_info() finds the JSON-LD block marked Restaurant and pulls the name, address, cuisine, price range, rating, and rating count.
  • preloaded_state() finds window.__PRELOADED_STATE__, undoes the JavaScript string escaping, and parses the JSON underneath.
  • find_menu_items() walks that JSON and keeps every object that has both a name and a price. Because it doesn’t depend on the exact nesting, it keeps working when Zomato reshuffles the structure.
  • Duplicates are removed because the same dish can appear in more than one menu section, and the script pauses 5–10 seconds between restaurants.

Step 5 — When it stops working:

  • 403 errors or an empty page: you’re being rate-limited. Wait, slow down and scrape fewer restaurants per run.
  • Restaurant details but no menu items: the embedded state has moved or been renamed. View the page source, search for PRELOADED_STATE, and adjust the pattern.
  • Prices don’t match the page: check a few items against the live page. Embedded data can hold original and discounted prices side by side, or amounts in paise rather than rupees, so confirm which field matches what customers see before you use the numbers.

What the output looks like

Illustrative rows showing the CSV structure; your restaurants and values will differ.

restaurantaddresscuisineratingitemprice
Example Kitchen12 MG Road, BengaluruNorth Indian4.2Paneer Tikka249
Example Kitchen12 MG Road, BengaluruNorth Indian4.2Dal Makhani279
Example Kitchen12 MG Road, BengaluruNorth Indian4.2Butter Naan60

Get Started

Get Zomato Data Without Building a Scraper

Need every restaurant in Bengaluru, Mumbai or Delhi NCR, refreshed weekly? FoodSpark delivers Zomato data at city scale.

Request a Free Sample
cta-bg

From ten restaurants to a whole city: what breaks

The script handles a list of restaurants you’ve picked. Market-level work, meaning every restaurant in a city, tracked over time, runs into problems a script like this doesn’t solve:

  1. Finding every restaurant. City and locality listing pages load more results as you scroll, so building a complete list of restaurant URLs needs a real browser or a lot of patient crawling.
  2. Rate limits. A few requests are fine. Thousands trigger blocks, empty pages, and silent failures, and those can quietly leave gaps in your data.
  3. Structure changes. When Zomato changes its embedded data, parsers need fixing. You want to find out from monitoring, not from a stakeholder asking why the numbers look wrong.
  4. Comparable data. Dishes, portion sizes, combos, and “cost for two” need normalising before you can compare restaurants, localities, or platforms such as Zomato and Swiggy.
  5. History. Pricing and menu analysis needs regular runs, stored snapshots and change detection, not a one-off CSV.

That’s the part FoodSpark handles. Our Zomato restaurant data scraping service and Zomato API deliver restaurant, menu, price, and rating data by city and locality, refreshed on your schedule, as CSV, JSON, or API. Need Swiggy data alongside it? See the Swiggy API.

What you can do with Zomato data

  • Menu pricing benchmarks. Compare your dish prices against nearby restaurants in the same cuisine and locality, and see where you sit on “cost for two.”
  • Cuisine gap analysis. Find localities where demand is high, but supply of a cuisine is thin before you open a kitchen. See how this works in finding cloud kitchen cuisine gaps with Swiggy data and scraping cloud kitchen data from Swiggy and Zomato.
  • Ratings and reputation tracking. Monitor rating changes across your own outlets and competitors’. For review-level work, see how to scrape Zomato reviews.
  • Offer and discount tracking. See how often competitors discount and by how much, so your own promotions are based on evidence.
  • Zomato vs Swiggy comparison. The same restaurant can price and position itself differently on each app. Pull both to see the full picture of India’s two largest delivery platforms.
  • Sales prospecting. Food-service suppliers and POS vendors use restaurant lists by locality, cuisine and rating to target their outreach.

For a no-code overview of the business uses, see our complete guide to Zomato data scraping.

Frequently asked questions

How do you scrape data from Zomato?

Request a public Zomato restaurant page with Python, then parse two embedded data blocks: the JSON-LD block (name, address, cuisine, price range, rating) and the window.__PRELOADED_STATE__ object (full menu with prices). requests and BeautifulSoup are enough; a headless browser isn’t required for restaurant pages.

Can I scrape Zomato with BeautifulSoup?

Yes. BeautifulSoup finds the script tags that hold Zomato’s structured data, and Python’s json module parses them. Scraping visible HTML by CSS class name also uses BeautifulSoup, but it breaks every time Zomato changes its design.

Does Zomato have a public API?

No. Zomato’s old public developer API was retired. Its current developer platform is for restaurant partners connecting point-of-sale systems, not for accessing other restaurants’ data. The alternatives are scraping public pages or using a data provider’s Zomato API.

Is it legal to scrape Zomato?

Zomato’s terms restrict automated access, so commercial scraping carries contract risk, and India’s Digital Personal Data Protection Act, 2023 applies to any personal data. Collecting public business data such as menus, prices and ratings at a slow rate is much lower risk than collecting personal information. This isn’t legal advice.

What data can I get from Zomato?

Restaurant names, addresses, localities, cuisines, cost for two, ratings and rating counts, full menus with item descriptions and prices, offers, delivery availability, and public reviews.

Why does my Zomato scraper return a 403 error or no data?

Usually because the request looks automated or you’ve sent too many too quickly. Send browser-like headers, slow down, and scrape fewer pages per run. If you get the page but no menu, the embedded data has changed shape and your parser needs updating.

How do I scrape all restaurants in a city on Zomato?

First, build a list of restaurant URLs from the city’s listing pages, which load more results as you scroll, then run each URL through a restaurant-page scraper. For complete, repeated city coverage, a managed feed is usually cheaper than maintaining it yourself.

Which cities does Zomato cover in 2026?

Zomato is focused on India today, having wound down most of its international operations, including food delivery in the UAE and its Indonesia business. Coverage is deepest in major metros such as Delhi NCR, Bengaluru, Mumbai, Hyderabad, Pune, and Chennai.

Is there a Zomato scraper API or data extractor I can use instead of writing code?

Yes, though not from Zomato itself. There’s no official API for this data, but there are third-party options: scraping APIs that fetch pages for you, no-code scraper tools, and managed providers that deliver finished datasets. FoodSpark’s Zomato API returns restaurant, menu, price, and rating data by city, so you don’t have to maintain a scraper.

Can I import Zomato reviews into a spreadsheet or my website?

Not through an official Zomato reviews API, because there isn’t a public one. Extract the public reviews (text, rating and date) from the restaurant pages into a CSV, then import that file into your spreadsheet, CRM or review widget. Leave out reviewer names and profile details.

Conclusion

The reliable way to scrape Zomato in 2026 is to stop parsing class names and read the structured data Zomato already puts in every restaurant page: JSON-LD for the restaurant profile and the embedded page state for the menu. With requests, BeautifulSoup and the script above, you can go from a list of restaurant URLs to a clean CSV of dishes and prices in minutes.

When the list becomes “every restaurant in Bengaluru, refreshed every week,” the work shifts from writing a script to running infrastructure. FoodSpark takes that on and delivers structured Zomato data by city and locality, with a free sample to check the quality first.

Tracking India’s food delivery market more broadly? See our India food data API and the Swiggy API.

Table of Contents

Need Custom Food Data?
  • Custom Food Data Solutions
  • Choose Platforms, Fields, Formats
  • Flexible Data Delivery Formats

Web Scraping Zomato: A Step-by-Step Python Guide to Restaurants, Menus & Ratings (2026)

Web Scraping Zomato A Step-by-Step Python Guide to Restaurants, Menus & Ratings (2026)

Table of Contents

Explore Our Latest Insights

Food Delivery Promotions Data: Track Discounts, BOGO and Free Delivery on DoorDash, Uber Eats and Grubhub

Food Delivery Promotions Data: Track Discounts, BOGO, and Free Delivery on DoorDash, Uber Eats, and Grubhub

Swiggy and Zomato Offers Data: How to Track Restaurant Discounts by Pincode (2027)

Swiggy and Zomato offers data shows which discounts restaurants run, where they run them, and for how long. Because each...

Read more

Restaurant Promotion Tracking 2027: How QSR Chains Monitor Competitor Deals

Restaurant promotion tracking means recording competitors’ deals, discounts, and limited-time offers across every channel. For QSR chains, it has become...

Read more