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

Extract data from thousands of Google Maps locations and businesses, including reviews, reviewer details, images, contact info, including full name, email, opening hours, prices & more. Export data, run via API, schedule and monitor runs, or integrate with other tools.

- **URL**: https://apify.com/boztek-ltd/google-maps-scraper.md
- **Developed by:** [Boztek LTD](https://apify.com/boztek-ltd) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2,466 total users, 171 monthly users, 91.8% runs succeeded, 8 bookmarks
- **User rating**: 3.99 out of 5 stars

## Pricing

from $2.60 / 1,000 results

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/platform/actors/running/actors-in-store#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

A fast, reliable Google Maps scraping actor built for clean, consistent place data. Give it search terms plus **city** and **country** (or a custom geolocation) — get a structured dataset with the fields you actually need.

### What it does

- Searches Google Maps by keywords, URLs, or Place IDs
- Divides the target area into a geographic grid for maximum coverage
- Scrolls through Google Maps search results and extracts place data
- **Detail Mode** (default): visits each place's detail page and enriches available business websites with published emails, social profiles, and detected technologies
- **Fast Mode**: captures data directly from search result cards — no individual page visits

### Related Tool

If you want a hosted version of this scraper, check out:
👉 https://basedonb.com

On **basedonb.com**, Detail Mode (full place details — phone, website, opening hours, and more) is included **for free**, with no per-place add-on fee.

### Scraping Modes

#### Detail Mode (`detailGeneration: true`, default)

Visits each place's individual page to extract the full dataset.

> **Pricing notice:** Detail Mode is enabled by default for paying users and adds a per-place `detail-generation` charge. Set `detailGeneration: false` to use the lower-cost Fast Mode.

**Fields captured:** Everything in Fast Mode, plus — Full Address (street, city, postal code, state), Phone, Website, Coordinates (precise), Plus Code, Opening Hours, Additional Info (amenities, accessibility, etc.), Place ID, published website emails, social profiles, and detected CMS/technology stack.

Website enrichment is requested only when a business website is available. The actor waits up to `enrichmentTimeoutSecs` for each result batch, then returns all fields available so far together with `enrichmentStatus` and `emailEnrichmentStatus`. A missing website, an email that is not publicly listed, or a site that cannot be reached does not fail the run.

> Best for: complete business profiles where phone numbers, hours, and websites are needed.

#### Fast Mode (`detailGeneration: false`)

Captures data directly from Google Maps search result lists without opening individual place pages.

**Fields captured:** Title, Rating, Review Count, Category, Partial Address, Google Maps URL.

> Best for: large-scale searches where speed matters and basic info is sufficient.

***

### Pricing

Prices depend on your Apify subscription plan. The advertised **"from $2.60 / 1,000 results"** price is the Business-plan price for Fast Mode.

| Apify plan | Fast Mode result fee | Detail generation add-on | Detail Mode total |
|------------|----------------------|--------------------------|-------------------|
| Free | $2.90 / 1,000 places | Charged per place (sample dataset) | Sample dataset only |
| Starter | $2.80 / 1,000 places | $25 / 1,000 places | **$27.80 / 1,000** |
| Scale | $2.70 / 1,000 places | $22 / 1,000 places | **$24.70 / 1,000** |
| Business | $2.60 / 1,000 places | $15 / 1,000 places | **$17.60 / 1,000** |

An actor-start fee is also charged per run: $0.008 on Free, $0.007 on Starter, $0.006 on Scale, and $0.005 on Business. Platform usage is included.

#### Free Tier

Free-tier runs return a **built-in sample dataset** instead of a live search, so you can inspect the exact output format before you subscribe.

##### Free tier limits

- **No live search.** Search terms, country/state/city, custom areas, place IDs and start URLs are not used on the free tier. Every free run returns the same bundled sample, and a warning in the log says so.
- **The sample dataset.** 200 real Google Maps places from **Chicago, USA**, spanning 84 local business categories. It ships inside the actor at `src/data/free-tier-sample-places.json`.
- **Max 100 places per run.** `maxCrawledPlacesPerSearch` is automatically clamped to **100** on the free tier, so a run returns the first 1–100 places of the 200-place dataset. The slice is deterministic and stays category-diverse.
- **Every feature is unlocked — including Detail Mode.** `detailGeneration: true` works on the free tier and is billed with the same `detail-generation` event as paid plans (set the Free-plan price for that event in the Apify Console). Nothing is scraped or enriched at run time; the fields come straight from the dataset.

Every place in the sample carries the full Detail Mode payload: opening hours, place attributes and plus code, review count, coordinates, website technology stack, and — where a business publishes them — social profiles and contact emails. Coverage is what a real run yields: 100% opening hours and attributes, 94% tech stack, 79% social profiles, 56% email.

Upgrade to a paid Apify plan to search your own locations live, or scrape unlimited places directly on basedonb.com.

#### Concurrent task limit

Each Apify user can have at most **2 active live-search tasks** at a time. Pending, generating, running, active, assigned, and paused tasks all count toward this limit. A run that finds all requested places in the existing pool does not create a live-search task and therefore does not consume a slot.

#### How billing events work

- **Fast Mode** — Only the `result` fee applies: $2.90, $2.80, $2.70, or $2.60 per 1,000 emitted places, depending on the user's Apify plan.
- **Detail Mode** — One `detail-generation` event is charged for every emitted place when `detailGeneration: true`. The add-on costs $25, $22, or $15 per 1,000 places on Starter, Scale, or Business. Website email, social-profile, and technology enrichment are bundled into this event; there is no separate enrichment event.
- **Free tier** — Same events as any other plan. Free runs serve the pre-enriched sample dataset, and `detailGeneration: true` still charges one `detail-generation` event per emitted place.

***

### Proxy Auto-Selection

The actor automatically selects the right proxy type — you do not need to configure this manually.

| Condition | Proxy type | Reason |
|-----------|------------|--------|
| Country unknown (no `country` input) | Datacenter | Cannot route without a country code |
| United States + Fast Mode | Datacenter | US datacenter IPs are reliable and cheap for list-level scraping |
| United States + Detail Mode | Residential (US) | Detail pages require local IPs to avoid geo-filtering |
| Any non-US country, any mode | Residential (target country) | Non-US datacenter IPs are routinely blocked or return wrong regional content, even for list searches |

The target country is taken from the `country` input (and optional `city`). For example:

- Searching in **Istanbul** → Turkish residential IPs in both modes
- Searching in **São Paulo** → Brazilian residential IPs in both modes
- Searching in **New York** → US datacenter (Fast Mode) or US residential (Detail Mode)

If the target country cannot be determined, the actor falls back to datacenter proxy automatically.

***

### Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `searchStringsArray` | Array | — | One or more search terms (e.g. `["restaurant", "cafe"]`) |
| `city` | String | `New York` | City for pool search and scrape tasks |
| `country` | String | `USA` | Country or region; required for `search_places` when not using `customGeolocation`. With geo, omit only if the pool alone satisfies your quota; if workers must fill the gap, set `country` (and optionally `city`). |
| `maxCrawledPlacesPerSearch` | Integer | `50` | Global maximum number of places to scrape across all grid cells |
| `language` | String | `en` | Language for Google Maps results |
| `detailGeneration` | Boolean | `true` | Detail Mode (visits each place page); set `false` for Fast Mode |
| `enrichmentTimeoutSecs` | Integer | `180` | Maximum Detail Mode wait per available result batch for website enrichment; returns partial fields and statuses on timeout |
| `startUrls` | Array | — | Direct Google Maps search or place URLs |
| `placeIds` | Array | — | List of Google Maps Place IDs |
| `maxRequestRetries` | Integer | `3` | Retries per failed request |
| `maxConcurrency` | Integer | `5` | Parallel browser pages |
| `navigationTimeoutSecs` | Integer | `20` | Page load timeout in seconds |
| `verboseLog` | Boolean | `false` | Enable debug-level logging |

**Breaking change:** The old `locationQuery` field was removed. Use `city` and `country` instead (for example, replace `"Istanbul, Turkey"` with `city: "Istanbul"` and `country: "Turkey"`).

***

### How Geographic Search Works

When `searchStringsArray` is used with `city` / `country` (or with `customGeolocation` for geo-based pool search):

1. **Resolve location** — The actor uses `city` and `country` for the Supabase `search_places` RPC (or centroid + radius when `customGeolocation` is set).
2. **Pool then tasks** — Matching rows are returned from your pool; if more places are needed, a `scrape_tasks` row is created with the same city, country, and query so workers can fill the gap. If you use `customGeolocation` only, you can skip `country` for the geo pool step, but you must still provide `country` (and optionally `city`) when the pool cannot satisfy `maxCrawledPlacesPerSearch`, or the run fails before creating an invalid task.

See `.actor/INPUT_SCHEMA.json` for the full list of options (e.g. `placeIds`, `startUrls`, `customGeolocation`).

***

### Example Output

#### Fast Mode

```json
{
  "title": "Karadeniz Pide Fırını",
  "totalScore": 4.6,
  "reviewsCount": 892,
  "categories": ["Pide restaurant"],
  "address": "Moda Cd. No:12, Kadıköy",
  "url": "https://www.google.com/maps/place/Karadeniz+Pide+Fırını/...",
  "scrapedAt": "2025-03-06T14:22:10.000Z"
}
```

#### Detail Mode

```json
{
  "title": "Karadeniz Pide Fırını",
  "totalScore": 4.6,
  "reviewsCount": 892,
  "categories": ["Pide restaurant"],
  "address": "Moda Cd. No:12, 34710 Kadıköy/İstanbul",
  "phone": "+90 216 345 67 89",
  "website": "https://karadenizpide.com",
  "hasWebsite": true,
  "email": "info@karadenizpide.com",
  "emails": [
    {
      "email": "info@karadenizpide.com",
      "kind": "role",
      "sourceUrl": "https://karadenizpide.com/contact",
      "sameDomain": true
    }
  ],
  "socials": {
    "instagram": "https://instagram.com/karadenizpide",
    "facebook": "https://facebook.com/karadenizpide"
  },
  "techStack": {
    "cms": "wordpress",
    "detected": ["google-tag-manager", "cloudflare"]
  },
  "enrichmentStatus": "done",
  "emailEnrichmentStatus": "done",
  "coordinates": { "lat": 40.9897, "lng": 29.0275 },
  "plusCode": "3GXJ+R2 Istanbul",
  "openingHours": [
    { "day": "Monday", "hours": "08:00 to 22:00" },
    { "day": "Tuesday", "hours": "08:00 to 22:00" }
  ],
  "additionalInfo": {
    "Service options": [{ "Dine-in": true }, { "Takeaway": true }]
  },
  "placeId": "ChIJ...",
  "url": "https://www.google.com/maps/place/Karadeniz+Pide+Fırını/...",
  "scrapedAt": "2025-03-06T14:22:10.000Z"
}
```

***

### Tips

- **Use a specific `city`** (e.g. `Kadıköy` with `country` `Turkey`) for tighter, more targeted pool matches when your data supports it.
- **`maxCrawledPlacesPerSearch`** is a global ceiling — the actor stops as soon as this many places are collected, regardless of how many grid cells remain.
- **Detail Mode** (default) returns full business profiles with phone numbers, websites, opening hours, published website emails, social profiles, and detected technologies. Switch to **Fast Mode** (`detailGeneration: false`) for discovery and lead generation at scale when basic info is enough and you want to avoid the per-place usage fee.
- For direct place lookups, use `placeIds` or `startUrls` — these skip the search step entirely.

# Actor input Schema

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

One or more search terms (e.g. restaurant, cafe). Up to 25 terms per run; duplicates are not allowed. Paid subscriptions use all provided terms. Free-tier runs do not search live: they ignore these terms and return a built-in 200-place sample dataset from Chicago, USA instead.

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

Country or region. Required when not using a custom map area. If you use a custom area, you can omit it only when that area already yields enough places; otherwise set country (and optionally state/city) so additional places can be fetched.

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

State or region used for search and place requests.

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

City used for search and place requests.

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

Maximum number of places per search term. On the free tier this is capped at 100 and decides how many places of the built-in 200-place sample dataset are returned.

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

Select the language for the search results.

## `detailGeneration` (type: `boolean`):

Include richer place fields and request website enrichment (email, social profiles and technology stack) when a website is available. Available on the free tier too, where it is served from the pre-enriched sample dataset and billed the same way.

## `enrichmentTimeoutSecs` (type: `integer`):

Maximum time Detail Mode waits for each available result batch. If the deadline is reached, the actor returns all fields available so far together with enrichment status fields.

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

Paste Google Maps search or place URLs.

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

List of Google Maps place IDs.

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

GeoJSON polygon or multipolygon. Coordinates must be \[lng, lat]. Search uses the area centroid and radius below.

## `geoSearchRadiusKm` (type: `integer`):

Search radius in kilometers when a custom map area is set.

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

Reserved for backward compatibility; not used by this actor.

## `waitMode` (type: `string`):

block: wait until the fetch finishes, then return all places. stream: poll and emit places as they become available.

## `taskPollTimeoutSecs` (type: `integer`):

Maximum time to wait for an extra place fetch to finish (block or stream).

## `pollIntervalMs` (type: `integer`):

Initial delay between status checks while waiting for a fetch (backoff applies).

## `maxRequestRetries` (type: `integer`):

Unused.

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

Unused.

## `navigationTimeoutSecs` (type: `integer`):

Unused.

## `sessionPool` (type: `object`):

Unused.

## `verboseLog` (type: `boolean`):

Enable debug logs.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "country": "United States",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "detailGeneration": true,
  "enrichmentTimeoutSecs": 180,
  "startUrls": [],
  "placeIds": [],
  "geoSearchRadiusKm": 25,
  "waitMode": "stream",
  "taskPollTimeoutSecs": 3300,
  "pollIntervalMs": 10000,
  "maxRequestRetries": 3,
  "maxConcurrency": 5,
  "navigationTimeoutSecs": 20,
  "sessionPool": {
    "enabled": true,
    "maxPoolSize": 20
  },
  "verboseLog": false
}
```

# Actor output Schema

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

The dataset items for this 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 = {
    "searchStringsArray": [
        "restaurant"
    ],
    "startUrls": [],
    "placeIds": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("boztek-ltd/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 = {
    "searchStringsArray": ["restaurant"],
    "startUrls": [],
    "placeIds": [],
}

# Run the Actor and wait for it to finish
run = client.actor("boztek-ltd/google-maps-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
  ],
  "startUrls": [],
  "placeIds": []
}' |
apify call boztek-ltd/google-maps-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=boztek-ltd/google-maps-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/L0GpmEqFDAH4qQkFS/builds/0KnyAshakZUaXvDNh/openapi.json
