# Google Maps Menu Scraper (`rainminer/google-maps-menu-scraper`) Actor

AI-based structured extraction of restaurant menu data from Google Maps — item names, descriptions, prices, categories, and languages from menu photos at scale. Built for food delivery platforms, price intelligence, competitor menu research, dietary databases, and hospitality data pipelines.

- **URL**: https://apify.com/rainminer/google-maps-menu-scraper.md
- **Developed by:** [rainminer](https://apify.com/rainminer) (community)
- **Categories:** Developer tools, E-commerce, AI
- **Stats:** 28 total users, 13 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.90 / 1,000 menu items

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/platform/actors/running/actors-in-store#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

The **Google Maps Menu Scraper** is an Apify Actor that extracts structured menu data from restaurant and café listings on Google Maps. Provide place URLs, Place IDs, or search queries — get back item names, descriptions, prices, and categories in a flat dataset.

> **Important — AI output is not production-ready as-is.**\
> This Actor uses AI vision to read menu photos. Results can include misread names, wrong prices, invented or missing items, and inconsistent formatting. Treat the dataset as raw input for **your own data cleaning / QA pipeline** (confidence filters, validation against source photos, human review, etc.) before any production use.

***

### Key Features

- **Menu photo extraction**: Reads uploaded menu photos when no structured menu tab is available.
- **Text search**: Find places by keyword (e.g. `"brunch austin"`) — no manual URL collection required.
- **Language detection**: Each item carries an ISO 639-1 language code detected from the item text itself.
- **Source metadata**: Each item records the source menu photo URL and when it was uploaded.
- **Deduplication**: Items appearing across multiple menu photos are merged, keeping the most recent version.
- **Multilingual**: Preserves original item names and descriptions in any language.

***

### Why Scrape Google Maps Menus?

Google Maps is the most comprehensive public source of restaurant information, but menu data is typically locked in uploaded images. This Actor unlocks that data for:

- **Food delivery and aggregator platforms** building comprehensive menu databases.
- **Price intelligence** tracking menu price changes over time across competitors.
- **Restaurant research** comparing offerings across similar establishments in a city.
- **Dietary and allergy databases** that need structured item names and descriptions.
- **Travel and tourism apps** showing visitors what to expect before dining.

***

### Who Is It For?

- **Food-tech startups** that need affordable, scalable menu data.
- **Restaurant chains** auditing competitor menus across locations.
- **Market researchers** studying pricing trends in the hospitality sector.
- **Developers** building culinary discovery apps or dining recommendation systems.

***

### Input Schema

```json
{
  "startUrls": [
    { "url": "https://www.google.com/maps/place/Mercanti+Pizzeria+%26+So./..." }
  ],
  "placeIds": ["ChIJZ2rw6ac5qBQRKgoWMAqIY3E"],
  "textQueries": ["brunch austin", "pizza in New York"],
  "maxMenuImages": 1,
  "maxItems": 100,
  "maxPlacesPerQuery": 5,
  "menuLanguage": "en"
}
```

**At least one of `startUrls`, `placeIds`, or `textQueries` must be provided.**

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | Array | — | Google Maps place page URLs or `maps.app.goo.gl` short links |
| `placeIds` | Array of strings | — | Google Maps Place IDs starting with `ChIJ` |
| `textQueries` | Array of strings | — | Free-text search queries (e.g. `"brunch austin"`) |
| `maxPlacesPerQuery` | Integer | `5` | Max places to scrape per text query (up to 60) |
| `maxMenuImages` | Integer | `1` | Max menu photos to process per place |
| `maxItems` | Integer | `100` | Max unique menu items to output per place after deduplication |
| `menuLanguage` | String | `"en"` | ISO 639-1 language code for menu extraction (e.g. `en`, `es`, `bg`, `fr`, `de`, `it`). |

***

### Output Schema

Each dataset item represents one unique menu item extracted from a place:

```json
{
  "placeId": "ChIJN1t2JNayGGARRwe9bMh9diY",
  "placeName": "Mercanti Pizzeria & So.",
  "placeUrl": "https://www.google.com/maps/place/Mercanti+Pizzeria+%26+So./...",
  "category": "Pizzas",
  "name": "Margherita",
  "description": "Tomato sauce, fior di latte mozzarella, fresh basil",
  "price": 14,
  "currency": "USD",
  "priceText": "$14.00",
  "language": "en",
  "confidence": 0.92,
  "menuImageUrl": "https://lh3.googleusercontent.com/gps-cs-s/...",
  "scrapedAt": "2026-05-31T04:49:29.132Z"
}
```

| Field | Description |
|---|---|
| `placeId` | Google Maps Place ID (`ChIJ…`) |
| `placeName` | Display name of the restaurant |
| `placeUrl` | Google Maps place URL |
| `category` | Menu section header (e.g. `"Pizzas"`, `"Desserts"`) — `null` if not visible |
| `name` | Item name in original language |
| `description` | Item description or ingredients — `null` if absent |
| `price` | Parsed numeric price without currency symbol (e.g. `8.99`) — `null` if absent |
| `currency` | ISO 4217 currency code (e.g. `"EUR"`, `"BGN"`) — `null` if indeterminate |
| `priceText` | Original price text as shown in the photo (e.g. `"€8.99"`) — `null` if absent |
| `language` | ISO 639-1 language code of the item text (e.g. `"en"`, `"bg"`, `"it"`) |
| `confidence` | Model confidence that the item was read correctly (`0`–`1`). Filter out low scores if you only want high-certainty rows. |
| `menuImageUrl` | High-resolution URL of the source menu photo |
| `scrapedAt` | ISO timestamp of when this item was scraped |

***

### How It Works

1. **Input** — provide `startUrls`, `placeIds`, and/or `textQueries`.
2. **Extract** — the Actor collects menu photos for each place and reads visible items.
3. **Output** — one dataset row per unique menu item, with place details and price fields.

***

### Pricing

This Actor uses pay-per-event pricing with three events (see the Store pricing tab for current rates):

| Event | When charged |
| --- | --- |
| `place-scraped` | Once per place looked up for menu photos (including places with none) |
| `menu-photo-analyzed` | Once per menu photo sent to AI analysis (capped by `maxMenuImages`) |
| `result-item` | Once per unique menu item written to the dataset |

**Budgeting tips**

- **`place-scraped` is charged for every place you submit**, whether or not menu photos exist. Looking up a place has a real cost even when there is nothing to extract — places with no menu photos still incur this event (and produce no further charges).
- **`result-item` is usually the dominant cost** for places with long menus. Real cost per place varies far more than place+photo alone suggests; long menus cost more because they return more dataset rows.
- A per-place average above place+photo alone almost always means `result-item` charges — not extra photos beyond `maxMenuImages`.

**Example (100 places, `maxMenuImages: 1`):** `place-scraped` alone is about `$0.69` across the whole batch. Places that also have a photo add `menu-photo-analyzed`; if the run writes ~400–1,100 menu items, `result-item` adds roughly `$0.35–$1.00` on top.

***

### Notes and Limitations

- **AI-powered extraction**: This Actor uses AI to identify menu photos and extract items. Output is **non-deterministic** and **not ready for production use without your own cleaning pipeline** — the same place may produce different results across runs, and the model can misclassify photos or misread names, prices, and descriptions. Use the `confidence` field and your own validation before shipping data downstream. You may still be charged for photos that are analyzed even when no usable menu items are returned.
- **Photo availability**: Results depend on user-uploaded photos. Places with no menu photos produce no output.
- **Photo quality**: Handwritten or low-quality menu photos may yield incomplete extractions.
- **Menu language**: Set `menuLanguage` to the primary script of the menus you are scraping for best results with non-Latin alphabets (Cyrillic, Arabic, Chinese, etc.).
- **Batch size & timeout**: Plan on roughly **10–12 minutes per 100 places** (`maxMenuImages: 1`). Prefer batches of about **100–150 places** per run, or raise the run timeout (default is 2 hours). Results are saved per place as soon as that place finishes, so a timeout still keeps partial output instead of discarding everything.

# Actor input Schema

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

Google Maps place URLs or maps.app.goo.gl short links.

## `placeIds` (type: `array`):

Google Maps Place IDs (e.g. ChIJZ2rw6ac5qBQRKgoWMAqIY3E). Will be resolved to place pages.

## `maxMenuImages` (type: `integer`):

Maximum number of menu photos to fetch and analyze per place. Caps menu-photo-analyzed billing for that place.

## `textQueries` (type: `array`):

Free-text queries to find places (e.g. "brunch austin", "pizza in New York").

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

Maximum number of places to scrape per text query (up to 60).

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

Maximum number of unique menu items to output per place after deduplication.

## `menuLanguage` (type: `string`):

ISO 639-1 language code for menu extraction (e.g. en, es, bg). When set, only items in this language are extracted — useful for bilingual menus that would otherwise produce duplicates.

## Actor input object example

```json
{
  "placeIds": [
    "ChIJZ2rw6ac5qBQRKgoWMAqIY3E"
  ],
  "maxMenuImages": 1,
  "maxPlacesPerQuery": 5,
  "maxItems": 100,
  "menuLanguage": "en"
}
```

# Actor output Schema

## `overview` (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 = {
    "placeIds": [
        "ChIJZ2rw6ac5qBQRKgoWMAqIY3E"
    ],
    "maxMenuImages": 1,
    "menuLanguage": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("rainminer/google-maps-menu-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 = {
    "placeIds": ["ChIJZ2rw6ac5qBQRKgoWMAqIY3E"],
    "maxMenuImages": 1,
    "menuLanguage": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("rainminer/google-maps-menu-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "placeIds": [
    "ChIJZ2rw6ac5qBQRKgoWMAqIY3E"
  ],
  "maxMenuImages": 1,
  "menuLanguage": "en"
}' |
apify call rainminer/google-maps-menu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=rainminer/google-maps-menu-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/8GGZ9N7j2JhqnQ3oT/builds/auq3OHaou5z1hO2e6/openapi.json
