# Google Maps Scraper + Email Extractor (`scrapyx/google-maps-scraper`) Actor

Scrapes Google Maps business listings — name, address, coordinates, rating, review count, phone, website, categories and opening status — with real pagination. Optionally visits each business website to extract contact emails. HTTP only, no API key.

- **URL**: https://apify.com/scrapyx/google-maps-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 1,000 results

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

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

## Google Maps Scraper + Email Extractor

Pulls business listings straight from Google Maps — name, address, coordinates,
rating, review count, phone, website, categories and opening status — and
optionally visits each business's own website to find a contact email.

| | |
| --- | --- |
| **Per query** | Real pagination, ~20 places per page, no duplicates |
| **Returns** | name · full address · lat/lng · rating · review count · phone · website · categories · neighbourhood · opening status |
| **Emails** | optional — visits the business website and its contact pages |
| **Method** | HTTP only. No API key, no login, no browser |
| **Anti-bot** | none on this endpoint — 6/6 TLS profiles answered 200 cold |

### Example input

```json
{
  "searchQueries": ["digital marketing agency jakarta"],
  "latitude": -6.2,
  "longitude": 106.8,
  "maxPlacesPerQuery": 60,
  "extractEmails": true
}
```

### Set the search centre — it is a real filter

`latitude` / `longitude` are **not** a hint. Google searches *around that point*,
so the same query at a different centre returns a different neighbourhood's
businesses. Changing the query but leaving the default centre is the single most
common way to get confusing results — if you ask for "dentist in Berlin" while
the centre is still Jakarta, Google will do its best with Jakarta.

`searchRadiusHint` widens or narrows the area around that centre.

### Email extraction

Turn on `extractEmails` and every place that lists a website gets visited:
homepage first, then `/contact`, `/contact-us`, `/kontak`, `/hubungi-kami`,
`/about` — stopping as soon as an address turns up.

It costs no extra Google traffic at all, only requests to the businesses' own
sites, so it is safe to leave on; it just makes runs longer. On a sample run of
20 agencies, 20 had a website and **12 yielded an email**.

Addresses written as `name (at) domain (dot) com` are decoded. Template
boilerplate — `example@`, `noreply@`, Sentry and Wix CMS addresses, and image
filenames that happen to look like addresses — is filtered out.

### Output

Every row carries the portfolio envelope — `_input`, `_source`, `_scrapedAt`,
`recordType`:

- **`PLACE`** — one per business.
- **`SEARCH_SUMMARY`** — one per query: how many places came back, how many
  pages were read, how many had a website, how many yielded an email, and the
  Maps vs website request counts so the cost of a run is visible in the data.
- **`ERROR`** — one per query that failed, so **every input maps to at least one
  row**.

### Known limits — worth reading

- **Review count is missing on roughly one run in five.** Google serves the place
  record in two variants at random, and only one of them carries the count — in
  the other it is absent from the response entirely, so there is nothing to
  parse. The actor re-asks once when a page comes back without it, which covers
  most of the gap; when it still is not there the field is honestly `null`
  rather than guessed. Nothing in the request controls which variant you get.
- **No reviews.** Google's review endpoints are either retired (the one its
  robots.txt permits) or require an authenticated session (the one that still
  works, which robots.txt also disallows). A review scraper is not buildable
  HTTP-only, so this actor does not pretend to.
- **No web search results.** This scrapes Google *Maps*, not Google Search.
  Google no longer serves organic results as HTML to non-JavaScript clients —
  eleven request shapes were tested, including from a residential connection —
  so a SERP scraper needs a real browser, which this actor deliberately is not.
- **Around 400 places per query, in practice fewer.** Results thin out and start
  repeating well before that; the actor stops as soon as a page adds nothing new
  rather than paying for pages that repeat themselves.
- **Some places have no website or phone.** That is the listing, not a miss —
  48 of 80 sampled places had a website.

### Proxy and cost

Defaults to Apify Proxy on the shared **datacenter** pool, which is included in
your plan at no extra cost and works fine here. If you ever see Google's rate
wall, switch the group to Residential in the proxy field — it uses real consumer
IPs, but Apify bills residential traffic per gigabyte, so leave it off unless you
need it.

# Actor input Schema

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

What to search for on Google Maps, one per line — e.g. 'coffee shops in Jakarta' or 'dentist'. Each query is paginated independently.

## `latitude` (type: `number`):

Latitude of the point to search around. This is a REAL filter, not a hint — the same query at a different centre returns a different neighbourhood's businesses, so set it to the city you actually want.

## `longitude` (type: `number`):

Longitude of the point to search around. Pair it with the latitude above; leaving the default while changing the query is the most common way to get confusing results.

## `searchRadiusHint` (type: `integer`):

Viewport span Google is asked to cover. Larger values search a wider area around the centre; smaller values concentrate on the immediate neighbourhood.

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

Stop after this many places per query. 0 = keep paging until Google runs out (it thins out well before the 400-place ceiling).

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

Google UI language code (hl), e.g. en, id, de. Affects category names and opening-hours wording.

## `countryCode` (type: `string`):

Google country code (gl), e.g. us, id, de. Affects ranking and phone formatting.

## `extractEmails` (type: `boolean`):

For every place that lists a website, fetch that site and pull contact email addresses from it. This costs extra requests to third-party sites (not to Google), so it makes runs slower — leave it off if you only need the Maps listing.

## `emailMaxPagesPerSite` (type: `integer`):

How many pages to try per business site before giving up — homepage first, then /contact, /about and friends. Stops early as soon as an address is found.

## `maxConcurrency` (type: `integer`):

Parallel website fetches during email extraction. Maps pages are always fetched one at a time because Google rate-limits that endpoint.

## `minRequestInterval` (type: `number`):

Minimum gap between request starts, in seconds. Paces starts without holding a worker. Raise it if you hit Google's rate wall.

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

Apify Proxy on the shared datacenter pool. This is the default because it is included in your plan at no extra cost and the Maps endpoint works through it. If Google starts showing its rate wall, switch the group here to Residential — it uses real consumer IPs, but Apify bills residential traffic per gigabyte.

## Actor input object example

```json
{
  "searchQueries": [
    "digital marketing agency jakarta"
  ],
  "latitude": -6.2,
  "longitude": 106.8,
  "searchRadiusHint": 10000,
  "maxPlacesPerQuery": 60,
  "language": "en",
  "countryCode": "us",
  "extractEmails": false,
  "emailMaxPagesPerSite": 4,
  "maxConcurrency": 4,
  "minRequestInterval": 0.5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "searchQueries": [
        "digital marketing agency jakarta"
    ],
    "latitude": -6.2,
    "longitude": 106.8
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/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 = {
    "searchQueries": ["digital marketing agency jakarta"],
    "latitude": -6.2,
    "longitude": 106.8,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/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 '{
  "searchQueries": [
    "digital marketing agency jakarta"
  ],
  "latitude": -6.2,
  "longitude": 106.8
}' |
apify call scrapyx/google-maps-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/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/HfuCkMw0CvsHVEota/builds/admNJmcHj0IOu8QSc/openapi.json
