# Google Maps Email Scraper — Business Emails & Contacts (`thenetaji/google-maps-email-scraper`) Actor

Search Google Maps for a kind of business in an area and get each one's contact details: the phone and address from the listing, plus the emails and social profiles found by crawling its website. Covers areas larger than one map view by tiling them.

- **URL**: https://apify.com/thenetaji/google-maps-email-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 business founds

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 Email Scraper — Business Emails & Contacts

The Actor searches Google Maps for a kind of business in an area and returns each one's contact details: the phone and address published on the listing, plus the email addresses and social profiles found by crawling that business's own website. One business produces one record, whether or not it has a website.

```json
{
  "query": "dentist",
  "location": "Austin, Texas",
  "spanMetres": 20000,
  "maxPlaces": 20
}
```

### Accepted input

`query` is what would be typed into Google Maps — `dentist`, `hardware store`, `plumber`, or a business name. Google's own interpretation of the phrase applies.

`location` is where to search — a town, city, district or address such as `Austin, Texas` or `Shoreditch, London`. It is resolved to a point once at the start of the run. `latitude` and `longitude` are optional and override it, for a search centred on an exact point rather than a named place; they must be given together.

`spanMetres` is how wide a map view to search around that point, and defaults to 20,000 m — roughly a metro area. `areaMetres` defaults to `0`, which searches one view at the centre. Anything larger walks a grid of views across that area.

The grid is the reason `areaMetres` exists. One search term at one map view returns about 182 distinct businesses, and that ceiling belongs to the question rather than to the site: covering an area holding more than that is a matter of asking several narrower views instead of one wide one. A city walked at a 2,000 m span enumerates it. `maxPlaces` stops the walk after a given number of businesses; `0` means no limit.

`contactTypes`, `includeContactText`, `smartDiscovery`, `maxPagesPerWebsite`, `useStealth` and `solveCloudflare` control the website crawl and behave as they do in the Website Email Scraper. `maxPagesPerWebsite` defaults to `3` here, because a business website only has to be read as far as its contact page.

`enrichSocialProfiles` is off by default. Turned on, each social account linked from a business website is read for follower counts, category, verification and biography — and for the contact email or phone that business accounts publish and their website often does not.

### Response fields

One record per business, in one shape. The shape does not change with any input.

| Field | Contents |
|---|---|
| `schemaVersion` | Version of this output contract |
| `url` | The business website, or empty when it has none |
| `domain` | Domain of that website |
| `status` | `ok` when contacts were found, `empty` when none were, `failed` when the business could not be read at all |
| `scrapedAt` | When the business finished being processed |
| `place` | The Google Maps listing: name, address, phone, categories, rating, hours, coordinates and map link |
| `company` | Name, description and website read from the business's own pages |
| `emails`, `phones`, `socials`, `addresses` | Every contact found, from the listing and the website together |
| `stats` | `pagesScanned`, `pagesFailed`, `browserPages`, `durationMs` |
| `error` | `code` and `message` when the business could not be read, otherwise `null` |

Every entry in every collection carries the same five keys: `value` (always a string), `sourceUrl`, `foundBy`, `confidence` and `attributes`. `foundBy` says where the detail came from — `google-maps` for the listing's own phone and address, `mailto` or `dom` for the website, and the platform name for anything a social profile published.

A business with no website still returns a record, with its listing phone and address as ordinary entries. Those are frequently the only contact details a small business publishes anywhere, so they are the result rather than a consolation for one.

### Reshaping the results

The dataset export handles the common reshapes without changing what the Actor writes.

| Wanted | Export setting |
|---|---|
| One row per email | `unwind=emails` — the record's `url` and `place` merge onto each row |
| Businesses and their emails only | `fields=place,emails` with `unwind=emails` |
| Flat CSV for a CRM | `unwind=emails`, `flatten=place,attributes`, format CSV |
| One row per business | The default; change nothing |

### Charging

| Event | When |
|---|---|
| Business found | Once per distinct business the search returned, including businesses with no website |
| Add-on: contacts from the business website | Once per business whose website was crawled, however many pages that took |
| Add-on: social profile | Once per social account read, and only when the account returned data |

Website crawling is charged once per business rather than per page, so the cost of a run is the number of businesses it found and not how large their websites happen to be. `maxPagesPerWebsite` is what keeps that offer safe, and `smartDiscovery` spends those pages on contact and about pages first.

The pages of one search overlap, and a business that appears on several of them is charged once.

### Questions

**How precise does the location need to be?**

A city or district is enough for most searches — it is resolved to a point, and `spanMetres` decides how far around that point the search reaches. A name that matches several places resolves to the best match, so add the region or country when that matters: `Cambridge, England` rather than `Cambridge`. When an exact centre is needed, set `latitude` and `longitude` instead and the name is ignored.

**Why did a wide search return fewer businesses than expected?**

One query at one map view stops at about 182 distinct businesses however wide the view is — a wider view reaches further out rather than returning more. Set `areaMetres` to the area to cover and lower `spanMetres`, and the run walks a grid of narrower views instead.

**What happens to a business with no website?**

It is returned as a record with its listing phone, address and categories, and `url` is empty. `status` is `ok` when the listing itself carried a contact.

**Why did a business return no email when its website clearly has one?**

Only the first few pages of each website are read, ranked so contact and about pages come first. Raising `maxPagesPerWebsite` reads further. Some sites also publish contact details only behind a form, which is not something a crawl can read.

### Related Actors

For crawling websites you already have a list of, see [Website Email Scraper](https://apify.com/thenetaji/website-email-scraper). For Google Maps business listings without the contact crawl, see [Google Maps Scraper](https://apify.com/thenetaji/google-maps-scraper).

# Actor input Schema

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

The kind of business to find, exactly as it would be typed into Google Maps — dentist, hardware store, plumber — or a business name.

## `location` (type: `string`):

A town, city, district or address to search around — for example 'Austin, Texas' or 'Shoreditch, London'. Leave empty only if setting exact coordinates below.

## `spanMetres` (type: `integer`):

How wide a map view to search around that location. Narrower views reach fewer businesses each but return them from closer in, which is what makes a large area coverable.

## `areaMetres` (type: `integer`):

Leave at 0 to search one map view. Anything larger walks a grid of views across that area, which is the only way past the roughly 182 businesses a single view returns.

## `maxPlaces` (type: `integer`):

Stop after this many distinct businesses, so a first run finishes quickly and cheaply. Use 0 for no limit.

## `contactTypes` (type: `array`):

Choose which kinds of public contact information to return.

## `includeContactText` (type: `boolean`):

Also find contact details written in page text, including common at/dot email obfuscation. Turn off to return only details published in links and structured data.

## `enrichSocialProfiles` (type: `boolean`):

Read the accounts behind the social links found on each website and attach follower counts, category, verification and bio — including the contact email and phone that business accounts publish and their website often does not. Charged per profile read.

## `maxSocialProfilesPerWebsite` (type: `integer`):

Cap on how many social accounts are read for each website, so a site listing every network it has ever joined cannot dominate the bill. Only applies when profile enrichment is on.

## `smartDiscovery` (type: `boolean`):

Inspect contact, about, team, support, company, and legal pages before lower-value links.

## `maxCrawlDepth` (type: `integer`):

Maximum number of link hops from each starting page. Use 0 to inspect only the supplied URLs.

## `maxPagesPerWebsite` (type: `integer`):

Cap on pages inspected for each supplied URL, so one large website cannot consume the whole budget and leave the rest of the list empty. Use 0 for no per-website cap.

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

Maximum number of pages processed at the same time.

## `excludeUrlPatterns` (type: `array`):

Optional regular expressions for URLs that should never be inspected.

## `useStealth` (type: `boolean`):

Retry blocked pages with a rendered browser after fast requests fail.

## `solveCloudflare` (type: `boolean`):

Allow the browser fallback to handle supported Cloudflare challenges.

## `maxRequestRetries` (type: `integer`):

Maximum retries for ordinary request failures.

## `maxBlockedRetries` (type: `integer`):

Maximum retries when a website blocks a request.

## `useProxy` (type: `object`):

Optionally use Apify Proxy. Residential access is used only when explicitly selected or after repeated blocks.

## `verboseLogs` (type: `boolean`):

Log every page and request instead of periodic progress. Useful when diagnosing a run, but a large crawl can log faster than the platform records it, so leave this off for normal use.

## `crawlEngine` (type: `string`):

Internal engine-selection contract.

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

Optional. Overrides the location above when set, for a search centred on a precise point rather than a named place. Must be given together with longitude.

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

Optional. Overrides the location above when set. Must be given together with latitude.

## Actor input object example

```json
{
  "query": "dentist",
  "location": "Austin, Texas",
  "spanMetres": 20000,
  "areaMetres": 0,
  "maxPlaces": 20,
  "contactTypes": [
    "email",
    "phone",
    "social",
    "address"
  ],
  "includeContactText": true,
  "enrichSocialProfiles": false,
  "maxSocialProfilesPerWebsite": 5,
  "smartDiscovery": true,
  "maxCrawlDepth": 2,
  "maxPagesPerWebsite": 3,
  "maxConcurrency": 10,
  "excludeUrlPatterns": [],
  "useStealth": true,
  "solveCloudflare": false,
  "maxRequestRetries": 3,
  "maxBlockedRetries": 5,
  "useProxy": {
    "useApifyProxy": false,
    "apifyProxyGroups": []
  },
  "verboseLogs": false,
  "crawlEngine": "auto",
  "latitude": 30.2672,
  "longitude": -97.7431
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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": "dentist",
    "location": "Austin, Texas"
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/google-maps-email-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": "dentist",
    "location": "Austin, Texas",
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/google-maps-email-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": "dentist",
  "location": "Austin, Texas"
}' |
apify call thenetaji/google-maps-email-scraper --silent --output-dataset

```

## MCP server setup

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