# Google Maps Email Extractor — Leads, Emails, Phones & Socials (`tortuga/google-maps-email-scraper`) Actor

Find businesses on Google Maps and get the emails, phone numbers and social profiles (Facebook, Instagram, LinkedIn, X...) published on their own websites, or scan a list of websites.

- **URL**: https://apify.com/tortuga/google-maps-email-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Lead generation
- **Stats:** 3 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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 Email Extractor

Find businesses on Google Maps by keyword and location, then get the **emails, phone numbers and social profiles** each business publishes on its **own website**: Facebook, Instagram, LinkedIn, X (Twitter), YouTube, TikTok, Pinterest and WhatsApp links, plus the contact page URL. Every lead also carries the Google Maps data: name, category, address, phone, website, rating, review count, coordinates and opening hours.

Already have a list of websites? Paste them into **Website URLs** and the actor scans those sites directly, without Google Maps.

Export the leads as CSV, Excel or JSON, send them to Google Sheets or your CRM, or pull them through the API. You pay only for the results.

### What data does Google Maps Email Extractor extract?

| Field | Description |
|---|---|
| `name`, `category`, `categories` | Business name and Google Maps categories |
| `address`, `street`, `city`, `postalCode`, `state`, `countryCode` | Address and its parts |
| `phone`, `phoneInternational` | Phone number shown on Google Maps (local and E.164 format) |
| `website`, `domain` | The business website listed on Google Maps and its domain |
| `rating`, `reviewCount` | Google rating and number of reviews |
| `latitude`, `longitude`, `plusCode` | Location |
| `openingHours`, `openStatus` | Opening hours per day and the current status line |
| `placeId`, `cid`, `googleMapsUrl` | Google identifiers and a link to the place |
| `emails` | Emails found on the business's website, lower-cased, deduplicated, addresses on the site's own domain first |
| `phones` | Phone numbers found on the website, normalized to E.164 (e.g. `+13033283060`) |
| `phonesUncertain` | Phone links that could not be validated for the business's country (kept as written) |
| `facebooks`, `instagrams`, `linkedIns`, `twitters`, `youtubes`, `tiktoks`, `pinterests`, `whatsapps` | Social profile links found on the website (profiles and company pages, not shares or single posts) |
| `contactPageUrl` | The website's contact page |
| `websiteStatus`, `websiteError` | How the visit went: `ok`, `noWebsite`, `socialProfileOnly`, `blockedByRobotsTxt`, `blocked`, `unreachable`, `httpError`, `notHtml`, `platformPage`, `skipped` |
| `websitePagesFetched`, `websitePages` | Which pages of the website were read |
| `contactsFound` | `true` when the website showed at least one email, phone or social profile (this is what the `contacts` event charges) |
| `searchTerm`, `searchLocation`, `rank`, `scrapedAt` | Which search found the place, its position in the list, and when |

### How to extract emails from Google Maps

1. Enter one or more **Search terms** (e.g. `dentist`, `roofing contractor`, `accountant`) and a **Location** (`Denver, CO`, `10001`, `Munich, Germany`).
2. Set **Max results** to cap the run (and the cost). Turn on **Only places with an email** if you only want leads that have an email.
3. Click **Start**. For each place the actor opens its website: the homepage plus up to 3 linked pages that usually hold contacts (Contact, Impressum/Imprint, About, Team). Change that with **Extra pages per website** (0-10).

You can also paste Google Maps place or search URLs, place IDs or CIDs instead of a search, or turn on **Deep search** to split a big city into a grid and go past the ~200 places one Google Maps list shows.

### Input example

```json
{
  "searchTerms": ["plumber", "dentist"],
  "location": "Denver, CO",
  "maxPlacesPerSearch": 50,
  "maxItems": 100,
  "maxPagesPerSite": 3
}
```

Contacts only, for your own list of websites:

```json
{
  "websites": ["https://www.applewoodfixit.com", "patsys.com"],
  "maxPagesPerSite": 3
}
```

### Output example

```json
{
  "type": "place",
  "placeId": "ChIJkWUJx22Ha4cRVVLq4AJk7Ng",
  "name": "Applewood Plumbing Heating & Electric",
  "category": "Plumber",
  "address": "5000 W 29th Ave, Denver, CO 80212",
  "city": "Denver",
  "countryCode": "US",
  "latitude": 39.7583004,
  "longitude": -105.0512657,
  "phone": "(303) 444-1633",
  "phoneInternational": "+13034441633",
  "website": "https://www.applewoodfixit.com/",
  "domain": "applewoodfixit.com",
  "rating": 4.8,
  "reviewCount": 17842,
  "openingHours": [{ "day": "Monday", "hours": "6 AM–10 PM" }],
  "googleMapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJkWUJx22Ha4cRVVLq4AJk7Ng",
  "websiteStatus": "ok",
  "websitePagesFetched": 4,
  "emails": ["custservice@applewoodfixit.com"],
  "phones": ["+13033283060"],
  "facebooks": ["https://www.facebook.com/ApplewoodPHE"],
  "instagrams": ["https://www.instagram.com/applewoodphe"],
  "linkedIns": ["https://www.linkedin.com/company/applewood-plumbing-heating-&-electric"],
  "youtubes": ["https://www.youtube.com/user/ApplewoodPHE"],
  "contactPageUrl": "https://www.applewoodfixit.com/contact-us/",
  "contactsFound": true,
  "searchTerm": "plumber",
  "rank": 1,
  "scrapedAt": "2026-10-12T17:43:06Z"
}
```

### How many places get an email?

It depends on the trade and the country, because the actor only returns what a business shows on its own site. In our test runs (October 2026, 320 places in 7 searches, 3 extra pages per site): 97% of places had a website and 84% of those websites could be read. **62% of places with a website had at least one email**: electricians 50%, plumbers 51%, accountants 58%, dentists 59%, roofers 70%, real estate agencies 73% in US cities, and 78% of Munich dentists (German sites must publish an Impressum). 81% had at least one email, phone or social profile. Many US service businesses only use a contact form; those places still come with their Google Maps phone and their social profiles.

### Does it guess or verify emails?

No. Every email in the output is written on the business's own website: in `mailto:` links, in the page text (including spellings like `info (at) firma (dot) de`), in Cloudflare-protected email links (decoded), in the site's structured data, or as an address on the site's own domain in the page source. Nothing is guessed from name patterns and no verification service is called. Placeholders from website templates (`info@mysite.com`, `user@domain.com`, Wix/Sentry ids, `image@2x.png`) are removed.

### Monitor new places on a schedule

Turn on **Only new places** and run the same search on a [Schedule](https://docs.apify.com/platform/schedules) (daily or weekly). The actor remembers which places earlier runs of this search delivered and outputs only places that are new since then, so each scheduled run gives you fresh leads. Places that were already delivered are not visited again and not charged. Save your input as a task, then add the task to a schedule in Apify Console; give each watch its own **Monitoring key** if you run several schedules on the same search. New places carry `"changeType": "new"`.

### Pricing

Pay per event, plus the small platform usage of your run:

| Event | Price | When |
|---|---|---|
| `place` | $0.003 | each Google Maps place saved |
| `website` | $0.003 | each site from the **Website URLs** input saved (no Google Maps) |
| `contacts` | $0.002 | only when the website showed at least one email, phone number or social profile |

1,000 places where 800 websites had contacts cost $3.00 + $1.60 = $4.60, plus the platform usage of the run: we measured $0.03-0.06 per 1,000 places with 3 extra pages per site. Places whose website had nothing, or could not be loaded, cost only the `place` event; with **Only places with an email** on, places without an email are not charged at all.

### Does it need a login, an API key or a special proxy?

No. It reads public pages only: Google Maps through Apify's datacenter proxy (no Google API key) and each business's public website. It never logs in anywhere and does not open LinkedIn, Facebook or Instagram; it only collects the links the website shows. It respects each website's `robots.txt` for the pages it reads, skips PDFs, images and other non-HTML files, and reads at most a few pages per site.

### Why does a place have no email?

Common reasons, all visible in `websiteStatus`: the business has no website (`noWebsite`) or only lists a Facebook page (`socialProfileOnly`); the site uses a contact form instead of an email (`ok` with empty `emails`); the site blocks automated visitors (`blocked`) or disallows them in robots.txt (`blockedByRobotsTxt`); the site was down or too slow (`unreachable`). Sites that build their content only in the browser with JavaScript can also show less than a browser does. Raising **Extra pages per website** helps on sites with many sub-pages.

### Integrations and API

Use the results in Google Sheets, HubSpot, Zapier, Make, n8n or your own code with the Apify API and client libraries. See the **API** tab for ready-made snippets.

### Is it legal to scrape Google Maps and business websites?

The actor collects business information that companies publish themselves: their Google Maps listing and the contact details on their own websites (often a legal requirement, like the German Impressum). It does not log in, does not bypass access controls and respects robots.txt. Emails can still be personal data (e.g. `firstname@company.com`): you are responsible for using the data lawfully, for example under GDPR and anti-spam rules such as CAN-SPAM, before you contact anyone.

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

# Actor input Schema

## `searchTerms` (type: `array`):

What to search for on Google Maps, one term per line, e.g. 'dentist', 'plumber', 'marketing agency'. Each term is searched in the location below.

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

City, address, ZIP code or region to search in, e.g. 'Austin, TX', '10001' or 'Berlin, Germany'. Leave empty when you use coordinates or the location is part of the search term.

## `maxPagesPerSite` (type: `integer`):

Besides the homepage, open up to this many pages linked from it that usually carry contacts: Contact, Impressum/Imprint, About, Team. 0 = homepage only. More pages find more emails but take longer.

## `onlyWithEmail` (type: `boolean`):

Output only places (and websites) where at least one email was found. The others are dropped and not charged; the search goes on until 'Max results' places with emails are found or the search ends.

## `maxPlacesPerSearch` (type: `integer`):

Look at no more than this many places per search term. One Google Maps list ends after about 160-260 places; turn on deep search to go further in big areas.

## `maxItems` (type: `integer`):

Stop after this many results (places plus websites) in total. Keeps the cost predictable.

## `deepSearch` (type: `boolean`):

Split the location into a grid of smaller map views and search each one, to find more places than one Google list shows (a single list stops at about 160-260). Places are deduplicated.

## `maxGridCells` (type: `integer`):

Number of map views the area is split into for deep search (4, 9, 16, 25, ...). More cells find more places in dense cities.

## `coordinates` (type: `string`):

Search around a point instead of a named location: 'latitude,longitude', e.g. '30.2672,-97.7431'. Used together with the zoom level.

## `zoom` (type: `integer`):

Map zoom for coordinates: 12 = whole city, 14 = district (default), 16 = a few streets.

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

Google Maps place URLs (google.com/maps/place/...), search URLs (google.com/maps/search/...), '?cid=' links or maps.app.goo.gl short links. Each place's website is visited for contacts.

## `placeIds` (type: `array`):

Google place IDs (ChIJ...), CIDs (the number in '?cid=') or feature ids (0x...:0x...).

## `websites` (type: `array`):

Business websites or domains to scan directly, e.g. 'https://acme.com' or 'acme.co.uk'. Each one becomes one result with its emails, phones and social profiles.

## `country` (type: `string`):

Two-letter country code Google localises results for (Google 'gl', e.g. us, gb, de). Also used to read local phone numbers on websites whose country is not known.

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

Language of names, categories and opening hours (Google 'hl' code, e.g. en, de, fr, es).

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only places that are new since then (fresh leads). Use with a Schedule; places already delivered are not visited again and not charged.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

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

Apify datacenter proxy (default) is used for Google Maps and for the business websites; no SERP or residential proxy needed.

## Actor input object example

```json
{
  "searchTerms": [
    "dentist"
  ],
  "location": "Austin, TX",
  "maxPagesPerSite": 3,
  "onlyWithEmail": false,
  "maxPlacesPerSearch": 100,
  "maxItems": 100,
  "deepSearch": false,
  "maxGridCells": 16,
  "country": "us",
  "language": "en",
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "searchTerms": [
        "dentist"
    ],
    "location": "Austin, TX",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/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 = {
    "searchTerms": ["dentist"],
    "location": "Austin, TX",
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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