# Google Maps Email Scraper 📇 Contact Extractor (emails, phones) (`tagadanar/google-maps-contact-extractor`) Actor

Search Google Maps and get emails, phone numbers and social profiles for every business that comes back. Each business's own website is read for you: homepage, contact page, mentions légales or Impressum. One flat row per business, empty fields where the data genuinely is not there.

- **URL**: https://apify.com/tagadanar/google-maps-contact-extractor.md
- **Developed by:** [Tagada Data](https://apify.com/tagadanar) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.80 / 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 📇 Contact Extractor (emails, phones)

Search Google Maps the way you would in the app, and get **emails, phone
numbers and social profiles** for every business that comes back. Each
business's own website is read for you: the homepage, and then the pages that
actually carry a contact, which on a French site is "mentions légales" and on a
German one is the Impressum.

One flat row per business. Empty where the data genuinely is not there, never a
guessed address.

### Google Maps scraper vs this actor

| | plain Maps scraper | Google Maps Contact Extractor |
|---|---|---|
| Business name, address, phone, website | yes | yes |
| Rating, category, coordinates, place id | yes | yes |
| Advertisements filtered out of the results | usually not | yes, always |
| Email address from the business's own site | no | yes |
| Extra phone numbers published on the site | no | yes, in international format |
| LinkedIn, Facebook, Instagram and X profiles | no | yes, real profiles only |
| Contact and legal pages read, not just the homepage | no | yes, up to 3 per site |
| Says why a business has no email | no | yes, `websiteStatus` |

A Maps scraper gives you the shopfront. This one gives you the mailbox you can
actually write to.

### What you get per business

| Field | What it is |
|---|---|
| `name`, `category`, `categories` | The business as Google lists it |
| `address`, `street`, `city`, `postalCode`, `country` | Split out, ready for a mail merge |
| `phone`, `phoneNational` | The number Google holds, international format first |
| `website`, `websiteDomain` | Tracking parameters stripped off |
| `rating`, `latitude`, `longitude`, `placeId`, `cid`, `googleMapsUrl` | The usual Maps fields |
| `email` | The best address found on the site, general mailbox first |
| `emails` | Every address found, deduplicated and lowercased |
| `websitePhones` | Numbers published on the site, as they are written there |
| `websitePhonesE164` | The same numbers as `+33…`, when they could be normalised confidently |
| `phoneSources` | `tel-link` or `page-text`, one entry per phone |
| `linkedinUrl`, `facebookUrl`, `instagramUrl`, `twitterUrl` | One profile per network |
| `socialProfiles` | Every distinct profile found |
| `contactsFound` | `true` when the website gave up at least one contact |
| `websiteStatus` | `ok`, `no-contact-found`, `unreachable`, `unreadable` or `no-website` |
| `websiteError` | The reason a website could not be read |
| `pagesCrawled`, `pagesCrawledUrls` | Exactly which pages were read for that row |

Fields are empty when the data genuinely is not there. Nothing is guessed.

### How much does it actually find

Straight answer, because it decides whether this is useful to you.

Measured on 2026-08-19 against three real searches (plumbers in Lyon, dentists
in Manchester, Zahnarzt in Berlin): **80 businesses, 77 of which had a website,
75 distinct websites**.

| | France | United Kingdom | Germany | All |
|---|---|---|---|---|
| Websites read successfully | 28 of 29 | 19 of 19 | 19 of 19 | 73 of 75 |
| At least one email | 59% | 68% | **100%** | **73%** |
| At least one phone from the site | 86% | 100% | 100% | 91% |
| At least one social profile | 28% | 84% | 53% | 48% |
| Any contact at all | 90% | 100% | 100% | **93%** |

Per delivered row, counting the businesses with no website at all: **71% came
back with an email address** and 90% with some contact from the website.

Germany hits 100% because the Impressum is a legal requirement and it always
carries a mailbox. France is the hardest of the three: a good number of small
trades publish a contact form and a mobile number and no address at all.

Two things worth knowing before you buy:

- **Averages 1.4 pages per website.** It reads the homepage, and only goes
  looking further when there is no address on it. That is deliberate: it is
  somebody else's small business site.
- **This trade mix is website-heavy.** Plumbers and dental practices nearly all
  have a site. Search market stalls or barbers and the "no website" share will
  be much larger, and those rows come back with their Maps details only.

### What is deliberately not extracted

- **Addresses assembled by JavaScript.** Some sites build `info@…` from string
  fragments in a script. Reading those means running the page, which this actor
  does not do. Written-out obfuscations are covered: `name [at] domain [dot] fr`,
  `(at)`, `{at}`.
- **Share buttons.** A link to `facebook.com/sharer` is not the business's
  Facebook page, and a link to `instagram.com/p/…` is a post, not a profile.
  Only real profile URLs are kept.
- **Form placeholders.** `votre@email.fr`, `you@example.com` and the rest of the
  theme defaults are filtered out, along with retina image artefacts like
  `logo@2x.png`, which parse as perfect email addresses and are not.
- **Numbers scraped from running text that could not be checked.** A phone in a
  `tel:` link is trusted as published. A number found in the page text is kept
  only when it normalises to a valid international number for that country, so a
  SIRET or a VAT number never turns up in the phone column.

### Use it for

- **Cold email lists.** Search a trade and a town, filter to rows where `email`
  is set, and you have a list nobody had to be paid to type up.
- **Multichannel prospecting.** `linkedinUrl` plus `email` plus a phone in one
  row, so an outreach sequence has all three touchpoints from the start.
- **Filling gaps in a CRM.** Search the same trade and town you already sell to,
  join on `phone` or `websiteDomain`, and take the emails you were missing.
- **Agency prospecting.** Filter to `websiteStatus` of `no-website` for
  businesses with nothing to sell them but a website, or to
  `no-contact-found` for the ones whose site is impossible to contact through.
- **Local market research.** Every row carries the category, the rating and the
  social profiles, so you can see who is actually marketing themselves.

### Input

Three fields. Nothing else to configure.

```json
{
    "searchQueries": ["plumbers in Lyon"],
    "maxResults": 10,
    "country": "fr"
}
```

Several searches in one run, up to ten:

```json
{
    "searchQueries": [
        "plumbers in Lyon",
        "electricians in Villeurbanne",
        "roofers in Lyon"
    ],
    "maxResults": 60
}
```

A German list, where the Impressum makes the email hit rate highest:

```json
{
    "searchQueries": ["Zahnarzt in Berlin", "Zahnarztpraxis in Potsdam"],
    "maxResults": 120,
    "country": "de"
}
```

Put the town in the search text, the way you would when typing into Maps. The
`country` field is only there for the case where your search has no place in it
at all.

### Pricing

Pay per result, and only for the work that produced something.

| Event | When it fires | Price |
|---|---|---|
| `business-row` | One business delivered, with its Google Maps details | **$0.004** ($4 per 1,000) |
| `contact-found` | That business's website gave up an email, a phone or a social profile | **$0.005** ($5 per 1,000) |
| `actor-start` | Once per run | **$0.001** per run |

So 1,000 businesses of which 400 hand over a contact cost $4.00 + $2.00 + $0.001
\= **$6.00**. `business-row` and `contact-found` both carry the 10 / 20 / 30 %
Apify Store discount tiers (Bronze / Silver / Gold and up); `actor-start` is
flat.

A business whose website was down, or had no contact anywhere on it, bills as a
business row and nothing more. You never pay the contact rate for a row with no
contact in it.

Platform usage is included, no subscription. There is nothing else to pay, no
API key to buy, and no proxy to configure.

### FAQ

**Is this a Google Maps scraper?**
It contains one, and the Maps half of every row is what a good Maps scraper
gives you. The reason to use this one is that it then goes and reads the
business's website.

**How many pages of each website do you load?**
The homepage, and up to three more if the homepage carried no email address.
Ranked by how likely they are to hold a contact, which puts "mentions légales"
and "Impressum" first, then "contact", then "about". In the measured set the
average came out at 1.4 pages per site.

**Why does a business come back with no email?**
Three different reasons and the row tells you which. `no-website` means Google
holds no website for it. `unreachable` means the site did not answer, and
`websiteError` says what happened. `no-contact-found` means the site answered
and genuinely has no address, no phone and no social profile on the pages that
were read, which usually means a contact form and nothing else.

**Can I trust the phone numbers?**
The ones with `phoneSources` of `tel-link` are numbers the site owner marked as
dialable. The `page-text` ones were found in the page text and kept only after
they normalised to a valid international number for that country. Everything
that did not normalise was dropped rather than shipped with a caveat.

**Are advertisements included?**
No. Google returns two paid placements per page and every one of them is
filtered out before any website is read. You never pay for an advert.

**How many results can I get per search?**
Up to 120. Google Maps itself stops producing results for most local searches
somewhere between 60 and 120, and the actor stops as soon as Google does.

**Do you verify that the emails are deliverable?**
No. They are the addresses published on the businesses' own websites, as
published. No mailbox is probed and no address is guessed from a pattern.

**What about businesses that share a website?**
A chain with several branches in one search has its website read once, and every
branch row gets the result. Those rows carry `fromCachedDomain: true`.

### Related actors

- **Google Maps Verified Leads** if you want the same Maps search checked
  against the official company register instead: registered legal name,
  registration number, still trading or insolvent.
- **B2B Lead Verifier** if you already have a list and want it checked against
  SIRENE, Companies House and Handelsregister.
- **French Company Contacts** for contact details starting from a company name
  or a SIREN rather than from a map search.

### Support

Open an issue on the actor's
[Apify Store page](https://apify.com/tagadanar/google-maps-contact-extractor).
Issues are answered within a day or two.

***

google maps email extractor, google maps contact extractor, google maps scraper
with emails, business email finder, local business leads, email scraper from
website, contact details scraper, b2b lead generation, linkedin facebook
instagram profiles, mentions légales email, impressum email, cold email list,
lead list building, google maps leads

# Actor input Schema

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

What you would type into Google Maps, including the place: "plumbers in Lyon", "dentists in Manchester". Include the town or the district in the text, since that is what decides where the results come from. Up to 10 searches per run.

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

How many businesses to bring back for each search, at most 120. Google Maps itself runs out of results for most local searches somewhere between 60 and 120. Every business with a website has that website read for its contact details.

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

Optional. Runs the search from this country, the way results shift when you travel. Leave it empty and Google decides from your search text, which works well whenever the text names a town. Set it when your search is a bare trade with no place in it.

## Actor input object example

```json
{
  "searchQueries": [
    "plumbers in Lyon",
    "dentists in Manchester"
  ],
  "maxResults": 10,
  "country": "fr"
}
```

# Actor output Schema

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

One record per business: its Google Maps details plus the contacts found on its own website.

# 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": [
        "plumbers in Lyon"
    ],
    "maxResults": 10,
    "country": "fr"
};

// Run the Actor and wait for it to finish
const run = await client.actor("tagadanar/google-maps-contact-extractor").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": ["plumbers in Lyon"],
    "maxResults": 10,
    "country": "fr",
}

# Run the Actor and wait for it to finish
run = client.actor("tagadanar/google-maps-contact-extractor").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": [
    "plumbers in Lyon"
  ],
  "maxResults": 10,
  "country": "fr"
}' |
apify call tagadanar/google-maps-contact-extractor --silent --output-dataset

```

## MCP server setup

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

```

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/DZRYGbZadIEh6Dl9p/builds/Ne7tbWX4BxmgDk4aG/openapi.json
