# Google Maps Scraper (`intergration/google-maps-scraper`) Actor

Scrapes Google Maps places — business data, reviews, images, contacts. Pay-per-event pricing.

- **URL**: https://apify.com/intergration/google-maps-scraper.md
- **Developed by:** [Ian Emmanuel](https://apify.com/intergration) (community)
- **Categories:** Integrations, Automation, AI
- **Stats:** 82 total users, 11 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 place scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Scraper

Extract business data, reviews, contacts, and images from Google Maps — with pay-per-event pricing and support for 20 languages.

***

### What This Actor Does

The Google Maps Scraper searches Google Maps for businesses, landmarks, and services in any location worldwide. It returns structured place records and supports optional add-ons for reviews, images, contact details, and enriched business data. Results are returned in your chosen language — independent of the scraping location or proxy IP.

***

### Output Fields

Each place record includes the following fields:

| Field | Description |
|---|---|
| `title` | Business name |
| `address` | Full formatted address |
| `phone` | Phone number (E.164 formatted) |
| `website` | Website URL (tracking params stripped) |
| `category` | Google Maps category label |
| `totalScore` | Star rating (1.0 – 5.0) |
| `reviewsCount` | Total number of reviews |
| `lat` / `lng` | GPS coordinates (7 decimal places) |
| `openingHours` | Opening hours by day |
| `priceBracket` | Price level ($ to $$$$) |
| `placeId` | Unique Google Maps Place ID |
| `isExternalServicePlace` | `true` for booking-provider injected results |
| `reviews[]` | Array of review objects *(when enabled)* |
| `images[]` | Array of image objects *(when enabled)* |
| `emails[]` | Extracted email addresses *(when enabled)* |
| `socialUrls[]` | Social media profile URLs *(when enabled)* |
| `scrapedAt` | ISO 8601 timestamp of extraction |

***

### Pricing

This actor uses a **pay-per-event** model. You are charged only for events that actually fire during your run — no subscriptions, no idle fees, no minimum commitments.

| Event | Rate | Billing Unit | When It Fires |
|---|---|---|---|
| Actor start | $0.0056 | Per run | Every run — infrastructure flat fee |
| Place scraped | $0.0032 | Per place | Every successfully returned place |
| Filter applied | $0.0008 | Per filter × place | When any filter is active (category, rating, website, keyword) |
| Additional place details | $0.0016 | Per place | When enriched details are enabled |
| Contact details scraped | $0.0016 | Per place w/ website | Only places with a website |
| Review scraped | $0.0004 | Per review | Each individual review extracted |
| Image scraped | $0.0004 | Per image | Each image including metadata |

#### Worked Examples

| Job Type | Configuration | Formula | Cost |
|---|---|---|---|
| Plain directory | 3,500 places, no add-ons | 3,500 × $0.0032 + $0.0056 | **$11.21** |
| Filtered search | 1,200 places, 3 filters | 1,200 × $0.0032 + 1,200 × 3 × $0.0008 + $0.0056 | **$6.73** |
| Contact leads | 2,000 places, 1,400 with websites | 2,000 × $0.0032 + 1,400 × $0.0016 + $0.0056 | **$8.65** |
| Review extraction | 500 places, 60 reviews/place cap | 500 × $0.0032 + 30,000 × $0.0004 + $0.0056 | **$13.61** |
| Full enrichment | 400 places, 2 filters, 350 websites, 25 reviews, 8 images | 400×$0.0032 + 800×$0.0008 + 400×$0.0016 + 350×$0.0016 + 10,000×$0.0004 + 3,200×$0.0004 + $0.0056 | **$8.41** |
| Direct ID lookup | 5,000 Place IDs | 5,000 × $0.0032 + 5,000 × $0.0016 + $0.0056 | **$24.01** |

> 💡 Multiple categories under the category filter = 1 filter event — no extra charge for category breadth.\
> 💡 Direct Place ID/URL input always triggers Additional place details ($0.0016/place) — minimum base rate is $0.0048/place.\
> ⚠️ Set `reviewsPerPlaceLimit` and `imagesPerPlaceLimit` before every run — these are the highest-scaling line items.

***

### Input Fields

#### Search

| Field | Type | Default / Required | Description |
|---|---|---|---|
| `searchTerms` | array | **Required** (or `directPlaceIds`) | Keywords to search Google Maps (e.g. `"restaurant"`, `"pharmacy"`). Each term triggers a full map-scroll pass. |
| `directPlaceIds` | array | Optional | List of Place IDs or Google Maps URLs. Skips the search step and extracts specific places directly. |

#### Location

| Field | Type | Default / Required | Description |
|---|---|---|---|
| `location` | string | Recommended | City, region, or country name. Resolved via OpenStreetMap. |
| `country` | string | Optional | ISO 3166-1 alpha-2 country code (e.g. `"KE"`, `"NG"`, `"ZA"`). |
| `state` | string | Optional | State or province name. |
| `city` | string | Optional | City name. |
| `postalCode` | string | Optional | Postal or ZIP code. |
| `customGeolocation` | object | Optional | GeoJSON Polygon, MultiPolygon, or Point with `radiusKm`. Subdivides the area into tiles and bypasses the 120-place limit. |

#### Run Control

| Field | Type | Default | Description |
|---|---|---|---|
| `maxCrawledPlaces` | integer | 100 | Hard cap on total places returned. Set before every run to control cost. |
| `language` | string | English | Result language — 20 supported including Mandarin Chinese, Arabic, Hindi, and Swahili. |

#### Filters

| Field | Type | Default | Description |
|---|---|---|---|
| `categories` | array | Optional | Only keep places matching these category labels. |
| `minimumStarRating` | number | Optional | Exclude places below this rating (1.0 – 5.0). |
| `requiresWebsite` | boolean | false | Only return places with a website URL. Recommended when contact scraping is enabled. |
| `titleKeyword` | string | Optional | Only return places whose name contains this string (case-insensitive). |

#### Add-ons

| Field | Type | Default | Description |
|---|---|---|---|
| `scrapeAdditionalDetails` | boolean | false | Extract enriched fields: reservations, web results, Q\&A, menu URLs, booking links. |
| `scrapeContacts` | boolean | false | Visit each business website to extract emails and social media URLs. Charged $0.0016/place with a website. |
| `enrichSocialMedia` | boolean | false | Categorise discovered social URLs by platform. **Requires** `scrapeContacts`. |
| `scrapeBusinessLeads` | boolean | false | Pull LinkedIn profiles for key personnel. Part of Additional place details — $0.0016/place. |

#### Reviews

| Field | Type | Default | Description |
|---|---|---|---|
| `scrapeReviews` | boolean | false | Extract individual reviews per place. |
| `reviewsPerPlaceLimit` | integer | 50 | Cap on reviews per place. Set conservatively — 200 reviews × 5,000 places = $400 in review charges alone. |
| `scrapeReviewerData` | boolean | false | Include reviewer name, ID, photo, `isLocalGuide` flag. Separate toggle — enabling reviews does not automatically enable this. |

#### Images

| Field | Type | Default | Description |
|---|---|---|---|
| `scrapeImages` | boolean | false | Extract gallery images and metadata per place. |
| `imagesPerPlaceLimit` | integer | 10 | Cap on images per place. 50 images × 10,000 places = $200 in image charges alone. |

#### Other

| Field | Type | Default | Description |
|---|---|---|---|
| `estimatedPlaces` | integer | Optional | Enter your expected place count to log a pre-run cost estimate before scraping begins. |
| `proxyConfiguration` | object | Optional | Apify proxy settings. Residential proxy recommended for production runs. |
| `debugMode` | boolean | false | Enable verbose logging — XHR hits, selector misses, filter rejection reasons. |

***

### Important Notes

#### ⚠️ 120-place limit without geolocation

Without a `location`, `country`, `city`, or `customGeolocation`, Google Maps renders a single screen and returns at most ~120 results. Provide any geolocation field to get full coverage. The `customGeolocation` field (GeoJSON polygon) subdivides the area into tiles and bypasses this limit entirely.

#### ⚠️ GeoJSON coordinate order

GeoJSON (RFC 7946) uses `[longitude, latitude]` order — the **opposite** of Google Maps display order. Pasting coordinates from Google Maps directly will scrape the wrong area with no error. Use [geojson.io](https://geojson.io) to build and visually verify your polygon before running.

#### ⚠️ Direct Place ID billing

Inputting Place IDs or Google Maps URLs directly skips the search step and goes straight to detail extraction. This automatically triggers the Additional place details add-on ($0.0016/place) on every place — the minimum base rate for direct-ID runs is **$0.0048/place**.

#### ⚠️ Social enrichment dependency

`enrichSocialMedia` requires `scrapeContacts` to also be enabled. Without contact scraping, there are no discovered social URLs to enrich and the feature produces no output.

#### ⚠️ Overlapping search terms

Each search term triggers a full independent map-scroll pass. Near-duplicate terms like `"restaurant"` and `"restaurants"` re-scrape the same places, inflating run time and cost. Use distinct, non-overlapping terms only, and use category filters to narrow results instead.

#### ℹ️ External service places

Hotel and accommodation searches may include places sourced from booking providers. These are flagged with `isExternalServicePlace: true` and may have incomplete fields (`phone`, `address`, and `hours` may be `null`). Filter them post-run using this field.

#### ℹ️ Review data and privacy

Reviewer personal data (name, ID, photo, `isLocalGuide`) is disabled by default for privacy compliance. Only enable `scrapeReviewerData` when you have a documented lawful basis.

#### ℹ️ 5,000-review dataset limit

Places with more than 5,000 reviews are split into multiple dataset items sharing the same place fields. Use the Reviews export view (append `&view=reviews` to the dataset API URL) to get one flat row per review.

***

### Getting Started Example

The following example runs a full-enrichment scrape of cafes in Nairobi with all major add-ons enabled. It is designed to test every billing event type with a minimal place count to keep cost predictable.

```json
{
  "searchTerms": ["cafe"],
  "location": "Nairobi, Kenya",
  "country": "KE",
  "city": "Nairobi",
  "maxCrawledPlaces": 5,
  "language": "English",
  "categories": ["Cafe", "Coffee shop", "Espresso bar", "Bakery"],
  "minimumStarRating": 1.0,
  "requiresWebsite": false,
  "scrapeAdditionalDetails": true,
  "scrapeContacts": true,
  "enrichSocialMedia": true,
  "scrapeReviews": true,
  "reviewsPerPlaceLimit": 3,
  "scrapeReviewerData": true,
  "scrapeImages": true,
  "imagesPerPlaceLimit": 3,
  "estimatedPlaces": 5,
  "debugMode": false
}
```

**Expected cost:** ~$0.08 – $0.12 depending on how many places have websites.

#### Each place record will include:

| Field Group | Fields |
|---|---|
| Core | `title`, `address`, `phone`, `website`, `category`, `totalScore`, `reviewsCount`, `lat`, `lng`, `openingHours`, `priceBracket`, `placeId`, `scrapedAt` |
| Additional details | `reservationUrl`, `orderUrl`, `menuUrl`, `qaItems[]`, `webResults[]` |
| Contacts | `emails[]`, `socialUrls[]`, `socialProfiles{}`, `phonesFromSite[]` |
| Reviews | `reviewId`, `text`, `stars`, `publishedDate`, `ownerResponse`, `reviewerName`*, `reviewerUrl`*, `isLocalGuide`\* |
| Images | `url`, `authorName`, `uploadDate` |

*\*Only included when `scrapeReviewerData` is enabled.*

***

*Built with Node.js 18+ · Crawlee · Playwright · Apify SDK*

# Actor input Schema

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

Keywords to search on Google Maps (e.g. 'restaurant', 'pharmacy'). Use distinct, non-overlapping terms. Each term triggers a full map-scroll pass — duplicate or adjacent terms inflate run time and cost without adding unique places.

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

City, region, or country name. Resolved via OpenStreetMap Nominatim. Omitting this field AND all geolocation parameters caps output at 120 places.

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

ISO 3166-1 alpha-2 country code for precise resolution (e.g. 'KE', 'NG', 'ZA').

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

State or province name for more precise location resolution (e.g. 'Nairobi County', 'Lagos State').

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

City name for precise location resolution (e.g. Nairobi, Lagos, Cape Town).

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

Postal or ZIP code for precise location resolution.

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

GeoJSON Polygon, MultiPolygon, or Point with radiusKm. IMPORTANT: coordinates must be in \[longitude, latitude] order per RFC 7946 — the opposite of Google Maps display order. Use geojson.io to build and visually verify before pasting.

## `directPlaceIds` (type: `array`):

Skip search — extract specific known places directly. WARNING: Direct Place ID/URL input automatically triggers the 'Additional place details' add-on ($0.0016/place) on top of the standard place-scraped rate. Minimum cost per place is $0.0048.

## `maxCrawledPlaces` (type: `integer`):

Hard cap on total places returned. Set this to control cost on large runs.

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

Language for Google Maps results and UI. Overrides region-based language detection — results will be returned in this language regardless of the scraping location or proxy IP. Affects place names, categories, reviews, and all text fields in the output.

## `categories` (type: `array`):

Only keep places matching these Google Maps category labels. Include ALL synonyms — 'Divorce attorney' and 'Divorce lawyer' are different category IDs. Missing a synonym silently excludes all places under that label. Multiple categories count as 1 filter event.

## `minimumStarRating` (type: `number`):

Exclude places below this rating (1.0–5.0). Each active filter adds $0.0008 per place.

## `requiresWebsite` (type: `boolean`):

Only return places that have a website URL. Recommended when contact details are enabled — prevents spending place-scraping budget on listings that cannot return contact data.

## `titleKeyword` (type: `string`):

Only return places whose name contains this string (case-insensitive partial match). Adds $0.0008 per place.

## `scrapeAdditionalDetails` (type: `boolean`):

Extract enriched fields: table reservations, web results, Q\&A. Flat $0.0016/place regardless of how many detail types are enabled. Always enable all types you need — selecting fewer does not reduce cost.

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

Visit each business website to extract email addresses and social media profile URLs. Charged $0.0016 per place that has a website. Enable the 'Has website' filter to avoid spending place budget on listings with no website.

## `scrapeReviews` (type: `boolean`):

Extract individual reviews (text, rating, date, owner response). Charged $0.0004 per review. Set reviewsPerPlaceLimit to control cost.

## `reviewsPerPlaceLimit` (type: `integer`):

Maximum reviews to extract per place. Set this before every review run — without a cap, places with thousands of reviews can drive significant cost. A cap of 200 reviews on 5,000 places = $400 in review charges.

## `scrapeReviewerData` (type: `boolean`):

Include reviewer name, ID, URL, photo, isLocalGuide flag, and review count. Disabled by default for privacy compliance. Only enable when you have a documented lawful basis. Requires 'Reviews' to also be enabled.

## `scrapeImages` (type: `boolean`):

Extract images and metadata (author, upload date) from each place listing. Charged $0.0004 per image. Set imagesPerPlaceLimit to control cost.

## `imagesPerPlaceLimit` (type: `integer`):

Maximum images to extract per place. Set conservatively — 50 images × 10,000 places = $200 in image charges.

## `enrichSocialMedia` (type: `boolean`):

Retrieve follower counts, descriptions, and verification status for discovered social profiles. REQUIRES 'Contact details' to also be enabled — social enrichment has no URLs to process if contacts are not scraped first.

## `scrapeBusinessLeads` (type: `boolean`):

Pull LinkedIn profiles for identified personnel. Part of the Additional place details add-on — $0.0016/place (mandatory) plus $0.0016/place for contacts.

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

Apify proxy settings. Residential proxies are strongly recommended for production runs.

## `debugMode` (type: `boolean`):

Enable verbose logging including selector miss events and raw XHR body samples.

## `estimatedPlaces` (type: `integer`):

Optional. Enter your expected place count to see a cost estimate in the billing summary before the run starts. Does not affect scraping — purely informational. Leave blank to skip pre-run estimate.

## Actor input object example

```json
{
  "searchTerms": [
    "restaurant",
    "bar",
    "cafe"
  ],
  "location": "Nairobi, Kenya",
  "maxCrawledPlaces": 100,
  "language": "English",
  "categories": [
    "Divorce lawyer",
    "Divorce attorney",
    "Family law attorney"
  ],
  "requiresWebsite": false,
  "scrapeAdditionalDetails": false,
  "scrapeContacts": false,
  "scrapeReviews": false,
  "reviewsPerPlaceLimit": 50,
  "scrapeReviewerData": false,
  "scrapeImages": false,
  "imagesPerPlaceLimit": 10,
  "enrichSocialMedia": false,
  "scrapeBusinessLeads": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "debugMode": false
}
```

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

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

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

```

## MCP server setup

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