# Google Maps Scraper Pro (`badr_xba/google-maps-scraper-pro`) Actor

Extract businesses from Google Maps by search term and location, URL or place ID: name, address, phone, website, rating, reviews count and distribution, opening hours, categories, coordinates, reviews, images and emails from websites. No API key. Pay per place.

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

## Pricing

from $1.20 / 1,000 scraped 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 Scraper Pro – Places, Reviews, Emails & Leads

**Google Maps Scraper Pro** extracts business data from Google Maps without an API key: names, addresses, phone numbers, websites, ratings, reviews count and distribution, opening hours, categories, coordinates, reviews, images, and **emails and social profiles from business websites**. Build lead lists, track competitors and analyse local markets in a few clicks — and **pay only for the places you get**, about **20% less** than the market reference.

It goes past Google Maps' 120-results limit: a city or a region is split automatically into smaller squares, and places found twice are delivered (and billed) only once.

### What can it do?

- 🎯 **Generate leads**: business names, phones, websites, emails and social profiles
- 📊 **Track competitors**: ratings, reviews count, reviews distribution and review topics
- 🗺️ **Analyse markets**: every pizzeria of a city, every dentist of a region, places with no website
- 🔗 **Scrape any input**: search term + location, Google Maps URLs, place IDs, custom GeoJSON areas
- ⭐ **Reviews and images** of each place (add-ons)
- 📤 Export to JSON, CSV, Excel, XML or HTML, or use the API, webhooks, Make, Zapier, n8n and the Apify MCP server

### What data does it extract?

| | | |
|---|---|---|
| 🔗 Title, category, all categories | 📍 Address, street, city, state, postal code, country | 🌍 Coordinates, plus code |
| ☎️ Phone (formatted and international) | 🌐 Website | ⭐ Rating (totalScore) and reviews count |
| 📊 Reviews distribution (1–5 stars) | 🏷️ Review topics (reviewsTags) | 🕒 Opening hours, open at scrape time |
| 🔒 Temporarily / permanently closed | ➕ Additional info (services, accessibility, payments…) | 📝 Description and owner description |
| 🆔 placeId, cid, fid, kgmid | 🖼️ Main image and images | 💬 Reviews: text, stars, date, reviewer, owner response, photos |
| 📧 Emails from the website | 👥 Facebook, Instagram, LinkedIn, X, YouTube, TikTok | 🛵 Order online link |

### How to use it in 3 steps

1. Type one or more **search terms** (restaurant, dentist, hotel…) — or directly `dentist in Chicago`, a Google Maps link or a place ID.
2. Type a **location** (New York, USA) — or use country / city / postal code, GPS coordinates or a custom GeoJSON area.
3. Click **Start**, then download your data (set the number of places and the add-ons first if you want).

Tip: use distinct search terms (*restaurant, bar, cafe*) rather than synonyms (*restaurant, restaurants*): synonyms only make the run longer.

#### Every input you can paste

The input is tidied silently — extra spaces, duplicates, bullets, a list pasted on one line — so you can paste straight from a spreadsheet.

| You paste | What happens |
|---|---|
| `restaurant` + location `New York, USA` | Restaurants in New York |
| `dentist in Chicago`, `plombier à Lyon` | The location is read from the search term |
| `restaurant, bar, cafe` on one line | Three search terms |
| `https://www.google.com/maps/search/pizza/@40.75,-73.98,14z` | "pizza" in the area shown on the map |
| `https://www.google.com/maps/search/pizza` (no coordinates) | "pizza" in your Location |
| Place link `…/maps/place/…`, `?cid=…`, `maps.app.goo.gl/…`, `?q=place_id:…` | That place with its details, one row |
| `ChIJ…` or `place_id:ChIJ…` | That place with its details, one row |
| Location `40.7128, -74.0060` or `10001, USA` | Searched around that point / postal code |

Entries that cannot be used (non-Google link, expired short link, unknown location, malformed place ID) are skipped with a warning and listed in `FAILED_INPUTS` — never billed, and they never make the run fail. An empty input runs the demo search (20 restaurants in New York) instead of failing.

#### Input example

```json
{
  "searchStringsArray": ["restaurant"],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "placeMinimumStars": "four",
  "website": "withWebsite",
  "scrapeContacts": true,
  "maxReviews": 5
}
```

The input uses the same field names as the most popular Google Maps scraper of the Store, so an existing input works as it is.

#### Output example

```json
{
  "searchString": "pizza",
  "rank": 2,
  "title": "SIMÒ PIZZA - MIDTOWN",
  "categoryName": "Pizza restaurant",
  "address": "…, New York, NY 10018",
  "city": "New York",
  "postalCode": "10018",
  "countryCode": "US",
  "phone": "(646) 000-0000",
  "website": "https://simopizza.com/",
  "totalScore": 4.7,
  "reviewsCount": 610,
  "reviewsDistribution": { "oneStar": 12, "twoStar": 6, "threeStar": 14, "fourStar": 60, "fiveStar": 518 },
  "plusCode": "Q26C+7F New York",
  "placeId": "ChIJ…",
  "cid": "9027705500530585310",
  "kgmid": "/g/11v3yv8j5s",
  "email": "info@simopizza.com",
  "openingHours": [{ "day": "Monday", "hours": "11 AM–11 PM" }],
  "reviews": [{ "stars": 5, "text": "…", "publishAt": "3 months ago", "isLocalGuide": true }]
}
```

Views: **Overview**, **Contacts (leads)** and **Reviews** (one review per row, also via `&view=reviews` in the API). Every run writes **RUN_SUMMARY** (places, add-ons billed, searches) and **FAILED_INPUTS** (inputs that gave nothing — never billed).

### Pricing

Pay per event. Platform usage is included. Add-ons are billed only when you turn them on, and only for what is actually delivered.

| Event | Free | Starter | Scale | Business |
|---|---|---|---|---|
| **Scraped place** | $3.20 / 1,000 | $2.40 / 1,000 | $1.60 / 1,000 | $1.20 / 1,000 |
| Add-on: Filter applied (per place, any filter) | $0.80 / 1,000 | $0.80 / 1,000 | $0.60 / 1,000 | $0.424 / 1,000 |
| Add-on: Place details (per detailed place) | $1.60 / 1,000 | $1.60 / 1,000 | $1.20 / 1,000 | $0.84 / 1,000 |
| Add-on: Company contacts (per website read) | $1.60 / 1,000 | $1.60 / 1,000 | $1.20 / 1,000 | $0.84 / 1,000 |
| Add-on: Review scraped | $0.40 / 1,000 | $0.40 / 1,000 | $0.296 / 1,000 | $0.208 / 1,000 |
| Add-on: Image scraped | $0.40 / 1,000 | $0.40 / 1,000 | $0.296 / 1,000 | $0.208 / 1,000 |
| Actor start | $0.00004 | $0.00004 | $0.00004 | $0.00004 |

Example: 1,000 restaurants with emails on the Free plan = $3.20 + $1.60 (contacts, only for places with a website) ≈ **$4.80**. Cap any run with the number of places or a maximum cost per run.

### Good to know (limits)

- **Reviews**: Google sends the most relevant reviews with each place — usually **about 5 per place**, which is the maximum here. For all reviews of a place, a dedicated reviews scraper is needed.
- **Images**: a few images per place (those Google sends with the place).
- **Not available** (accepted in the input, ignored, never billed): business leads enrichment, email verification, social media profile enrichment, AI competitor analysis, popular times, questions & answers, table reservation providers, places inside malls, web results, "scrape all places" without a search term.
- **One area per search**: a location must be given with search terms (or a custom area / search URL).
- Counts are a snapshot at the time of the run and can be lower than what you see when signed in to Google.

### FAQ

**How does it work?** It reads the same public data your browser loads when you search Google Maps, splits large areas into smaller squares, and opens the place detail only when you ask for details, reviews or images.

**Do I need a proxy?** No. The Actor reads Google directly and switches to Apify datacenter addresses only when Google slows it down.

**Can I scrape a whole country?** Yes: set the country (or a large region) as the location; the area is split automatically. Large areas take longer — start with a city to check the results.

**Is it legal?** It collects publicly available data. Reviews and contacts can include personal data protected by the GDPR: collect them only for a legitimate reason, and turn off reviewers' data if you do not need it.

**Can I get one specific business?** Yes: paste its Google Maps link (place link, `?cid=` or `maps.app.goo.gl`) or place ID into *Search term(s)* or *Google Maps URLs*. One row, with its details.

**Something does not work?** Open an issue on the **Issues** tab with your run ID.

### Troubleshooting

| Symptom | Fix |
|---|---|
| Fewer places than expected | Leave *Number of places* empty (all) and start with a city rather than a country |
| `Location not found on OpenStreetMap` in `FAILED_INPUTS` | Write `city, region, country` or use GPS coordinates |
| `Google refused the requests` | Run again, or set **Proxy** to the RESIDENTIAL group. Refused searches are not billed |
| A link is listed in `FAILED_INPUTS` | Expired short link or non-Google link: open it in a browser and paste the full Google Maps address |
| Only ~5 reviews per place | That is what Google sends with a place (see limits above) |
| Warning about unknown or unavailable input fields | Options from another Actor: they are ignored and never billed |

### Use it via API

Replace `YOUR_API_TOKEN` with your token from **Apify Console → Settings → API & Integrations**.

**Python** (`pip install apify-client`)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("badr_xba/google-maps-scraper-pro").call(run_input={
    "searchStringsArray": ["restaurant"],
    "locationQuery": "New York, USA",
    "maxCrawledPlacesPerSearch": 50,
    "scrapeContacts": True,
})
for place in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(place["title"], place["phone"], place.get("email"))
```

**JavaScript** (`npm install apify-client`)

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

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('badr_xba/google-maps-scraper-pro').call({
    startUrls: [{ url: 'https://www.google.com/maps/search/pizza/@40.75,-73.98,14z' }],
    maxCrawledPlacesPerSearch: 20,
    maxReviews: 5,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((p) => [p.title, p.totalScore, (p.reviews || []).length]));
```

**cURL** (waits for the run and returns the places)

```bash
curl -X POST "https://api.apify.com/v2/acts/badr_xba~google-maps-scraper-pro/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchStringsArray": ["dentist in Chicago"], "maxCrawledPlacesPerSearch": 20}'
```

### Integrations

- **Make, Zapier and n8n**: start a run and send the places to any of their apps.
- **Google Sheets, CRM** (HubSpot, Salesforce, Pipedrive…): from the Integrations tab, or through Make, Zapier, n8n or the API.
- **Webhooks**: notify your service or Slack when a run finishes; read `RUN_SUMMARY` for the numbers.
- **AI agents (MCP)**: add the Actor to Claude, Cursor or any MCP client through the Apify MCP server — `https://mcp.apify.com/?actors=badr_xba/google-maps-scraper-pro`.

# Actor input Schema

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

What you would type in the Google Maps search bar, e.g. <b>restaurant</b> or <b>dentist</b>. Use distinct terms (restaurant, bar, cafe) rather than synonyms. You can also write the place in the term (<b>dentist in Chicago</b>), paste <b>Google Maps links</b> (place, search, maps.app.goo.gl short links, ?cid=) or <b>place IDs</b> (ChIJ…). A list pasted on one line (restaurant, bar, cafe) is split automatically and duplicates are removed.

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

Where to search, in plain text: <b>New York, USA</b>, <b>Paris, France</b>, <b>10001, USA</b>, <b>Casablanca, Morocco</b> (accents optional). GPS coordinates (<b>40.7128, -74.0060</b>) work too. Whole cities and regions are split automatically into smaller squares to go past Google's 120-results limit.

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

Places you want per search term. Leave empty to get all the places found.

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

Language of categories, opening hours and descriptions.

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

Keep only places whose Google category contains one of these words (e.g. <b>pizza</b> keeps "Pizza restaurant"). List synonyms too (divorce lawyer, divorce attorney). Without search terms, categories are also used as search terms.

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

Keep only places whose name contains, or exactly matches, the search term.

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

Keep only places rated at least this. Places without reviews are skipped.

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

Only places without a website is a classic lead list for web agencies.

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

Skip places marked temporarily or permanently closed.

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

Opens each place's detail: reviews distribution, review tags, owner description, plus code, order online link, and fills fields the search list sometimes leaves empty. Turned on automatically by reviews or images.

## `maxReviews` (type: `integer`):

Reviews per place: text, stars, date, reviewer, owner response, photos and context. Google sends the most relevant reviews with each place (usually about 5): that is the maximum per place. 0 = no reviews.

## `reviewsStartDate` (type: `string`):

2024-05-03, 2024-05-03T20:00:00 or a period such as "3 months" (UTC).

## `reviewsFilterString` (type: `string`):

Keep only reviews containing this text.

## `scrapeReviewsPersonalData` (type: `boolean`):

Reviewer name, ID, profile URL and photo. Personal data is protected by the GDPR: collect it only for a legitimate reason. The review ID is always included.

## `maxImages` (type: `integer`):

Images per place, in full size (Google sends a few with each place). 0 = none.

## `scrapeContacts` (type: `boolean`):

Visits each place's website to add emails and social profiles (Facebook, Instagram, LinkedIn, X, YouTube, TikTok…). Billed only for websites actually read.

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

Country, alone or with city / state / county / postal code below.

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

City name only, e.g. Pittsburgh. Used instead of Location when filled.

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

e.g. Massachusetts.

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

e.g. New York County.

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

One postal code, combined with Country (not City).

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

GeoJSON <b>Point</b> (with optional "radiusKm", default 5), <b>Polygon</b> or <b>MultiPolygon</b>, coordinates as \[longitude, latitude]. Used when Location is empty.

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

Place URLs (…/maps/place/…, ?cid=…, ?q=place_id:…), short links (maps.app.goo.gl/…) or search URLs (…/maps/search/pizza/@40.75,-73.98,14z — searched in the area shown on the map; without coordinates, the Location is used). Place URLs include the place details.

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

Google place IDs (27 characters starting with ChIJ or GhIJ). Place details are included.

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

Leave off: the Actor reads Google directly and switches to Apify datacenter addresses only when needed.

## `maxConcurrency` (type: `integer`):

Parallel requests to Google.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "searchMatching": "all",
  "placeMinimumStars": "",
  "website": "allPlaces",
  "skipClosedPlaces": false,
  "scrapePlaceDetailPage": false,
  "maxReviews": 0,
  "reviewsFilterString": "",
  "scrapeReviewsPersonalData": true,
  "maxImages": 0,
  "scrapeContacts": false,
  "countryCode": "",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 8
}
```

# Actor output Schema

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

Name, category, address, phone, website, rating and reviews count.

## `contacts` (type: `string`):

Phones, websites, emails and social profiles.

## `reviews` (type: `string`):

One review per row.

## `allFields` (type: `string`):

Every field, as JSON, CSV or Excel.

## `runSummary` (type: `string`):

Places delivered, add-ons billed, searches and failures.

## `failedInputs` (type: `string`):

Searches or places that gave nothing, with the reason.

# 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
};

// Run the Actor and wait for it to finish
const run = await client.actor("badr_xba/google-maps-scraper-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,
}

# Run the Actor and wait for it to finish
run = client.actor("badr_xba/google-maps-scraper-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
}' |
apify call badr_xba/google-maps-scraper-pro --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,badr_xba/google-maps-scraper-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/FBFPkosKhvCRmdweZ/builds/oC4s9vrustMZhrGpC/openapi.json
