# Google Maps Lead Scraper (`incognito_mode/google-maps-lead-scraper`) Actor

Scrapes businesses from Google Maps search results and enriches each lead with public emails from the business website.

- **URL**: https://apify.com/incognito\_mode/google-maps-lead-scraper.md
- **Developed by:** [Elena Vance](https://apify.com/incognito_mode) (community)
- **Categories:** Lead generation, Developer tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 place scrapeds

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

## Google Maps Lead Scraper

Turns Google Maps searches into a clean lead list: business name, category, address, phone, website, rating, coordinates — plus public email addresses discovered on each business's own website.

Built for lead generation, local market research and territory analysis.

### What you get

One dataset row per business, including:

| Field | Example |
| --- | --- |
| `name` | ATX Family Dental |
| `category` / `categories` | Dentist / Dentist, Cosmetic dentist, … |
| `phone` / `phoneUnformatted` | +1 512-717-3147 / +15127173147 |
| `website` / `websiteDomain` | https://www.atxfamilydental.com/ |
| `primaryEmail` / `emails` | info@atxfamilydental.com |
| `address`, `street`, `city`, `state`, `postalCode`, `countryCode` | 1700 S 1st St, Austin, TX 78704 |
| `rating` | 4.9 |
| `coordinates` | `{ "lat": 30.2482, "lng": -97.7560 }` |
| `googlePlaceId` | ChIJb5gBC-u0RIYRHkntaUcdJGs |
| `openingHours`, `hoursText`, `businessStatus`, `permanentlyClosed` | Open · Closes 5 PM |
| `attributes` | Wheelchair-accessible entrance |

### Quick start

```json
{
  "searchQueries": ["dentists in Austin"],
  "maxResultsPerQuery": 120,
  "includeEmails": true
}
```

Reuse one set of queries across cities with `locationQuery`:

```json
{
  "searchQueries": ["dentists", "orthodontists"],
  "locationQuery": "Austin, Texas",
  "requireWebsite": true,
  "minRating": 4.5
}
```

Target an exact map area instead of relying on the query text:

```json
{
  "searchQueries": ["coffee shop"],
  "customGeolocation": { "latitude": 51.5072, "longitude": -0.1276, "zoom": 13 },
  "countryCode": "gb"
}
```

### Input reference

| Field | Default | Notes |
| --- | --- | --- |
| `searchQueries` | required | One search per line. |
| `locationQuery` | – | Appended to every query unless already present. |
| `maxResultsPerQuery` | 120 | See the result-limit note below. |
| `maxTotalResults` | 10000 | Hard cap across all queries. |
| `includeEmails` | true | Crawls each business website for public emails. |
| `maxWebsitePagesPerPlace` | 5 | Homepage plus contact/about style pages. |
| `minRating` | – | Places with no rating are excluded when set. |
| `requireWebsite` / `requirePhone` | false | Keep only contactable businesses. |
| `skipClosed` | false | Drop permanently/temporarily closed places. |
| `categoryIncludes` | – | Case-insensitive substring match on categories. |
| `language` / `countryCode` | `en` | Localizes categories and hours wording. |
| `customGeolocation` | – | `{ latitude, longitude, zoom }`. |
| `proxyConfiguration` | Apify Proxy | Strongly recommended. |
| `maxConcurrency` | 10 | Lower it if you hit rate limits. |

### How many results can I get?

Google itself stops serving results at typically **100–400 places per query**, no matter how the search is run. Setting `maxResultsPerQuery` to 500 will not exceed what Google returns.

To collect more places in an area, split the search rather than raising the limit:

- by neighbourhood or suburb — `"dentists in North Austin"`, `"dentists in South Austin"`
- by related terms — `"dentist"`, `"dental clinic"`, `"orthodontist"`
- by map area — run the same query with several `customGeolocation` centres

Duplicates are removed automatically across all queries in a run, using the Google place ID.

### Email enrichment

For each business with a website, the Actor fetches the homepage and up to `maxWebsitePagesPerPlace - 1` further same-domain pages whose link text or path suggests contact, about, team, impressum or similar. It reads `mailto:` links, visible text, lightly obfuscated forms (`name [at] domain [dot] com`) and Cloudflare-protected addresses.

Addresses are lowercased, deduplicated, and filtered to drop placeholders (`you@example.com`), platform noise (`@sentry.io`, `@wixpress.com`) and no-reply mailboxes. `primaryEmail` prefers an address on the business's own domain, then a role mailbox such as `info@` or `contact@`.

Roughly half of small businesses publish an email; the rest use contact forms only. Set `requireWebsite: true` to concentrate the run on businesses that can be enriched.

### Pricing

**$4.00 per 1,000 businesses — email enrichment included.**

You are charged one `place-scraped` event for each business saved to the
dataset, whether or not it turned out to have an email. There is no separate
add-on fee for finding emails, and no charge for places skipped by your filters.

Set a maximum cost per run in the Actor's run options to cap spending; the run
stops cleanly once it is reached.

### Speed and cost

The Actor reads Google's Maps data over plain HTTP with no browser, so it is
cheap to run. A measured run of 80 businesses across two queries, with email
enrichment on:

| Memory | Runtime | Compute units |
| --- | --- | --- |
| 1024 MB | 219 s | 0.061 |
| **2048 MB (default)** | **125 s** | **0.069** |
| 4096 MB | 88 s | 0.097 |

Peak memory use is around 500 MB. Apify allocates CPU in proportion to memory,
so lower settings are slightly cheaper but noticeably slower. 2048 MB is the
default because it is close to the cheapest option at roughly half the runtime.

### Notes and limitations

- **`reviewCount` is usually empty.** Google removed review counts from the data behind Maps search results; the field remains in the schema and will populate again if Google restores it.
- **`openingHours` covers the current day only**, which is all Google includes in search results.
- Rows where extraction was incomplete are still saved, with `extractionStatus` set to `partial` and a diagnostic `errorMessage`, so a run never silently drops a lead.

### Compliance

This Actor collects publicly listed business information and publicly published business contact details. It does not scrape personal profiles or reviews, and it does not bypass logins or access controls. You are responsible for using the results lawfully, including under GDPR, CAN-SPAM and local marketing rules, and for honouring the terms of any site you contact.

### Local development

```bash
npm install
npm test          # unit tests, no network required
apify run         # live run using .actor/input_schema.json defaults
```

# Actor input Schema

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

One Google Maps search per line, for example "dentists in Austin" or "plumbers Brooklyn NY". Include the location in the query, or set it once in "Location" below.

## `locationQuery` (type: `string`):

Optional location appended to every search query, so you can reuse the same queries across cities. Skipped for a query that already names the location.

## `maxResultsPerQuery` (type: `integer`):

Maximum places to collect for each search query. Google itself stops serving results at roughly 120-400 places per query; to gather more, split the search by city or neighbourhood.

## `maxTotalResults` (type: `integer`):

Hard cap across all queries. The run stops enqueuing new results once this many places have been collected.

## `includeEmails` (type: `boolean`):

Visits each business website and extracts public email addresses. Adds runtime but is the main source of contactable leads.

## `maxWebsitePagesPerPlace` (type: `integer`):

How many pages of each business website to visit. The homepage counts as one; the rest are contact/about style pages.

## `minRating` (type: `number`):

Skip places rated below this value, for example 4.5. Places with no rating are skipped when this is set. Leave empty to keep every place.

## `requireWebsite` (type: `boolean`):

Keep only places that list a website. Recommended when the goal is email discovery.

## `requirePhone` (type: `boolean`):

Keep only places that list a phone number.

## `skipClosed` (type: `boolean`):

Skip places marked permanently or temporarily closed.

## `categoryIncludes` (type: `array`):

Keep only places whose Google category matches one of these words, for example "dentist" or "restaurant". Case-insensitive substring match.

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

Language code for Google Maps results, such as "en", "de" or "pt-BR". Affects category and opening-hours wording.

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

Two-letter country code used to localize results, such as "us", "gb" or "de".

## `customGeolocation` (type: `object`):

Centre the search on exact coordinates instead of relying on the query text. Example: {"latitude": 51.5072, "longitude": -0.1276, "zoom": 13}. Higher zoom searches a smaller area.

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

Apify Proxy is strongly recommended. Google rate-limits repeated requests from one IP.

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

Maximum parallel HTTP requests. Lower this if you see rate limiting.

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

How many times a failed request is retried on a fresh proxy session before it is given up.

## `debugMode` (type: `boolean`):

Log per-request detail. Useful when diagnosing missing results.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists in Austin"
  ],
  "locationQuery": "Austin, Texas",
  "maxResultsPerQuery": 120,
  "maxTotalResults": 10000,
  "includeEmails": true,
  "maxWebsitePagesPerPlace": 5,
  "requireWebsite": false,
  "requirePhone": false,
  "skipClosed": false,
  "categoryIncludes": [],
  "language": "en",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "maxRequestRetries": 4,
  "debugMode": false
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset items containing Google Maps place details and optional public business website emails.

# 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": [
        "dentists in Austin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("incognito_mode/google-maps-lead-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": ["dentists in Austin"] }

# Run the Actor and wait for it to finish
run = client.actor("incognito_mode/google-maps-lead-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": [
    "dentists in Austin"
  ]
}' |
apify call incognito_mode/google-maps-lead-scraper --silent --output-dataset

```

## MCP server setup

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