# Google Maps Leads & Email Scraper (`bgfc97/google-maps-leads-scraper`) Actor

Scrape Google Maps businesses by search term and location: name, category, rating, reviews, address, phone, website, coordinates. Optional contact enrichment (email, social).

- **URL**: https://apify.com/bgfc97/google-maps-leads-scraper.md
- **Developed by:** [Bruno](https://apify.com/bgfc97) (community)
- **Categories:** Lead generation, Business
- **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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Maps Leads Scraper + Email Enrichment

A **real headless browser** (Playwright, via Crawlee's `PlaywrightCrawler`) drives Google Maps through an Apify **RESIDENTIAL proxy** to pull real business leads — then visits each business's own website with another real browser page to enrich it with **emails, WhatsApp and social links**. This is lead generation, not a demo: no raw-HTTP tricks against Maps (Google blocks those instantly) and **no fabricated data** — if a field truly isn't on the page, it comes back `null`; if Maps blocks the request, you get an honest error, never invented results.

### Why it's useful

Google Maps is the single best public source of local-business leads (dentists, restaurants, contractors, agencies...) with a phone number and website attached. The hard, valuable part is turning that into a **contactable** lead — an email address, a WhatsApp number, a social handle. This Actor does both steps in one run.

### Input

```json
{
  "searchQueries": ["dentists in Austin", "restaurantes em Lisboa"],
  "maxResults": 40,
  "enrichContacts": true,
  "language": "en",
  "timeoutSecs": 60
}
```

- `searchQueries` (required) — one or more Google Maps search terms. Each is searched separately.
- `maxResults` — max businesses per query (default 40). The results feed is scrolled repeatedly until this many are loaded or the feed stops growing.
- `enrichContacts` — when `true` (default), each business's website (and its `/contact`-style page, if one is found) is opened with a real browser page to extract emails, a WhatsApp link/number, and social links (Instagram, Facebook, LinkedIn, Twitter/X, TikTok, YouTube).
- `language` — Maps UI/results language code (`en`, `pt-BR`, `es`, ...).
- `proxyConfiguration` — defaults to Apify RESIDENTIAL. Datacenter proxies get blocked by Maps quickly; residential is strongly recommended.
- `timeoutSecs` — how long to wait for the results feed / each business page to render.

### Output (per business)

```json
{
  "searchQuery": "dentists in Austin",
  "name": "Example Dental Studio",
  "category": "Dentist",
  "rating": 4.8,
  "reviewsCount": 312,
  "address": "123 Congress Ave, Austin, TX 78701",
  "phone": "+15125550123",
  "website": "https://exampledentalstudio.com",
  "hours": "Monday 8 AM–5 PM | Tuesday 8 AM–5 PM | ...",
  "lat": 30.2672,
  "lng": -97.7431,
  "mapsUrl": "https://www.google.com/maps/place/...",
  "emails": ["hello@exampledentalstudio.com"],
  "whatsapp": "+15125550123",
  "socials": { "instagram": "https://instagram.com/exampledental", "facebook": "https://facebook.com/exampledental" },
  "contactEnrichmentError": null
}
```

Any field the real page didn't have comes back `null` / empty — never guessed.

### How it works

1. **Maps**: navigates `google.com/maps/search/<query>`, dismisses the EU/UK consent wall if shown, waits for the results feed (`div[role="feed"]`), and scrolls it until `maxResults` is reached or it stops growing (Google lazy-loads results as you scroll).
2. For each result, it opens the business's own Maps page and reads its real `data-item-id` fields (address, phone, website) plus name, category, rating, review count, opening hours, and the lat/lng embedded in the page URL.
3. **Enrichment** (if `enrichContacts`): opens the business website in a fresh browser tab (same proxy), scans `mailto:` links and page HTML for emails (filtered against tracking/CDN/placeholder domains — never asset filenames), `wa.me` / `api.whatsapp.com` links for WhatsApp, and anchor links for social profiles. If a same-domain "contact" link is found, it visits that too (bounded to 2 pages per site total).

### Honesty / limits

- If Google serves a CAPTCHA/anti-bot challenge instead of results, the item reports `BLOCKED` with a snippet of what was actually shown — it does not retry-and-fabricate.
- If a website is down, times out, or has no public email, `emails` comes back empty and `contactEnrichmentError` explains why.
- Fields depend on Google's current DOM structure (`data-item-id` attributes); if Google changes markup, affected fields fall back to `null` rather than wrong values.

### ⭐ Enjoying this Actor?

A quick **rating/review** helps others find it. Want CSV export presets, per-city batching, or CRM webhook push added? Open a ticket on the **Issues** tab.

# Actor input Schema

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

One or more Google Maps search terms, e.g. "dentists in Miami" or "restaurantes em Lisboa". Each query is searched separately and paginated until maxResults is reached.

## `maxResults` (type: `integer`):

Maximum number of businesses to extract per search query (the results feed is paginated 20 at a time until this is reached or no more results are returned).

## `enrich` (type: `boolean`):

OFF by default. The place data (name, category, rating, reviews, address, phone, website, coordinates) is cheap and returned for every result. When you turn this ON, the actor additionally visits each business's own website (and its /contact page, when found) to extract email addresses, a WhatsApp link/number and social links (Instagram, Facebook, LinkedIn, Twitter/X, TikTok, YouTube). This is the slower, higher-value step and is charged separately per enriched lead.

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

Google results language code (hl), e.g. "en", "pt-BR", "es".

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

2-letter country code (gl) that biases Google's results, e.g. "us", "br", "gb". The search text itself still drives the location (e.g. "dentists in Miami" returns Miami regardless).

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

Leave on the default and the actor uses the cheapest proxy that works: it calls Google's results-feed endpoint over DATACENTER IPs first (which Google serves fine) and only falls back to RESIDENTIAL if a page is blocked. Website enrichment also uses datacenter. Pick specific groups only to force them.

## `timeoutSecs` (type: `integer`):

Per-request HTTP timeout in seconds (10-120).

## Actor input object example

```json
{
  "searchQueries": [
    "dentists in Austin"
  ],
  "maxResults": 40,
  "enrich": false,
  "language": "en",
  "countryCode": "us",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "timeoutSecs": 30
}
```

# Actor output Schema

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

Scrapes real business leads from Google Maps (name, category, rating, reviews, address, phone, website, coordinates) by reading the Maps results-feed's internal JSON endpoint over plain HTTP — no browser — through a datacenter-first proxy, then optionally visits each business website to enrich it with emails, WhatsApp and social links (separate opt-in step). Honest results only — no fabricated data.

# 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"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bgfc97/google-maps-leads-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"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("bgfc97/google-maps-leads-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"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call bgfc97/google-maps-leads-scraper --silent --output-dataset

```

## MCP server setup

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