# Google Maps Restaurant Menu & Popular Dish Extractor (`dataspryva/google-maps-restaurant-menu-and-popular-dish`) Actor

Extract a restaurant's menu items from its Google Maps place page - dish name, photo, Popular badge, menu section, and an external menu link when the business has one.

- **URL**: https://apify.com/dataspryva/google-maps-restaurant-menu-and-popular-dish.md
- **Developed by:** [Dataspryva](https://apify.com/dataspryva) (community)
- **Categories:** Automation, Travel, E-commerce
- **Security:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

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

## Google Maps Restaurant Menu & Price Scraper

Scrape restaurant menus from **Google Maps** and the restaurant's own public menu page. Paste one or many place URLs, or search by cuisine and city, and get one row per dish with its section, description, price, currency, photo, menu provenance, and the full place context — address, rating, review count, categories, website and coordinates. No login, API key or proxy setup is required.

### What this actor does

- **Two modes:** `byPlace` (Maps URL, short link, Place ID or feature ID) and `bySearch` (cuisine + city query)
- **Batch-ready:** process many restaurants in one run, with a separate item cap per place
- **Dish-level data:** menu section, item name, description, price text, numeric price and currency
- **Menu provenance:** `menuSource`, `menuProvider`, the menu URL and the sections found for each restaurant
- **Place context on every row:** address, rating, review count, price level, categories, website, latitude and longitude
- **Honest no-menu rows:** when a place has no readable menu, one summary row records the place instead of inventing items
- Empty or unavailable fields are omitted from each row rather than sent as `null`

### Output per menu item (`recordType: "menuItem"`)

- `businessName`, `placeId`, `featureId`, `placeUrl`
- `placeAddress`, `placeRating`, `placeReviewCount`, `placePriceLevel`
- `placeCategories[]`, `placeWebsite`, `latitude`, `longitude`
- `menuUrl`, `menuCategories[]` — the menu page and the sections detected on it
- `menuSource` — `menuPage` when the restaurant's own menu supplied the items, `google` when Google's inline menu did
- `menuProvider` — the menu site or provider label, when identifiable
- `menuSection` — the dish's section, e.g. `Antipasti` or `Desserts`
- `itemName`, `itemDescription`
- `priceText` — the price exactly as published, e.g. `$24.95`, `€12,50`, `12.00 USD`
- `price` — numeric value when the price is unambiguous
- `currency` — ISO code when the page states it or the symbol is unambiguous
- `itemImage` — dish photo when the menu publishes one
- `isPopular` — `true` only when Google's inline menu shows a Popular badge
- `rank` — 1-based position in the collected menu
- `totalItems` — rows returned for this restaurant
- `menuFound: true`, `recordType: "menuItem"`, `sourceUrl`, `scrapedAt`

### Output per place without a readable menu (`recordType: "menuSummary"`)

- The same place context fields and `menuUrl` when one was found
- `menuFound: false`, `menuSource: "none"`
- `itemCount: 0`, `totalItems: 0`
- `recordType: "menuSummary"`, `sourceUrl`, `scrapedAt`

When `includePopularDishes` is enabled and Google exposes Popular badges, an additional summary row carries `popularDishes[]` beside the item rows.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `byPlace` | `byPlace` or `bySearch` |
| `placeUrls` | array | – | Maps URLs, `maps.app.goo.gl` links, `ChIJ…` Place IDs or `0x…:0x…` feature IDs (mode=byPlace) |
| `searchQueries` | array | – | Cuisine + city queries, e.g. `italian restaurant, New York` (mode=bySearch) |
| `maxPlacesPerQuery` | integer | `20` | Restaurants to process per search query (1–120, mode=bySearch) |
| `maxItemsPerPlace` | integer | `100` | Maximum menu rows per restaurant (1–1000) |
| `language` | string | `en` | Language for Maps lookups and search results |
| `includePlacesWithoutMenu` | boolean | `true` | Return a summary row for places with no readable menu |
| `includePopularDishes` | boolean | `false` | Also check Google's inline menu for Popular badges (slower) |

#### Example: several restaurants by URL

```json
{
  "mode": "byPlace",
  "placeUrls": [
    "https://www.google.com/maps/place/Sicily+Osteria/@40.7601601,-73.9891863,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25932322b79d3:0x7c085aeae6751161!8m2!3d40.7601601!4d-73.9891863",
    "https://www.google.com/maps/place/?q=place_id:ChIJxZXYx7cEdkgRdgAOZ6OHOJw"
  ],
  "maxItemsPerPlace": 50
}
```

#### Example: search by cuisine and city

```json
{
  "mode": "bySearch",
  "searchQueries": ["italian restaurant, New York", "sushi in Austin"],
  "maxPlacesPerQuery": 10,
  "maxItemsPerPlace": 25,
  "includePlacesWithoutMenu": true
}
```

#### Example: place IDs only

```json
{
  "mode": "byPlace",
  "placeUrls": ["ChIJ03krMjJZwokRYRF15upaCHw"],
  "maxItemsPerPlace": 100,
  "language": "en"
}
```

#### Example: non-English menu lookup

```json
{
  "mode": "byPlace",
  "placeUrls": ["https://www.google.com/maps/place/?q=place_id:ChIJxZXYx7cEdkgRdgAOZ6OHOJw"],
  "language": "de",
  "maxItemsPerPlace": 30
}
```

### Use cases

- **Menu intelligence** — track dish names, sections and prices across a city or chain
- **Delivery and food-tech catalogues** — seed dish data with descriptions and price text
- **Restaurant lead enrichment** — attach menu coverage, website and rating to a place list
- **Market research** — compare cuisine pricing and menu depth across neighbourhoods
- **Hospitality monitoring** — detect menu changes, new sections and removed dishes over time

### FAQ

**Where does the menu data come from?**
From Google Maps place data plus the restaurant's own public menu page when it publishes one. The actor reads the menu items the restaurant publishes, not an AI guess.

**Why is there no menu for some restaurants?**
Not every restaurant publishes a structured menu. Coverage in tested cities is roughly 60–80%; places that publish only a PDF, an image, or a login-gated ordering page may not expose readable item rows. When `includePlacesWithoutMenu` is on, those places still produce one summary row so nothing is silently dropped.

**Why do some items have no price or no description?**
Many menus publish dish names without prices, or prices without descriptions. The actor reports what the page actually contains. `priceText` preserves the published price even when it cannot be interpreted as a single number, and `price`/`currency` are omitted when the value is ambiguous (for example a price range).

**What is the difference between `menuSource` and `menuProvider`?**
`menuSource` tells you whether the rows came from `menuPage` or Google's inline menu (`google`). `menuProvider` names the menu site or platform when it can be identified, which helps when you compare the same chain across locations.

**What happens when `maxItemsPerPlace` is smaller than the published menu?**
Rows are returned in menu order up to the cap, and `totalItems` states how many rows this run returned for that place. Increase the cap to collect more.

**Can I scrape many restaurants in one run?**
Yes. Use several `placeUrls` entries in `byPlace`, or one or more `searchQueries` in `bySearch`. Places are deduplicated by Place ID or feature ID before menus are fetched.

**Do I need a proxy or a Google API key?**
No. The actor works without a key, login or proxy setup. It automatically chooses a working connection and only escalates when a source blocks the direct request.

**How fresh is the data?**
Each run reads the current public menu and place data; nothing is cached between runs. Menu changes appear on the next execution.

**Is this affiliated with Google?**
No. This is an independent third-party actor that reads public Google Maps place data and public restaurant menu pages.

# Actor input Schema

## `mode` (type: `string`):

How to choose restaurants: paste place URLs/IDs or run a Google Maps search query.

## `placeUrls` (type: `array`):

One restaurant per line (mode=byPlace). Accepts Google Maps place URLs, short maps.app.goo.gl links, canonical ChIJ Place IDs, and 0x…:0x… feature IDs.

## `searchQueries` (type: `array`):

Cuisine + city queries, e.g. `italian restaurant, New York` or `sushi in Austin` (mode=bySearch). Each query returns up to Max places per query.

## `maxPlacesPerQuery` (type: `integer`):

Maximum restaurants to process from each search query (mode=bySearch).

## `maxItemsPerPlace` (type: `integer`):

Maximum menu rows to return for each restaurant.

## `language` (type: `string`):

Language used for Google Maps place lookups and search results.

## `includePlacesWithoutMenu` (type: `boolean`):

Return one summary row for a restaurant whose menu cannot be read, so every input place is accounted for.

## `includePopularDishes` (type: `boolean`):

Also check Google's own inline menu when it is available and mark dishes Google labels Popular. This uses a browser and is slower.

## Actor input object example

```json
{
  "mode": "byPlace",
  "placeUrls": [
    "https://www.google.com/maps/place/Sicily+Osteria/@40.7601601,-73.9891863,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25932322b79d3:0x7c085aeae6751161!8m2!3d40.7601601!4d-73.9891863"
  ],
  "searchQueries": [],
  "maxPlacesPerQuery": 1,
  "maxItemsPerPlace": 3,
  "language": "en",
  "includePlacesWithoutMenu": true,
  "includePopularDishes": false
}
```

# Actor output Schema

## `menuItems` (type: `string`):

Dataset containing menu item rows and one summary row per place without a readable menu.

# 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 = {
    "mode": "byPlace",
    "placeUrls": [
        "https://www.google.com/maps/place/Sicily+Osteria/@40.7601601,-73.9891863,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25932322b79d3:0x7c085aeae6751161!8m2!3d40.7601601!4d-73.9891863"
    ],
    "maxPlacesPerQuery": 1,
    "maxItemsPerPlace": 3,
    "language": "en",
    "includePlacesWithoutMenu": true,
    "includePopularDishes": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataspryva/google-maps-restaurant-menu-and-popular-dish").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 = {
    "mode": "byPlace",
    "placeUrls": ["https://www.google.com/maps/place/Sicily+Osteria/@40.7601601,-73.9891863,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25932322b79d3:0x7c085aeae6751161!8m2!3d40.7601601!4d-73.9891863"],
    "maxPlacesPerQuery": 1,
    "maxItemsPerPlace": 3,
    "language": "en",
    "includePlacesWithoutMenu": True,
    "includePopularDishes": False,
}

# Run the Actor and wait for it to finish
run = client.actor("dataspryva/google-maps-restaurant-menu-and-popular-dish").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 '{
  "mode": "byPlace",
  "placeUrls": [
    "https://www.google.com/maps/place/Sicily+Osteria/@40.7601601,-73.9891863,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25932322b79d3:0x7c085aeae6751161!8m2!3d40.7601601!4d-73.9891863"
  ],
  "maxPlacesPerQuery": 1,
  "maxItemsPerPlace": 3,
  "language": "en",
  "includePlacesWithoutMenu": true,
  "includePopularDishes": false
}' |
apify call dataspryva/google-maps-restaurant-menu-and-popular-dish --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataspryva/google-maps-restaurant-menu-and-popular-dish"
        }
    }
}
```

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/tXaH53j6e7DJ08To5/builds/afN7oVipaRHfge5Pm/openapi.json
