# Google Maps · Leads by Category & City (`corent1robert/google-maps-city-leads`) Actor

Pick a trade and a list of cities. We collect matching Maps places and fill phone, website, published email and socials. Never invented. No login.

- **URL**: https://apify.com/corent1robert/google-maps-city-leads.md
- **Developed by:** [Corentin Robert](https://apify.com/corent1robert) (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 $1.05 / 1,000 place with emails

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 · Leads by Category & City

Pick a **trade** and a list of **cities**. Maps returns matching places. You get **one row per pin**: name, address, **phone**, **website**, a **published email**, and **social profiles** from the business website when they exist.

**No login. No API key. We never invent a mailbox, phone, or social URL.**

Need to enrich a CRM file you already have (name + street + city)? Use **[Google Maps · Emails & Phones by Search](https://apify.com/corent1robert/google-maps-search-scraper)** instead.

### Who is this for?

| You are… | Typical goal | Suggested setup |
|----------|--------------|-----------------|
| Local outbound / SDR | Build a city list for one trade | Category + 1–3 cities, website crawl on |
| Agency / lead gen | Same trade across several markets | Category + city list, 20–40 places per city |
| Franchise / network scout | See who already operates in a city | One category, raise places per city toward 80 |
| Market research | Count shops and who publishes a site | Overview view; email off if you only need pins |

**What you get by default:** name, address, Maps URL, public phone and website when Maps shows them, rating when visible. Website crawl then adds a published mailbox and socials when they exist.

**When to keep website crawl on:** Maps almost never shows an inbox or Instagram on the pin.

### Quick start

1. Open the Actor in [Apify Console](https://console.apify.com).
2. Choose a **Business category** (or type any Maps query, e.g. `plombier`).
3. Paste **cities**, one per row. **Paris, Lyon and Marseille** become one search per arrondissement (Maps would otherwise repeat downtown). **Free plan: 20 / run.**
4. Keep **Find email, phone and socials on the website** on. First runs: **20 places per city**. **Free plan: 20 / run.**
5. Click **Start**. Open the Dataset while it fills.

### Ready-made examples (published tasks)

These live in the **Examples** tab:

| Example | Best for |
|---------|----------|
| [Plumbers in Lyon](https://apify.com/corent1robert/google-maps-city-leads/examples/leads-lyon-plumbers) | Home services, first city |
| [Dentists in Paris](https://apify.com/corent1robert/google-maps-city-leads/examples/leads-paris-dentists) | Health / local CRM |
| [Restaurants in Bordeaux](https://apify.com/corent1robert/google-maps-city-leads/examples/leads-bordeaux-restaurants) | Hospitality outbound |
| [Hair salons in Marseille](https://apify.com/corent1robert/google-maps-city-leads/examples/leads-marseille-hair-salons) | Beauty leads, CRM view |

### What it extracts

| Category | Fields |
|----------|--------|
| Search | `searchQuery`, `searchCategory`, `searchCity`, `rankInResults` |
| Identity | `placeName`, `category` |
| Address | `fullAddress`, `streetAddress`, `postalCode`, `city` |
| Contact | `phone`, `phoneSource`, `website`, `email`, `emailSource` |
| Social | `facebookUrl`, `instagramUrl`, `linkedinUrl`, `tiktokUrl`, `youtubeUrl`, `twitterUrl`, `whatsappUrl` |
| Maps | `mapsUrl`, `latitude`, `longitude`, `plusCode` |
| Extra | `rating`, `reviewCount`, `openingSummary`, `openingHoursDetailed`, `permanentlyClosed` |

### Do we need every Google Maps category?

**No.** Google does not publish a stable, complete list of place types, and the list changes by language and country. Maps is a **search box**.

This Actor ships a **short list of common trades** as Console shortcuts. Anything else (French `plombier`, `cabinet dentaire`, a niche, a brand) goes in **Custom Maps query** and is searched as you typed it.

### Input

| Field | What it does |
|-------|----------------|
| `businessCategory` | Preset trade (plumber, dentist, restaurant, …). |
| `customCategory` | Optional. Overrides the preset. Any Maps query. No prefill. |
| `cities` | One city per row. Paris, Lyon, Marseille → arrondissements unless you turn that off. |
| `splitLargeCities` | Default on. One Maps search per arrondissement on those three cities. |
| `maxPlacesPerCity` | Scroll depth **per Maps area** (**1–80**, default **20**). |
| `enrichFromWebsite` | Published email, missing phone, socials on the business site / Facebook page (default on). |

**Free plan: 20 places / run.** Paid plans follow PPE / your max charge.

API-only: `maxPlaces` (run ceiling, **0** = no cap), `mapsCountry`, `placeDetailScrape`, `searchConcurrency` (default **8**).

#### Example — plumbers in two cities

```json
{
  "businessCategory": "plumber",
  "cities": ["Lyon", "Nantes"],
  "maxPlacesPerCity": 20,
  "enrichFromWebsite": true
}
```

#### Example — French query

```json
{
  "businessCategory": "custom",
  "customCategory": "plombier",
  "cities": ["Lyon"],
  "maxPlacesPerCity": 40,
  "enrichFromWebsite": true
}
```

### Output example

```json
{
  "searchQuery": "plumber Lyon",
  "searchCategory": "plumber",
  "searchCity": "Lyon",
  "rankInResults": 1,
  "placeName": "Example Plumbing Lyon",
  "fullAddress": "10 Rue de la République, 69001 Lyon",
  "streetAddress": "10 Rue de la République",
  "postalCode": "69001",
  "city": "Lyon",
  "phone": "04 00 00 00 00",
  "phoneSource": "maps",
  "website": "https://example-plombier.fr",
  "email": "contact@example-plombier.fr",
  "emailSource": "business-website",
  "category": "Plumber",
  "mapsUrl": "https://www.google.fr/maps/place/..."
}
```

Download in JSON, CSV, Excel, or HTML from the Dataset tab.

### How it works

1. Build one Maps search per city: **category + city**.
2. Open Google Maps (country inferred from the cities; France if unclear) — default **8** searches at once.
3. Scroll the sidebar up to `maxPlacesPerCity` (max **80**).
4. If website crawl is on, fetch published email, missing phone, and social URLs.
5. Places still without email but with a Facebook page go to [Facebook Pages · Email, Phone & Website](https://apify.com/corent1robert/facebook-page-contact-scraper) (needs **Full permissions**).
6. Charge **place-with-email**, **place-with-phone**, or **place-without-phone**, then push the row.

### How much does it cost to scrape Google Maps?

Same PPE as the name + address Actor. Browser compute is billed on top.

| Event | Free / 1k | Bronze | Silver | Gold | Platinum | Diamond |
|-------|-----------|--------|--------|------|----------|---------|
| Place with email (primary, socials included) | **$1.50** | $1.35 | $1.20 | $1.05 | $0.90 | **$0.75** |
| Place with phone, no email | $0.50 | $0.45 | $0.40 | $0.35 | $0.30 | $0.25 |
| Place without phone | $0.20 | $0.18 | $0.16 | $0.14 | $0.12 | $0.10 |
| Actor start | $0.00005 / GB | same | same | same | same | same |

| Scenario | Approx. cost (Free PPE) |
|----------|-------------------------|
| 20 places with phone only | ~$0.01 + start + compute |
| 20 places with email | ~$0.03 + start + compute |
| 1,000 places with phone | **$0.50** + start + compute |
| 1,000 places with email + socials | **$1.50** + start + compute |

#### Is scraping Google Maps free?

On the **Apify Free plan** you get **20 places per run**. Upgrade for bulk.

### Is it legal to scrape Google Maps?

This Actor only reads **public** Maps results, **public** business websites, and **public** Facebook pages. You must have a lawful basis (GDPR / ePrivacy) for your use case. See [Apify's guide to web scraping legality](https://blog.apify.com/is-web-scraping-legal/).

### Limits

- **One trade × many cities**, not a name + address match. For a known shop, use the [name + address Actor](https://apify.com/corent1robert/google-maps-search-scraper).
- **Up to 80 places per Maps area** (city or arrondissement). Paris = 20 searches, not 20× the same downtown list.
- **Empty `email`** means none was published. We never guess `contact@domain`.
- **Facebook nested run** needs **Full permissions**.

### FAQ

**Why is email empty?** We only keep an address published on the business website or Facebook page.

**Why is website empty?** Maps did not show a site on that pin. We open each result (list click, or the place URL if the list click misses) and wait for **that** pin’s **Site internet** chip. We never invent a URL.

**Can I search outside France?** Yes. Country is inferred from city names. Override with API `mapsCountry`.

**How do I export for CRM?** Dataset view **Outreach / CRM**.

### Local development

```bash
cd google-maps-city-leads
npm install
npm test
apify run --purge --input-file=./.actor/INPUT.json
```

### Also available

**[Google Maps · Emails & Phones by Search](https://apify.com/corent1robert/google-maps-search-scraper)** — paste name + address to match one pin.

**[PagesJaunes · Email & Website Scraper from URL](https://apify.com/corent1robert/pagesjaunes-local-leads)** — French directory URLs to emails, phones and SIRET.

### Support

Contact <corentin@outreacher.fr> if you need a custom scraper or a tailored trade mapping.

# Actor input Schema

## `businessCategory` (type: `string`):

What you would type in Google Maps **before** the city. Presets are shortcuts. Anything else belongs in **Custom Maps query**.

## `customCategory` (type: `string`):

Overrides the category above. Example: plombier, cabinet dentaire, Italian restaurant. Leave empty to use the preset.

## `cities` (type: `array`):

City or area names, one per line. **Paris, Lyon and Marseille** are split into arrondissements so Maps is not stuck on the centre. You can also paste `Lyon 3e` yourself.

Examples: Lyon · Paris · Nantes

## `splitLargeCities` (type: `boolean`):

On large French cities, Maps repeats the same downtown pins. We run one search per arrondissement instead (Paris 20, Lyon 9, Marseille 16). Duplicate pins are dropped. Turn off to search the city name only.

## `maxPlacesPerCity` (type: `integer`):

Maps shows a sidebar list, not an infinite directory. **20** is a safe first run. **40–80** is a fuller city scrape. **80** is the practical ceiling for one search.

## `enrichFromWebsite` (type: `boolean`):

When Maps publishes a website, fetch it over HTTP(S). Keep a published mailbox, fill phone only if Maps has none, and keep social URLs when they are on the page. If the pin has a Facebook page and still no email, we run Facebook Pages · Email, Phone & Website. Never invented.

## Actor input object example

```json
{
  "businessCategory": "plumber",
  "cities": [
    "Lyon"
  ],
  "splitLargeCities": true,
  "maxPlacesPerCity": 20,
  "enrichFromWebsite": true
}
```

# Actor output Schema

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

All extracted places — open or download from the Dataset tab.

## `overview` (type: `string`):

Name, city, phone, website, Maps link.

## `outreachCrm` (type: `string`):

Email, phone, website, socials for CRM import.

## `runLog` (type: `string`):

Progress lines while Maps searches run.

# 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 = {
    "businessCategory": "plumber",
    "cities": [
        "Lyon"
    ],
    "splitLargeCities": true,
    "maxPlacesPerCity": 20,
    "enrichFromWebsite": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("corent1robert/google-maps-city-leads").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 = {
    "businessCategory": "plumber",
    "cities": ["Lyon"],
    "splitLargeCities": True,
    "maxPlacesPerCity": 20,
    "enrichFromWebsite": True,
}

# Run the Actor and wait for it to finish
run = client.actor("corent1robert/google-maps-city-leads").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 '{
  "businessCategory": "plumber",
  "cities": [
    "Lyon"
  ],
  "splitLargeCities": true,
  "maxPlacesPerCity": 20,
  "enrichFromWebsite": true
}' |
apify call corent1robert/google-maps-city-leads --silent --output-dataset

```

## MCP server setup

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

```

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/IBq6LsRm0OAtRlIu2/builds/UpfWN1hdtVtvOf4qh/openapi.json
