# Simple Google Maps Scraper (`ecommerce_leads/simple-google-maps-scraper`) Actor

Simple Google Maps Scraper

- **URL**: https://apify.com/ecommerce\_leads/simple-google-maps-scraper.md
- **Developed by:** [Monster Leads](https://apify.com/ecommerce_leads) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 place from searches

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

## Simple Google Maps Scraper

**Extract business data from Google Maps — names, phone numbers, websites, ratings, review counts, opening hours, coordinates and 17 more fields.**

Two things, done well:

| | What it does | Returns |
|---|---|---|
| 🔍 **Search** | Find places matching a query, anywhere in the world | Up to **20 places** per page, ~100 per query |
| 📍 **Place lookup** | Get one specific place from its CID or Maps URL | **1 place**, plus up to 3 reviews |

No browser, no rendering, no scrolling — a search finishes in **about 8 seconds** and a place lookup in **under 5**. You pay per result, and results that come back empty cost nothing.

***

### Quick start

1. Pick a **mode** — Search or Place lookup.
2. Type a **query** like `Coffee shops in London`.
3. Click **Start**.

That is the whole setup. Everything else is optional.

***

## 🔍 Search

Find every place matching a query in a given area.

### Input

Only **Search query** is required.

```json
{
  "mode": "search",
  "query": "Coffee shops in London"
}
```

**Current page** sits right under it, defaulting to `1`. Raise it to fetch the next 20 places for the same query:

```json
{
  "mode": "search",
  "query": "Coffee shops in London",
  "page": 2
}
```

#### Ways to write a query

The query is what you would type into Google Maps yourself, **including the location**.

| Query | What you get |
|---|---|
| `Coffee shops in London` | Coffee shops in London |
| `restaurants in Paris` | Restaurants in Paris |
| `dentists in Austin, Texas` | Dentists in Austin |
| `plumbers near Manchester` | Plumbers around Manchester |
| `hotels in Tokyo` | Hotels in Tokyo |
| `Pizza Hut in Chicago` | One brand's Chicago locations |
| `vegan bakeries in Brooklyn, NY` | Vegan bakeries in Brooklyn |
| `car repair in 41.3851,2.1734` | Car repair at those coordinates |
| `coffee shops in 12.9716,77.5946` | Coffee shops at those coordinates |

**The location is not optional.** A search is centred on a place, so `coffee shops` on its own cannot be run — Google needs to know where. Write `coffee shops in Berlin`, or put the coordinates in the Advanced **Geo coordinates** field and leave the location out of the query.

Words like `in`, `near`, `around` and `at` all work, and so does a comma: `dentists, Austin` is read the same way as `dentists in Austin`.

#### Current page

Each page is a **different set of 20 places** for the same query. Leave it at `1` for the first 20; set it to `2` for the next 20, and so on.

```json
{ "mode": "search", "query": "coffee shops in Lisbon", "page": 2 }
```

Run the actor once per page, changing only this number, to build up a larger list. Three pages of `coffee shops in Lisbon` returned **56 distinct places** out of 60 rows in our testing.

- Google runs out of results after about **page 5** for a given query and location. A page past the end comes back empty and **costs you nothing**.
- Pages occasionally repeat a place — Google re-ranks between requests. Deduplicate on `place_id` when merging.
- Paging is not the only way to get more: a narrower query or a different **Geo coordinates** point usually reaches places that paging never will.

#### Advanced options

All optional. Open this section only when you want to target an exact area or change the result language.

| Option | Default | What it does |
|---|---|---|
| **Language** | `en` | Two-letter code for the result language — `de`, `es`, `fr`, `it`, `pt`, `nl`, `pl`, `ja`. Changes category names, hours labels and descriptions. |
| **Geo coordinates** | *(from query)* | Centre the search on an exact `lat,lon`, e.g. `51.5074,-0.1278`. |
| **Zoom** | `15` | How much ground to cover: `12` a whole city, `15` a district, `18` a few streets. |
| **Radius (m)** | `10000` | Search radius around the centre point, in metres. |

##### Using Geo coordinates

> **If you set Geo coordinates, take the location out of your query.**
>
> ✅ query `coffee shops` + geo `51.5074,-0.1278`
> ❌ query `coffee shops in London` + geo `51.5074,-0.1278`
>
> The coordinates *are* the location. Naming a second place in the query pulls the search away from the point you specified.

Use it when a city name is not precise enough — a specific neighbourhood, a business district, a point you picked off a map. To cover a large area, run the actor several times across a grid of coordinates at a fixed zoom.

**Getting coordinates:** right-click any point in Google Maps and the first item in the menu is the `lat,lon` pair. Click it to copy.

```json
{
  "mode": "search",
  "query": "coffee shops",
  "geo": "51.5074,-0.1278",
  "zoom": 16,
  "radius": 2000
}
```

### 📊 How many results?

**One run returns at most 20 places.** That is Google's page size, and no setting raises it — but you can keep going:

- **Page through** — set **Current page** to `2`, `3`, `4`, `5` for another 20 each time. Roughly **100 places** per query and location before Google stops returning more.
- **Vary the terms** — `coffee shops`, `cafes`, `espresso bars` return overlapping but different sets.
- **Vary the area** — search each district instead of the whole city.
- **Sweep a grid** — step Geo coordinates across the region at a fixed zoom.

The last three reach places paging cannot, because each one asks Google a genuinely different question. Paging deeper into one query eventually dries up; asking a new question does not.

### Search output

Every row is one place. Real output, `restaurants in Lisbon`:

```json
{
  "title": "Santa Marta",
  "categories": ["Mediterranean restaurant", "Restaurant"],
  "address": "R. de Santa Marta 61, 1150-294 Lisboa, Portugal",
  "phone": "+351916404726",
  "web_site": "https://www.santamartalisboa.com/",
  "review_rating": 4.8,
  "review_count": 3280,
  "status": "Open",
  "link": "https://maps.google.com/?cid=13231099540799968147",
  "latitude": 38.7253354,
  "longitude": -9.1470572,
  "timezone": "Europe/Lisbon",
  "place_id": "ChIJ9Uz0X9ozGQ0Rk1M3Ve5Onrc",
  "cid": "13231099540799968147",
  "thumbnail": "https://lh4.googleusercontent.com/.../s400-p-k-no-ns-nd/photo.jpg",
  "open_hours": { "Monday": ["12–11 pm"], "Tuesday": ["12–11 pm"] },
  "reviews_link": "https://search.google.com/local/reviews?placeid=ChIJ9Uz0X9ozGQ0Rk1M3Ve5Onrc"
}
```

***

## 📍 Place lookup

Get one specific place when you already know which one you want. Returns the same 24 fields as a search row, **plus up to 3 reviews**.

### Input

```json
{
  "mode": "place",
  "place": "https://maps.google.com/?cid=5249990186996207485"
}
```

#### Accepted input forms

All four work — paste whichever you have.

**1. Full Google Maps URL** — the usual case. Open the place and copy your browser's address bar.

```
https://www.google.com/maps/place/Eiffel+Tower/@48.8584,2.2945,17z/data=!3m1!4b1!4m6!3m5!1s0x47e66e2964e34e2d:0x8ddca9ee380ef7e0!8m2!3d48.8584!4d2.2945
```

**2. Short link with a CID** — what Google's *Share* button produces.

```
https://maps.google.com/?cid=5249990186996207485
```

**3. Bare CID** — the number on its own. This is the `cid` field of any search result, so a search feeds place lookups directly.

```
5249990186996207485
```

**4. Feature ID** — the `0x...:0x...` pair, the `data_id` field of a search result.

```
0x47e66e2964e34e2d:0x8ddca9ee380ef7e0
```

> ⚠️ **One URL form does not work:** a link with only `@lat,lng` and no `!1s0x...` segment carries no place identifier — that is a map position, not a place. Copy the link from the place's own page, not from a panned map.

#### Advanced options

| Option | Default | What it does |
|---|---|---|
| **Language** | `en` | Two-letter result language code. |

Geo, zoom and radius are ignored here: a CID identifies the place on its own.

### Place output

Real output for `5249990186996207485`:

```json
{
  "title": "Türkis Express Vienna Center",
  "categories": ["Fast food restaurant"],
  "address": "Goldschmiedgasse 4, 1010 Wien, Austria",
  "phone": "+43 1 60050101010",
  "web_site": "https://www.turkisexpress.at/",
  "review_rating": 4.8,
  "review_count": 666,
  "status": "Open",
  "link": "https://maps.google.com/?cid=5249990186996207485",
  "open_hours": {
    "Monday":    ["8:30 AM–12 AM"],
    "Tuesday":   ["8:30 AM–12 AM"],
    "Wednesday": ["8:30 AM–12 AM"]
  },
  "user_reviews": []
}
```

***

## 📋 What you get — all 24 fields

Both modes return the same field set. **Fill rate** is measured on a live 20-result run, so it reflects how often Google actually carries the field.

### Core — on essentially every place

| Field | Type | Example | Fill |
|---|---|---|---|
| `title` | string | `"Santa Marta"` | 100% |
| `address` | string | `"R. de Santa Marta 61, 1150-294 Lisboa, Portugal"` | 100% |
| `phone` | string | `"+351916404726"` | 90% |
| `web_site` | string | `"https://www.santamartalisboa.com/"` | 70% |
| `categories` | string\[] | `["Mediterranean restaurant", "Restaurant"]` | 100% |
| `review_rating` | number | `4.8` | 100% |
| `review_count` | number | `3280` | 100% |
| `link` | string | `"https://maps.google.com/?cid=13231099540799968147"` | 100% |

### Location & identifiers

| Field | Type | Example | Fill |
|---|---|---|---|
| `latitude` | number | `38.7253354` | 100% |
| `longitude` | number | `-9.1470572` | 100% |
| `timezone` | string | `"Europe/Lisbon"` | 100% |
| `place_id` | string | `"ChIJ9Uz0X9ozGQ0Rk1M3Ve5Onrc"` | 100% |
| `cid` | string | `"13231099540799968147"` | 100% |
| `data_id` | string | `"0xd1933da5ff44cf5:0xb79e4eee55375393"` | 100% |

`cid` and `data_id` are both accepted as **Place lookup** input, so a search result can be fed straight back in for a deeper look.

### Status, hours & media

| Field | Type | Example | Fill |
|---|---|---|---|
| `status` | string | `"Open"` / `"Closed"` / `"Temporarily closed"` | 100% |
| `open_hours` | object | `{"Monday": ["12–11 pm"], …}` | 100% |
| `thumbnail` | string | `"https://lh4.googleusercontent.com/…/s400-…/photo.jpg"` | 100% |
| `reviews_link` | string | `"https://search.google.com/local/reviews?placeid=…"` | 100% |

> **About opening hours:** Google sometimes returns the full week and sometimes only today, for the same request. Most rows carry the full week; some carry one day. When only one day comes back, the others are not in Google's response at all — there is nothing to extract.

### Attributes & extras

| Field | Type | Example | Fill |
|---|---|---|---|
| `about` | object\[] | Grouped attributes — see below | 100% |
| `credit_cards_accepted` | string\[] | `["Credit cards", "Debit cards", "NFC mobile payments"]` | 75% |
| `order_online` | object\[] | `[{"link": "https://…", "source": "ubereats.com"}]` | 60% |
| `reservations` | object\[] | `[{"link": "https://…", "source": "opentable.com"}]` | 60% |
| `description` | string | `"Elegant spot for Mediterranean plates…"` | 40% |
| `user_reviews` | object\[] | Up to 3 reviews — **Place lookup only** | occasional |

#### `about` — structured attributes

Google's attribute panel, grouped. How many groups exist depends on the category: restaurants and cafés return 10–15, hotels often just one.

```json
"about": [
  {
    "id": "service_options",
    "name": "Service options",
    "options": [
      { "name": "Outdoor seating", "enabled": true },
      { "name": "Takeout",         "enabled": true },
      { "name": "Delivery",        "enabled": false }
    ]
  },
  {
    "id": "highlights",
    "name": "Highlights",
    "options": [
      { "name": "Great coffee",    "enabled": true },
      { "name": "Great wine list", "enabled": true }
    ]
  }
]
```

Common group ids: `service_options`, `highlights`, `popular_for`, `accessibility`, `offerings`, `dining_options`, `amenities`, `atmosphere`, `crowd`, `planning`, `payments`, `children`, `parking`.

#### `user_reviews` — Place lookup only

Up to **3** reviews, when Google includes them in the response. They are a bonus rather than a guarantee: many places return none, and searches never return any.

```json
"user_reviews": [
  {
    "rating": 5,
    "description": "Best flat white in the city, and the staff remember you.",
    "author_url": "https://www.google.com/maps/contrib/1029384756/reviews",
    "profile_picture": "https://lh3.googleusercontent.com/a-/…"
  }
]
```

The reviewer's **display name is not included** — Google does not carry it in this response. For a full review history, use the `reviews_link` field.

***

### 💡 Common uses

**Lead lists** — `plumbers in Sheffield`, then filter to rows with a `phone` and no `web_site`: businesses with no website are the ones that want one.

**Competitor tracking** — run the same query weekly and diff `review_count` and `review_rating` to see who is gaining.

**Market research** — sweep Geo coordinates across a city to map where a category clusters.

**Data enrichment** — you have CIDs or Maps URLs already; run Place lookup to attach current hours, ratings and phone numbers.

**Store locators** — `Pizza Hut in Chicago` returns one brand's locations with coordinates ready to map.

***

### 💰 Pricing

Pay per result. **Empty results are free** — a query that matches nothing and a CID Google does not recognise are never charged, so exploring costs you nothing.

| Event | Price |
|---|---|
| Search result | $0.001 per place |
| Place lookup | $0.01 per place |

A 20-result search costs **$0.02**.

***

### ❓ FAQ

**Why only 20 results per search?**
That is Google's page size for this endpoint. Set **Current page** to `2`, `3`, `4` or `5` to fetch the next 20 each time — about 100 places per query and location. Beyond that, run several searches with different terms, districts or coordinates.

**Why did a later page come back empty?**
Google stops returning results after about page 5 for a given query and location. An empty page is the end of the results, not an error, and it is not charged.

**Why is `phone` or `web_site` empty on some rows?**
Because the business has not listed one. The fields are extracted whenever Google has them; the gaps are in Google's data, not in the scraper.

**Can I get more than 3 reviews?**
Not from this actor. Use the `reviews_link` field, which opens the place's full review page.

**Why did my search return nothing?**
Usually the query has no location — `coffee shops` cannot be searched, `coffee shops in Lisbon` can. If you set Geo coordinates, check they are `lat,lon` in that order.

**Do I need proxies or an API key?**
No. Everything is handled for you; there is nothing to configure.

**Can I choose the result language?**
Yes — set **Language** in Advanced options to any two-letter code.

**Is this legal?**
The actor reads publicly visible business listings — the same information any visitor sees. It collects no personal data and no private information. You are responsible for how you use the output, including under GDPR and CCPA where applicable.

***

### Output formats

Download the dataset as **JSON**, **CSV**, **Excel**, **XML**, **HTML** or **JSONL** from the Storage tab, or pull it from the [Apify API](https://docs.apify.com/api/v2). The dataset ships with three ready-made views — **Overview**, **Contact details** and **Location & IDs** — so a lead list is one click, not a spreadsheet cleanup.

# Actor input Schema

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

**Search** finds up to 20 places matching a query. **Place lookup** returns one specific place from its CID or Google Maps URL, with up to 3 reviews.

## `query` (type: `string`):

What to search for, including the location.

**Examples — all of these work:**

| Query | Finds |
|---|---|
| `Coffee shops in London` | coffee shops in London |
| `restaurants in Paris` | restaurants in Paris |
| `dentists in Austin, Texas` | dentists in Austin |
| `plumbers near Manchester` | plumbers around Manchester |
| `hotels in 41.3851,2.1734` | hotels at those coordinates |
| `Pizza Hut in Chicago` | a specific brand's locations |

The location is what the search is centred on, so include one — `coffee shops` alone cannot be searched. If you set **Geo coordinates** in the Advanced section instead, leave the location out of the query and write just `coffee shops`.

One query per run, returning at most 20 places. For wider coverage, run several queries (vary the terms, the cities, or the coordinates).

## `page` (type: `integer`):

Which page of results to fetch. Each page is a **new set of 20 places** for the same query — page 1 is the first 20, page 2 the next 20, and so on.

To collect more than 20 places, run the actor once per page with the same query and change only this number.

**Worth knowing:**

- Google stops returning results after about **page 5** for a given query and location. Past that you get an empty run, which costs you nothing.
- Pages occasionally repeat a place, because Google re-ranks between requests. Deduplicate on `place_id` if you are merging pages.
- A narrower query or a different **Geo coordinates** point reaches more places than paging deeper does.

Only used when **mode** is *Search*.

## `place` (type: `string`):

Which place to look up. **Four input forms are accepted:**

| Form | Example |
|---|---|
| Bare CID | `5249990186996207485` |
| Short link | `https://maps.google.com/?cid=5249990186996207485` |
| Full Maps URL | `https://www.google.com/maps/place/Eiffel+Tower/@48.8584,2.2945,17z/data=!3m1!4b1!4m6!3m5!1s0x47e66e2964e34e2d:0x8ddca9ee380ef7e0!8m2!3d48.8584!4d2.2945` |
| Feature ID | `0x47e66e2964e34e2d:0x8ddca9ee380ef7e0` |

**How to get one:** open the place on Google Maps and copy the address bar — that is the full URL form. The `!1s0x...:0x...` inside it is the feature ID; a CID is the second half converted to decimal.

A Maps URL with only `@lat,lng` and no `!1s` segment carries no identifier and is rejected — copy the link from the place's own page, not from a panned map.

Only used when **mode** is *Place lookup*.

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

Two-letter Google language code for the results — `en`, `de`, `es`, `fr`, `it`, `pt`, `nl`, `pl`, `ja`. Affects category names, opening-hours labels and the editorial description.

## `geo` (type: `string`):

Centre the search on exact coordinates, e.g. `51.5074,-0.1278`.

**If you set this, leave the location out of the query.** Write `coffee shops` rather than `coffee shops in London` — the coordinates are the location, and a query naming a second place pulls the search away from them.

Leave blank and the coordinates are derived from the location in your query automatically. Set it when you want a precise spot (a neighbourhood, not a city), or to sweep a grid of points across a large area.

Ignored in Place lookup mode — a CID identifies the place on its own.

## `zoom` (type: `integer`):

Map zoom level, 1–21. This is the *area* the search covers: lower zoom sweeps a wider region, higher zoom concentrates on a smaller one.

- `12` — a whole city
- `15` — a district (default)
- `18` — a few streets

At any zoom a query still returns at most 20 places, so a low zoom over a dense city gives 20 spread thinly, while a high zoom gives 20 from one neighbourhood.

## `radius` (type: `integer`):

Search radius around the coordinates, in metres. Default 10000 (10 km). Narrow it to keep results tight around a point, widen it to reach across a metro area.

## Actor input object example

```json
{
  "mode": "search",
  "query": "Coffee shops in London",
  "page": 1,
  "place": "https://maps.google.com/?cid=5249990186996207485",
  "language": "en",
  "geo": "51.5074,-0.1278",
  "zoom": 15,
  "radius": 10000
}
```

# Actor output Schema

## `places` (type: `string`):

The scraped places, one item per place.

## `overview` (type: `string`):

The columns most runs need — name, categories, address, phone, website, rating and link.

## `contacts` (type: `string`):

Name, phone, website and address only — for outreach lists.

# 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 = {
    "query": "Coffee shops in London",
    "place": "https://maps.google.com/?cid=5249990186996207485"
};

// Run the Actor and wait for it to finish
const run = await client.actor("ecommerce_leads/simple-google-maps-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 = {
    "query": "Coffee shops in London",
    "place": "https://maps.google.com/?cid=5249990186996207485",
}

# Run the Actor and wait for it to finish
run = client.actor("ecommerce_leads/simple-google-maps-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 '{
  "query": "Coffee shops in London",
  "place": "https://maps.google.com/?cid=5249990186996207485"
}' |
apify call ecommerce_leads/simple-google-maps-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ecommerce_leads/simple-google-maps-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/LXZQbPowNQHGkqRmT/builds/NpxUluGgj3UjVnoKv/openapi.json
