# Google Maps Email Extractor Pro (`nodeflow/google-maps-email-extractor-pro`) Actor

All-in-one Google Maps scraper & lead generator: Extract business info, public emails, phone numbers, and social media links in seconds.

- **URL**: https://apify.com/nodeflow/google-maps-email-extractor-pro.md
- **Developed by:** [NodeFlow](https://apify.com/nodeflow) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Extractor

A production-oriented Apify Actor designed to discover places on Google Maps, open each place page, and automatically extract contact details from the business's public website. It is highly optimized, reliable, and provides structured data including email addresses, phone numbers, and social media links.

### 🌟 Key Features

- **Comprehensive Extraction**: Gathers place name, Google Maps URL, category, rating, review count, full address, exact coordinates, phone number, website link, main image, and operational status.
- **Deep Contact Enrichment**: Follows the business website to discover public email addresses and telephone numbers.
- **Social Media Links**: Automatically finds links to LinkedIn, Facebook, Instagram, YouTube, TikTok, X (Twitter), Pinterest, and Discord.
- **Team & Lead Information**: Performs best-effort extraction of public `Person`/team information using JSON-LD and contact pages.
- **Social Profile Metadata**: Collects follower counts and other social profile metadata if publicly available in the fetched HTML.
- **Smart Filtering**: Supports filtering by category, rating, open status, and more.
- **Visual Results**: Generates an interactive Leaflet/OpenStreetMap visualization of all extracted places.

### 🚀 How It Works

The Actor accepts search strings, location queries, or direct Google Maps URLs. It searches for places, extracts their basic information from Google Maps, and then visits the associated websites to scrape public contact and social media information.

The Actor uses smart deduplication, request timeouts, retries, and limited website traversal to ensure speed and reliability without overloading target servers.

### 📝 Input Options

The input UI is designed to be intuitive and powerful. You can provide inputs in three primary ways:

1. `searchStringsArray` + `locationQuery` (e.g., "dentist" in "London, UK")
2. `searchStringsArray` + custom structured geolocation (Polygon/Grid)
3. `startUrls` directly pointing to Google Maps search or place URLs (Highest priority).

#### Minimal Example Input

```json
{
  "searchStringsArray": ["dentist"],
  "locationQuery": "Kadıköy, Istanbul, Turkey",
  "maxCrawledPlacesPerSearch": 20,
  "language": "tr",
  "website": "withWebsite",
  "skipClosedPlaces": true
}
```

### 📊 Outputs

Upon successful completion, the Actor provides:

- **Default Dataset**: A rich JSON/CSV dataset containing one detailed object per accepted place.
- **Run Summary**: A `summary` record in the Key-Value Store detailing counters and processing metrics.
- **Interactive Map**: A `results-map` record (HTML) in the Key-Value Store for a visual overview.

### 💰 Pay-Per-Event (Monetization)

This Actor supports Apify's Pay-Per-Event (PPE) pricing model. When running under PPE, it emits billable events incrementally and stops gracefully once the user's defined maximum charge is reached. If run as a free or unmonetized Actor during development, it extracts the same results without attempting to log billable events.
Please refer to `MONETIZATION.md` for a full breakdown of billable events.

### ⚙️ Operational Environment Variables

| Variable | Default | Purpose |
| --- | ---: | --- |
| `MAX_CONCURRENCY` | `4` | Maximum concurrent Google Maps pages processed. |
| `MAX_WEBSITE_PAGES` | `5` | Maximum number of sub-pages fetched from each business site. |
| `HARD_MAX_CANDIDATES_PER_SEARCH` | `1000` | Safety cap when the result limit is empty. |
| `CRAWLEE_BROWSER_EXECUTABLE_PATH` | image default | Local Chromium/Brave executable path for debugging. |

### 🛡️ Reliability and Responsible Use

- **Respectful Crawling**: The crawler implements retries, strict per-search caps, deduplication, request timeouts, and limits website traversal depths. It does NOT bypass CAPTCHAs or unauthorized access controls.
- **Data Privacy**: Ensure you only collect public business contact data for legitimate purposes and follow applicable privacy laws, marketing guidelines, and platform terms.
- **Validation**: Email validation checks the domain's mail capability (DNS/MX records), not the exact existence of a particular mailbox. Lead and social enrichment is strictly best-effort based on publicly available HTML.

### 💻 Local Development

Requirements: Node.js 22+ and a Chromium-family browser for live smoke tests.

```bash
npm ci
npm test
npm run lint
```

For a live Google Maps smoke test (Windows example):

```powershell
$env:CRAWLEE_BROWSER_EXECUTABLE_PATH='C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe'
npm run test:smoke
```

To run the complete Actor locally, create `storage/key_value_stores/default/INPUT.json` and run `npm start`.

### 📄 License

MIT. See [LICENSE](LICENSE).

# Actor input Schema

## `searchStringsArray` (type: `array`):

Type what you'd normally search for in the Google Maps search bar, like <b>English breakfast</b> or <b>pet shelter</b>. Aim for unique terms for faster processing. Using similar terms (e.g., <b>bar</b> vs. <b>restaurant</b> vs. <b>cafe</b>) may slightly increase your capture rate but is less efficient.<br><br>⚠️ <b>Searching for a specific place?</b> If you're looking for a particular business or location (e.g., <b>M\&M Indian Thai Halal Restaurant</b>), make sure to also specify the city or country in the <b>📍 Location</b> field below to get more accurate and reliable results. Without location context, Google Maps may return results from unexpected areas.<br><br>⚠️ Heads up: Adding a location directly to the search, e.g., <b>restaurant Pittsburgh</b>, can limit you to a maximum of 120 results per search term due to <a href='https://blog.apify.com/google-places-api-limits/#%E2%9B%94-what-are-google-maps-limitations-for-scraping'>Google Maps' scrolling limit</a>.<br><br>You can also use direct place IDs here in the format <code>place\_id:ChIJ8\_JBApXMDUcRDzXcYUPTGUY</code>. See the [detailed description](https://apify.com/compass/crawler-google-places#search-terms).

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

Define location using free text. Simpler formats work best; e.g., use City + Country rather than City + Country + State. <br><br>🌍 You can just set the whole country or state as the location: Google Maps Email Extractor intelligently splits it into subregions internally, so there's no need to search city by city or neighborhood by neighborhood yourself. <br><br>Verify with the <a href='https://nominatim.openstreetmap.org/ui/search.html'>OpenStreetMap webapp</a> for visual validation of the exact area you want to cover. <br><br>💡 <b>Pro tip:</b> Always specify a location when searching for specific place names in the <b>🔍 Search terms</b> field above. This helps narrow down results to the geographic area you're interested in and prevents getting results from unrelated locations.<br><br>⚠️ Automatically defined City polygons may be smaller than expected (e.g., they don't include agglomeration areas). If you need to define the whole city area, head over to the 📡 <b>Geolocation parameters\*</b> section instead to select Country, State, County, City, or Postal code.<br>For an even more precise location definition (especially when using City name as a starting point), head over to <b>🛰 Custom search area</b> section to create polygon shapes of the areas you want to scrape. Note that 📍 <b>Location</b> settings always take priority over <b>📡 Geolocation\*</b> (so use either section but not both at the same time). <br><br>For guidance and tricks on location definition, check the <a href='https://blog.apify.com/google-places-api-limits/#2-choose-the-location-using-regular-toponomy%F0%9F%93%8D'>Apify tutorial</a>.

## `maxCrawledPlacesPerSearch` (type: `integer`):

Number of results you expect to get per each Search term, Category or URL. The higher the number, the longer it will take. <br><br>If you want to scrape all the places available, <b>leave this field empty</b>.

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

Results will be scraped in this language.

## `categoryFilterWords` (type: `array`):

You can limit the places that are scraped based on the Category filter; you can choose as many categories for one flat fee for the whole field. ⚠️ Using categories can sometimes lead to false negatives, as many places do not properly categorize themselves, and there are over <a href='https://api.apify.com/v2/key-value-stores/epxZwNRgmnzzBpNJd/records/categories'> 4,000</a> available categories which Google Maps has. Using categories might filter out places that you’d like to scrape. To avoid this problem, you must list all categories that you want to scrape, including synonyms, e.g., divorce lawyer, divorce attorney, divorce service, etc. See the [detailed description](https://apify.com/compass/crawler-google-places#categories).

## `searchMatching` (type: `string`):

Restrict what places are scraped based on matching their name with provided 🔍 <b>Search term</b>. E.g., all places that have <b>chicken</b> in their name vs. places called <b>Kentucky Fried Chicken</b>.

## `placeMinimumStars` (type: `string`):

Scrape only places with a rating equal to or above the selected stars. Places without reviews will also be skipped. Keep in mind, filtering by reviews reduces the number of places found per credit spent, as many will be excluded.

## `website` (type: `string`):

Use this to exclude places without a website, or vice versa. This option is turned off by default.

## `skipClosedPlaces` (type: `boolean`):

Skip places that are marked as temporary or permanently closed. Ideal for focusing on currently open places.

## `scrapePlaceDetailPage` (type: `boolean`):

Scrape detail pages of each place the Actor finds. This will slow down the Actor since it needs to open another page for each place individually.<br><br> The fields available only when scrapePlaceDetailPage is enabled include: `reviewsDistribution`, `reviewsRemovedNotice`, `imageCategories`, popularTimes fields, `openingHours`, `BusinessConfirmationText`, `peopleAlsoSearch`, `reviewsTags`, `updatesFromCustomers`, `questionsAndAnswers`, `tableReservationLinks`, `ownerUpdates` and hotel fields. <br><br> Enabling this also ensures that `reviewsCount` will be scraped. <br><br>This option needs to be enabled if you wish to use any of the options below.

## `scrapeTableReservationProvider` (type: `boolean`):

Scrape table reservation provider data like name, address, email or phone. This data is present only in restaurants that have a blue "RESERVE A TABLE" button.

## `scrapeOrderOnline` (type: `boolean`):

Scrape the 'Order online' section of restaurants to get pickup and delivery providers (Uber Eats, DoorDash, etc.), along with fees and estimated times.

## `includeWebResults` (type: `boolean`):

Extract the "Web results" section located at the bottom of every place listing.

## `scrapeDirectories` (type: `boolean`):

Some places (e.g. malls) can have multiple businesses located inside them. This option will scrape inside the "Directory" or "At this place" as per different categories (example <a href='https://www.google.com/maps/place/Forum+Karlín/@50.0914263,14.4522411,532m/data=!3m1!1e3!4m7!3m6!1s0x470b94a14fd738ff:0x6a75e391416ab4fa!8m2!3d50.0914263!4d14.454816!10e3!16s%2Fg%2F1ptxlz77_?entry=ttu&g_ep=EgoyMDI1MDQwMi4xIKXMDSoASAFQAw%3D%3D'>here</a>). Turn this toggle on to include those places in your results.<br><br> ⚠️ Note that full place details need to be scraped in order to scrape directories.

## `maxQuestions` (type: `integer`):

Set the number of questions per place you expect to scrape. If you fill in <b>0</b> or leave the field empty, only the first question and answer will be scraped. To extract all questions, type <b>999</b> into the field.<br><br>⚠️ Note that some of the fields contain <b>personal data</b>.

## `scrapeSocialMediaProfiles` (type: `object`):

Enable enrichment for any social media profiles found. This add-on retrieves detailed public data for each profile, including <b>profile names, follower/following counts, descriptions, post/video counts, and verification status</b>.<br><br>You are charged a flat rate for the <b>total number of profiles enriched</b>, regardless of how many platforms (Facebook, YouTube, etc.) you select.<hr><b>Feature dependency:</b><br>To use this feature, the <b>Company contacts enrichment (from website)</b> add-on is enabled automatically. This ensures the enriched social media data is combined with the main contact record for each domain.<br><br><b>Output:</b> Enriched profiles are available in the <b>Social profiles</b> output view tab.

## `maximumLeadsEnrichmentRecords` (type: `integer`):

Enrich your results with detailed contact and company information, including employee names, job titles, emails, phone numbers, LinkedIn profiles, and key company data like industry and number of employees. <br><br> This setting allows you to set the maximum number of leads records you want to scrape per each place found on the map (that has a website). By default, it's set to 0 which means that no leads information will be scraped. <br><br>⚠️ Note that some of the fields contain <b>personal data</b>. GDPR protects personal data in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your use case is legitimate, please consult an attorney. <br><br>We exclude leads of big chains as these are not related to the local places: mcdonalds, starbucks, dominos, pizzahut, burgerking, kfc, subway, wendys, dunkindonuts, tacobell.

## `leadsEnrichmentDepartments` (type: `array`):

You can use this filter to include only specific departments (like Sales, Marketing, or C-Suite). Note: This will only work if the ⏩ Add-on: Extract business leads information - Maximum leads per place (maximumLeadsEnrichmentRecords) option is enabled. Please note that some job titles are sometimes miscategorized in the wrong departments.

## `verifyLeadsEnrichmentEmails` (type: `boolean`):

When enabled, verifies the email address of each lead extracted during business leads enrichment. Each lead receives an <b>emailVerification</b> object with the verification result and quality assessment.<br><br><b>Charged (decisive results):</b> valid (<code>ok</code>), invalid, and disposable email addresses.<br><b>Not charged:</b> catch-all, unknown, and error results.<br><br>⚠️ This add-on requires business leads enrichment to be enabled.

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

Set the country, e.g., <b>United States</b>.

## `city` (type: `string`):

Enter the city, e.g., <b>Pittsburgh</b>.<br><br>⚠️ <b>Do not include State or Country names here.</b><br><br>⚠️ Automatic City polygons may be smaller than expected (e.g., they don't include agglomeration areas).

## `state` (type: `string`):

Set a state, e.g., <b>Massachusetts</b> (mainly for the US addresses).

## `county` (type: `string`):

Set the county, e.g., <b>New York County</b>.<br><br>⚠️ Note that <b>county</b> may represent different administrative areas in different countries: a county (e.g., US), regional district (e.g., Canada) or département (e.g., France).

## `postalCode` (type: `string`):

Set the postal code, e.g., <b>10001</b>. <br><br>⚠️ <b>Combine Postal code only with 🗺 Country, never with 🌇 City. You can only input one postal code at a time.</b>

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

Use this field to define the exact search area if other search area parameters don't work for you. See <a href='https://apify.com/compass/crawler-google-places#custom-search-area' target='_blank' rel='noopener'>readme</a> or <a href='https://blog.apify.com/google-places-api-limits/#1-create-a-custom-area-by-using-pairs-of-coordinates-%F0%9F%93%A1' target='_blank' rel='noopener'>the Apify guide</a> for details.

## `startUrls` (type: `array`):

Max 300 results per search URL. Valid format for URLs contains <code>https://google.com/maps/</code>. This feature also supports uncommon URL formats such as: <code>https://google.com/maps?cid=***</code>, <code>https://goo.gl/maps/***</code>, and custom place list URL.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "categoryFilterWords": [
    "pizza",
    "italian"
  ],
  "searchMatching": "all",
  "placeMinimumStars": "",
  "website": "allPlaces",
  "skipClosedPlaces": false,
  "scrapePlaceDetailPage": false,
  "scrapeTableReservationProvider": false,
  "scrapeOrderOnline": false,
  "includeWebResults": false,
  "scrapeDirectories": false,
  "maxQuestions": 0,
  "scrapeSocialMediaProfiles": {
    "facebooks": false,
    "instagrams": false,
    "youtubes": false,
    "tiktoks": false,
    "twitters": false
  },
  "maximumLeadsEnrichmentRecords": 0,
  "leadsEnrichmentDepartments": [
    "sales",
    "marketing"
  ],
  "verifyLeadsEnrichmentEmails": false,
  "countryCode": "US",
  "city": "New York",
  "state": "New York",
  "county": "New York County",
  "postalCode": "10001",
  "customGeolocation": {
    "type": "Point",
    "coordinates": [
      -73.9857,
      40.7484
    ]
  },
  "startUrls": [
    {
      "url": "https://www.google.com/maps/place/Yellowstone+National+Park/@44.5857951,-110.5140571,9z/data=!3m1!4b1!4m5!3m4!1s0x5351e55555555555:0xaca8f930348fe1bb!8m2!3d44.427963!4d-110.588455?hl=en-GB"
    }
  ]
}
```

# Actor output Schema

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

All scraped places and contact details

## `resultsMap` (type: `string`):

Interactive map of geocoded results

## `summary` (type: `string`):

Counts, timing, and charging-limit status

# 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 = {
    "searchStringsArray": [
        "restaurant"
    ],
    "locationQuery": "New York, USA",
    "maxCrawledPlacesPerSearch": 50,
    "language": "en",
    "scrapeSocialMediaProfiles": {
        "facebooks": false,
        "instagrams": false,
        "youtubes": false,
        "tiktoks": false,
        "twitters": false
    },
    "maximumLeadsEnrichmentRecords": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("nodeflow/google-maps-email-extractor-pro").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 = {
    "searchStringsArray": ["restaurant"],
    "locationQuery": "New York, USA",
    "maxCrawledPlacesPerSearch": 50,
    "language": "en",
    "scrapeSocialMediaProfiles": {
        "facebooks": False,
        "instagrams": False,
        "youtubes": False,
        "tiktoks": False,
        "twitters": False,
    },
    "maximumLeadsEnrichmentRecords": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("nodeflow/google-maps-email-extractor-pro").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 '{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "scrapeSocialMediaProfiles": {
    "facebooks": false,
    "instagrams": false,
    "youtubes": false,
    "tiktoks": false,
    "twitters": false
  },
  "maximumLeadsEnrichmentRecords": 0
}' |
apify call nodeflow/google-maps-email-extractor-pro --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nodeflow/google-maps-email-extractor-pro"
        }
    }
}

```

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/Jyh7FiV4cVGZEHjfF/builds/IppzXNFcW9lLTcceG/openapi.json
