# Google Maps Leads Scraper (`keklik/google-maps-leads-scraper`) Actor

Google Maps scraper for leads: export businesses by keyword and city with phone, website, address, rating, hours, and optional emails and social links from their websites. No login.

- **URL**: https://apify.com/keklik/google-maps-leads-scraper.md
- **Developed by:** [Keklik Technologies](https://apify.com/keklik) (community)
- **Categories:** Lead generation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.10 / 1,000 places

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?

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

**Google Maps Leads Scraper** lets you **scrape businesses from Google Maps** by keyword and location and **export them as leads** to JSON, CSV or Excel: **name, category, address, phone, website, rating, review count, opening hours, coordinates and Place ID**, plus, if you turn them on, **emails and social profiles from each business's website**, the review breakdown per star, and the latest reviews. Type "dentist" and "Marseille, France", press Start, and get every dentist in the city. **No Google account, no login and no API key** are needed.

### What data can you extract from Google Maps?

Each place becomes one row with up to 53 fields:

| Group | Fields |
|---|---|
| **Business** | Name, main category and all categories, description, owner name, claimed profile flag, temporarily or permanently closed flags |
| **Contact** | Phone (as shown and in international format), website, order-online links, reservation link |
| **Location** | Full address, street, city, postal code, state or region, country code, neighborhood, latitude and longitude, time zone |
| **Reputation** | Star rating, number of reviews, link to the reviews |
| **Hours and attributes** | Opening hours per day, the About tab (accessibility, payments, amenities, service options...), main photo and photo count |
| **IDs** | Place ID, CID, Knowledge Graph ID, Google Maps link |
| **Place details** (optional) | Reviews per star (1 to 5), review keywords with counts, delivery and takeout hours, plus code |
| **Emails and social profiles** (optional) | Emails and phone numbers found on the website; Facebook, Instagram, LinkedIn, X, TikTok, YouTube and Pinterest profiles |
| **Reviews** (optional) | The place's latest reviews: stars, text, date, owner response, reviewer |
| **Run details** | The search term and location the place was found with, its rank, the input, and when it was scraped |

### Use cases

- **Lead generation**: build lists of local businesses (dentists, plumbers, restaurants, agencies) with phone numbers, websites and emails for sales outreach.
- **Web design and marketing agencies**: find businesses without a website, with a low rating, or with an unclaimed profile.
- **Market research**: count and map every business of a type in a city, with ratings and review counts, to size a market or find gaps.
- **Competitor analysis**: compare competitors' ratings, reviews, hours and services across locations.
- **Local SEO**: check categories, claimed status and review counts for the businesses that rank in your area.
- **Data enrichment**: get Place IDs, coordinates and opening hours for a list of places.

### How to scrape Google Maps

1. Open **Google Maps Leads Scraper** in Apify Console and go to the Input tab.
2. In **Search terms**, type what you are looking for, one per line: `dentist`, `italian restaurant`, `plumber`.
3. In **Locations**, type where, one per line: `Marseille, France`, `Brooklyn, NY`, `75011 Paris`.
4. Optionally set **Max places per search**, filters such as **Min rating** or **Website**, and the extras: **Place details**, **Emails and social profiles from websites**, **Reviews per place**.
5. Click **Start** and watch the places appear in the Output tab.
6. Download the data as JSON, CSV, Excel, XML or HTML, or read it through the Apify API.

### Input

| Field | What it does |
|---|---|
| **Search terms** | What to search for, one per line. Each term is searched in every location. |
| **Locations** | Where to search: a city, district, postal code or region. The scraper finds the area's outline and splits it into map tiles, as many as it needs, so it gets far more than the 200 places a single Google Maps search shows. Leave it empty to search the terms as typed ("pizza in Brooklyn"), which gives at most about 200 places per term. |
| **Google Maps URLs** | Optional. Search URLs (`https://www.google.com/maps/search/dentist/@43.29,5.37,14z`) are searched over the map area they show. Place URLs, `?cid=` links, `maps.app.goo.gl` links and Place IDs give one row each. |
| **Max places per search** | Stops each search term and location pair after this many places. Leave it empty to cover the whole area. |
| **Language** | Language code (`en`, `fr`, `de`, `pt-BR`...) for categories, opening hours, attributes and reviews. |
| **Min rating**, **Min reviews** | Keep only places rated at least 2.0+ to 4.5+, or with at least this many reviews. |
| **Website**, **Phone** | Keep only places with, or only places without, a website or a phone number. |
| **Skip closed places** | Leave out places marked temporarily or permanently closed. |
| **Skip unrelated places** | On by default. Leaves out the places Google adds around the real matches once an area runs out of them, such as osteopaths in a dentist search. |
| **Place details** | Adds reviews per star, review keywords, delivery and takeout hours and the plus code. One extra request per place. |
| **Emails and social profiles from websites** | Visits each place's website (home page and up to 3 contact, about or legal pages) for emails, phone numbers and social profiles. |
| **Reviews per place**, **Reviews sort** | Adds up to this many reviews to each place, newest first or in another order. |
| **Max results** | Stops the whole run after this many places. |

```json
{
    "searchTerms": ["dentist", "orthodontist"],
    "locations": ["Marseille, France"],
    "maxPlacesPerSearch": 500,
    "websiteFilter": "with",
    "contactDetails": true
}
```

### Output

A shortened row from a real run:

```json
{
    "name": "Maison Charlie",
    "category": "Restaurant",
    "categories": ["Restaurant", "Chocolate cafe", "Coffee shop", "Espresso bar", "French restaurant", "Patisserie", "Tea house"],
    "address": "38 Rue Caisserie, 13002 Marseille, France",
    "street": "38 Rue Caisserie",
    "city": "Marseille",
    "postal_code": "13002",
    "country_code": "FR",
    "lat": 43.296977,
    "lng": 5.367349,
    "phone": "+33 4 91 52 44 82",
    "phone_unformatted": "+33491524482",
    "website": "https://maisoncharlie.fr/",
    "rating": 4.8,
    "reviews_count": 1169,
    "opening_hours": [
        {"day": "Monday", "hours": "8 AM–6 PM"},
        {"day": "Saturday", "hours": "9 AM–4 PM"},
        {"day": "Sunday", "hours": "Closed"}
    ],
    "is_claimed": true,
    "about": {"Accessibility": {"Wheelchair accessible entrance": false}},
    "timezone": "Europe/Paris",
    "place_id": "ChIJi_GO28PAyRIR8lA2xszYZW0",
    "cid": "7882945096792297714",
    "kgmid": "/g/11cjg9nkyv",
    "url": "https://www.google.com/maps?cid=7882945096792297714",
    "reviews_distribution": {"one_star": 10, "two_stars": 10, "three_stars": 21, "four_stars": 172, "five_stars": 956},
    "review_tags": [{"tag": "focaccia", "count": 60}],
    "search_term": "restaurant",
    "search_location": "Marseille, France",
    "rank": 2,
    "scraping_time": "2026-10-16T16:02:11+00:00"
}
```

You can export the dataset to **JSON, CSV, Excel, XML and HTML** from the Output tab or through the API. The Output tab's overview table shows each place's name, category, address, phone, website, rating, review count, emails and Google Maps link.

#### What to know about the data

- **Coverage**: one Google Maps search shows at most 200 places, and fewer the wider the map. For a location, the scraper searches the whole area first, then splits it in four and searches each quarter again wherever many matching places remain, until the tiles come back nearly empty. In our test, "dentist" in Marseille gave 871 places from 145 searches, where a single search shows 200.
- **Area edges**: the area is the location's bounding box, a rectangle, so towns at its edges can appear too. Use the `city` or `postal_code` column to narrow the list down.
- **Unrelated places**: when an area runs out of real matches, Google fills the list with nearby places of other kinds. **Skip unrelated places** keeps the places whose categories match the best results of the first search (or whose name has the search term, for brand searches such as "Starbucks").
- **Search in the local language for best results**: Google matches your term against business categories. English works almost everywhere; if a search returns little, try the term in the local language (`dentiste`, `Zahnarzt`).
- **Emails** come from the business's own website, not from Google. Many small businesses don't publish one, and some websites block visitors or load their content with JavaScript only, so expect emails for part of the places.
- **No duplicates**: a place appears only once per run, even when several searches or tiles find it.

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

Google Maps Leads Scraper uses pay-per-event pricing: you pay per place in the dataset, plus each extra you turn on.

| Event | Free | Bronze | Silver | Gold |
|---|---|---|---|---|
| **Place** (every row) | $3.00 | $2.25 | $1.50 | $1.10 |
| **Place details** (when turned on) | $1.50 | $1.50 | $1.10 | $0.80 |
| **Emails and social profiles** (only places where the website gave something) | $1.50 | $1.50 | $1.10 | $0.80 |
| **Review** (each review added with Reviews per place) | $0.40 | $0.35 | $0.28 | $0.20 |

Prices are per 1,000 events.&#x20;

For example, 1,000 dentists with emails and social profiles cost at most $4.50 on the Free plan and $1.90 on Gold.  Filters are free: you don't pay for using them, and places they remove cost nothing.

Use **Max places per search** and **Max results** to cap what a run can spend.

### Integrations and API

Run Google Maps Leads Scraper from your own code with the [Apify API](https://docs.apify.com/api/v2). The Actor ID is `keklik/google-maps-leads-scraper`.

Python, with `apify-client`:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("keklik/google-maps-leads-scraper").call(run_input={
    "searchTerms": ["plumber"],
    "locations": ["Brooklyn, NY"],
    "contactDetails": True,
})
for place in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(place["name"], place["phone"], place["emails"])
```

Node.js, with `apify-client`:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('keklik/google-maps-leads-scraper').call({
    searchTerms: ['plumber'],
    locations: ['Brooklyn, NY'],
    contactDetails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

You can also:

- **Schedule** runs to refresh your lead lists every week or month.
- Use **webhooks** to get notified, or trigger your own pipeline, when a run finishes.
- Connect the results to **Google Sheets, HubSpot, Make, Zapier**, Slack and other tools through Apify integrations.
- Let **AI agents** run the scraper through the [Apify MCP server](https://mcp.apify.com), for example from Claude or Cursor.

### FAQ

#### Is it legal to scrape Google Maps?

The scraper collects only public business information that Google Maps shows to visitors who are not logged in. Some of it can still be personal data, such as a sole trader's name, phone number or email, or reviewers' names. Personal data is protected by laws such as the GDPR in the EU, so make sure you have a lawful reason to collect and use it, especially for cold outreach. If in doubt, ask a lawyer.

#### Do I need a Google account or a Google Maps API key?

No. You don't need a Google account, a login, cookies or a Places API key, and there is no proxy to set up.

#### How many places can I get?

As many as Google Maps has for your search in the area. A single Google Maps search stops at 200 places, but the scraper splits large areas into map tiles, so a city-wide search goes well beyond that. For a whole country, list its cities or regions as locations.

#### Why did some inputs fail?

Inputs that fail are listed, with the reason, in the `FAILED` record of the run's key-value store (Storage tab), not in the dataset:

- `invalid_input`: not a Google Maps search or place URL.
- `location_not_found`: Google Maps doesn't know the location. Check the spelling, or add the country.
- `not_found`: the place doesn't exist or was removed.
- `no_matching_places`: the search found no places, or none passed your filters.
- `blocked`: Google refused every attempt. Run it again later.
- `google_error`: Google answered with an error the scraper doesn't recognise. Please report it.

#### Can I get all the reviews of the places?

**Reviews per place** adds a few reviews to each row. For every review, one row per review, feed the `url` or `place_id` column into [Google Maps Reviews Scraper](https://apify.com/keklik/google-maps-reviews-scraper).

#### Are the emails verified?

No. Emails are collected from the business's own website and checked for obvious junk (image names, placeholder addresses, code libraries), but not verified with a mail server.

### Related scrapers

- [Google Maps Reviews Scraper](https://apify.com/keklik/google-maps-reviews-scraper): every review of the places you found, one row per review.

### Support

Found a bug or need a field that isn't there? Open an issue on the Issues tab and we'll look into it. Need a scraper for another site or a custom version of this one? Get in touch through the Issues tab too.

# Actor input Schema

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

What to search for on Google Maps, one per line, as you would type it: dentist, italian restaurant, plumber. Each term is searched in every location below.

## `locations` (type: `array`):

Where to search, one per line: a city, district, postal code or region (Marseille, France; Brooklyn, NY; 75011 Paris). The scraper splits the area into as many map tiles as it needs, so it goes well past the 200 places one Google Maps search shows. Leave empty to search the terms as typed ("pizza in Brooklyn"), which gives at most about 200 places per term.

## `urls` (type: `array`):

Optional. Google Maps search URLs (https://www.google.com/maps/search/dentist/@43.29,5.37,14z), which are searched over the map area they show, and place URLs (https://www.google.com/maps/place/..., ?cid= links, maps.app.goo.gl links or Place IDs), which give one row each.

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

Stop each search term and location pair (or search URL) after this many places. Leave empty to cover the whole area.

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

Language code (en, fr, de, es, pt-BR...) for categories, opening hours, attributes and reviews.

## `minRating` (type: `string`):

Keep only places rated at least this many stars. Google applies it to the search itself.

## `minReviews` (type: `integer`):

Keep only places with at least this many reviews.

## `websiteFilter` (type: `string`):

Keep places by whether they list a website. Places without one are often prospects for web agencies.

## `phoneFilter` (type: `string`):

Keep places by whether they list a phone number.

## `skipClosed` (type: `boolean`):

Leave out places marked temporarily or permanently closed.

## `skipUnrelated` (type: `boolean`):

Leave out the places Google adds around the real matches when an area runs out of them, such as osteopaths in a dentist search. A place matches when it has one of the main categories of the search's best results, or the search term in its name. Turn it off to keep everything Google returns.

## `placeDetails` (type: `boolean`):

Open each place for the fields its page adds: reviews per star (1 to 5), review keywords with counts, delivery and takeout hours, and plus code. One extra request per place.

## `contactDetails` (type: `boolean`):

Visit each place's website (home page and up to 3 contact, about or legal pages) for email addresses, phone numbers and Facebook, Instagram, LinkedIn, X, TikTok, YouTube and Pinterest profiles.

## `reviewsPerPlace` (type: `integer`):

Add up to this many reviews to each place, in a latest_reviews list. Off (0) by default. For many reviews per place, use Google Maps Reviews Scraper on the place URLs.

## `reviewsSort` (type: `string`):

Which reviews "Reviews per place" picks.

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

Stop the whole run after this many places, across all searches. Leave empty for no limit.

## Actor input object example

```json
{
  "searchTerms": [
    "dentist"
  ],
  "locations": [
    "Marseille, France"
  ],
  "maxPlacesPerSearch": 50,
  "language": "en",
  "minRating": "",
  "websiteFilter": "any",
  "phoneFilter": "any",
  "skipClosed": false,
  "skipUnrelated": true,
  "placeDetails": false,
  "contactDetails": false,
  "reviewsPerPlace": 0,
  "reviewsSort": "newest"
}
```

# Actor output Schema

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

One row per place: name, category, address, phone, website, rating, hours and the extras you turned on.

# 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"
    ],
    "locations": [
        "Marseille, France"
    ],
    "maxPlacesPerSearch": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("keklik/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 = {
    "searchTerms": ["dentist"],
    "locations": ["Marseille, France"],
    "maxPlacesPerSearch": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("keklik/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 '{
  "searchTerms": [
    "dentist"
  ],
  "locations": [
    "Marseille, France"
  ],
  "maxPlacesPerSearch": 50
}' |
apify call keklik/google-maps-leads-scraper --silent --output-dataset

```

## MCP server setup

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