# Google Maps Scraper - Business Leads & Emails (`eiv/google-maps-scraper`) Actor

Scrape Google Maps places by keyword and location, URL or Place ID: name, address, phone, website, rating, review count, hours, categories, coordinates. Whole cities are split into map cells for full coverage. Optional emails and socials from websites. Free filters. No login, no API key.

- **URL**: https://apify.com/eiv/google-maps-scraper.md
- **Developed by:** [Eimantas V](https://apify.com/eiv) (community)
- **Stats:** 2 total users, 1 monthly users, 85.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 place scrapeds

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** extracts business listings from **Google Maps**: name, address, phone, website, rating, review count, opening hours, categories, coordinates and IDs, plus optional emails and social profiles from each business's website. Search by keyword and location, or paste Google Maps URLs and Place IDs. No login, no API key, no browser, from **$0.40 per 1,000 places**.

```json
{
  "title": "Italala Caffè",
  "categoryName": "Coffee shop",
  "address": "Vokiečių g. 1, Vilnius, 01130 Vilniaus m. sav.",
  "phoneUnformatted": "063033363",
  "website": "https://www.italala.lt/",
  "totalScore": 4.7,
  "reviewsCount": 1519,
  "location": { "lat": 54.6785822, "lng": 25.2859445 },
  "openingHours": [{ "day": "Monday", "hours": "8 am–8 pm" }, { "day": "Tuesday", "hours": "8 am–8 pm" }],
  "placeId": "ChIJ6-1bDxWU3UYRue3sXBtJaiQ",
  "emails": ["ciao@italala.lt"],
  "instagrams": ["https://www.instagram.com/italalacaffe"]
}
```

**Measured on Apify: 3,000 Manhattan restaurants in 2 min 7 s; 300 Chicago dentists with place details and website contacts in 60 s.**

### What does Google Maps Scraper do?

It searches Google Maps the way you would, "dentist" in "Austin, Texas", and returns every place as one row. A large location is split into map cells automatically, so a city returns thousands of places instead of the ~120 that one Google Maps search shows. Field and input names are the same as in compass/crawler-google-places, so existing inputs and integrations keep working.

- **Search a term in a city, region, country, postal code or a polygon you draw.** Or paste Google Maps place links, search links, share links, Place IDs and CIDs.
- **Lead data in every row:** phone in local and international format, website, full address split into street, city, postal code and state, rating, review count, categories, hours for the whole week, coordinates, Place ID and CID.
- **Optional add-ons:** emails, phones and social profiles (Facebook, Instagram, LinkedIn, X, YouTube, TikTok, Pinterest) from each business's website, and place details with the 1-5 star breakdown, review topics and the top reviews.
- **Free filters:** category, minimum rating, with or without a website, closed places, exact name match, inside the location only. A place a filter drops is never charged.
- **Only pay for results.** Searches that find nothing, unknown locations and invalid IDs return an error row and are never charged. Each place is delivered once per run, even when several searches find it.
- **Built for automation.** Run it on a schedule, call it from the API, or connect it to Make, Zapier, n8n, Google Sheets or an AI agent through Apify's MCP server.

### Why it is fast and cheap

It reads the same data endpoints the Google Maps website loads in your browser, over plain HTTP, without running a browser. One request returns up to ~150 places with all their fields, so 1,000 places take a handful of requests and a few seconds. That is why the price is a fraction of browser-based scrapers'.

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

| Field | Example | What it is |
|---|---|---|
| `title` | `Italala Caffè` | Business name |
| `categoryName`, `categories` | `Coffee shop` | Main category, and all of them |
| `address`, `street`, `city`, `postalCode`, `state`, `countryCode` | `Vokiečių g. 1, Vilnius…` | Full address and its parts |
| `phone`, `phoneUnformatted` | `(0-630) 33363`, `063033363` | Local and international format |
| `website` | `https://www.italala.lt/` | The business's own site |
| `totalScore`, `reviewsCount` | `4.7`, `1519` | Average rating and number of reviews |
| `openingHours`, `additionalOpeningHours` | `[{"day":"Monday","hours":"8 am–8 pm"}]` | Hours for each weekday; delivery, kitchen and happy hours |
| `location` | `{"lat":54.678,"lng":25.286}` | Coordinates of the pin |
| `placeId`, `cid`, `fid`, `kgmid` | `ChIJ6-1bDxWU3UYRue3sXBtJaiQ` | Google's IDs (Places API Place ID included) |
| `url` | `https://www.google.com/maps/search/?api=1&…` | Opens the place on Google Maps |
| `description` | `Bread-themed branch of the famed Italian market…` | Google's editorial summary |
| `additionalInfo` | `{"Accessibility":[{"Wheelchair accessible entrance":true}]}` | Attributes and the owner's highlights |
| `permanentlyClosed`, `temporarilyClosed` | `false` | Closed status |
| `claimThisBusiness` | `false` | `true` when no owner manages the listing: a local-marketing lead |
| `reserveTableLinks`, `orderOnlineLinks` | `[{"name":"opentable.com","url":…}]` | Booking and ordering links |
| `imageUrl`, `imagesCount`, `imageUrls` | `1183` | Main photo, number of photos, photo URLs |
| `searchString`, `searchLocation`, `rank`, `insideSearchArea` | `coffee shop`, `2` | Which search found the place, and where |
| `emails`, `phones`, `facebooks`, `instagrams`, `linkedIns`, `twitters`, `youtubes`, `tiktoks`, `pinterests` | `["ciao@italala.lt"]` | Contacts add-on: from the business's website |
| `reviewsDistribution`, `reviewsTags`, `reviews`, `plusCode`, `ownerDescription`, `hotelStars` | `{"fiveStar":1266,…}` | Place details add-on |

### How to scrape Google Maps places

1. Click **Try for free**. Type what you are looking for in **Search terms** ("plumber", "coffee shop") and where in **Location** ("Austin, Texas, USA").
2. Set **Places per search**. You pay per place, so this is also your cost cap.
3. Turn on the add-ons you need, then click **Start**. 1,000 places take about a minute.
4. Open the **Output** tab (views: Overview, Contacts / leads, Location, Ratings, Top reviews, Opening hours), or download JSON, CSV, Excel or HTML.

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

You pay per place, and the price drops on higher Apify plans. Add-ons are charged only for places where they returned data.

| Apify plan | Per 1,000 places | Place details add-on | Contacts add-on (only where found) |
|---|---|---|---|
| Free | $1.00 | $0.60 | $1.20 |
| Starter | $0.80 | $0.50 | $1.00 |
| Scale | $0.70 | $0.45 | $0.90 |
| Business | $0.60 | $0.40 | $0.80 |
| Enterprise | $0.40 - $0.50 | $0.30 - $0.35 | $0.60 - $0.70 |

- 5,000 restaurants in a city on the Starter plan: 5,000 x $0.0008 = **$4.00**.
- 1,000 dentists with place details and contacts on Starter: $0.80 + $0.50 for details + about $0.73 for contacts (found for ~73 % of dentists with a website, ~63 % of all) = **about $2.03**.
- Filters are free: places they drop are never charged. A run also costs **$0.00005** per GB of memory to start.
- The Free plan's $5 monthly credit covers about **5,000 places**.

### Input

```json
{
  "searchStringsArray": ["dentist", "orthodontist"],
  "locationQuery": "Austin, Texas, USA",
  "maxCrawledPlacesPerSearch": 500,
  "website": "withWebsite",
  "scrapeContacts": true
}
```

| Field | Default | Notes |
|---|---|---|
| Search terms (`searchStringsArray`) | `restaurant` | One per line; each is searched in every location |
| Location (`locationQuery`) | `New York, USA` | One per line; split into map cells for full coverage |
| Places per search (`maxCrawledPlacesPerSearch`) | 100 | Per term and location: your main cost lever |
| Max places in total (`maxItems`) | none | Optional cap for the whole run |
| Language (`language`) | `en` | Language of categories, hours and summaries |
| Country, state, county, city, postal code | none | Instead of Location, as separate fields |
| Custom search area (`customGeolocation`) | none | GeoJSON polygon, or a point with `radiusKm` |
| Google Maps URLs (`startUrls`), Place IDs (`placeIds`) | none | Single places, or a search link with its map view |
| Filters | off | Category, minimum rating, website, closed, exact name, inside the location (on) |
| Place details (`scrapePlaceDetailPage`) | off | Star breakdown, review topics, top reviews, plus code, owner description |
| Contacts from website (`scrapeContacts`) | off | Emails, phones and social profiles from the business's site |
| Image URLs per place (`maxImages`) | 0 | Photo URLs in `imageUrls` |
| Proxy | automatic | Leave empty: the Actor picks the address itself |

### Output

Each place is one row. The **Overview** view shows the key columns; every export includes all fields. With place details on, `reviews` holds the top reviews:

```json
{
  "stars": 5,
  "text": "Everything was absolutely perfect. The service, pastries, and drinks were 11/10…",
  "publishedAtDate": "2026-05-17T15:30:22.997Z",
  "reviewerName": "Austeja G",
  "isLocalGuide": true,
  "reviewDetailedRating": { "Food": 5, "Service": 5, "Atmosphere": 5 },
  "reviewContext": { "Price per person": "€10–15", "Reservation": "Reservations not required" }
}
```

A search, URL or Place ID that cannot be read becomes a row with `"recordType": "error"` instead of failing the run. Error rows are never charged:

| `errorCode` | Meaning |
|---|---|
| `LOCATION_NOT_FOUND` | The location is not on the map. Write it as "City, Country" |
| `NOT_FOUND` | No place for this Place ID, CID or link |
| `BAD_AREA` | The custom search area is not valid GeoJSON |
| `BLOCKED`, `RATE_LIMITED`, `UNREACHABLE` | Google refused or did not answer; run again |
| `PARSE_FAILED` | Google changed its format; please report it in the Issues tab |

The run's `RUN_SUMMARY` record has the totals: places, add-ons charged, map views, places each filter dropped, and why the run stopped.

### Tips and limits

- **Different terms find different places.** "plumber" and "electrician" add places; "plumber" and "plumbers" mostly find the same ones.
- **"Only places inside the location" is on by default.** A city's map rectangle also covers its neighbours; turn it off to keep them (each row has `insideSearchArea`).
- **Reviews: the top ones only.** Google shows up to 5 reviews per place to visitors who are not signed in, and this Actor never signs in. Full review history, popular times and price levels are only shown to signed-in Google users, so they are not included.
- **Attributes** are the groups Google's website shows to visitors who are not signed in (accessibility and the owner's highlights).
- **Review counts:** Google sometimes hides them from visitors who are not signed in. The Actor detects that and asks again from another session; in the rare case Google keeps hiding it, `reviewsCount` is null and the run log says how many.
- **Charge limit:** the run stops cleanly at your maximum charge per run, with everything found so far saved and charged correctly.

### FAQ

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

This Actor collects business listings that Google Maps shows to anyone, plus what businesses publish on their own websites. It does not log in. Reviewer names are personal data under the GDPR: turn off **Include reviewer names** if you do not need them, and check with a lawyer what your use case allows. Google Maps is a trademark of Google LLC; this Actor is not affiliated with or endorsed by Google.

#### Can I use it through the API, Make, Zapier or n8n?

Yes. Start runs from the Apify API or any integration, schedule them, and get the dataset as JSON, CSV or Excel. The output schema links the places, the leads view and the run summary.

#### Can AI agents use it?

Yes, through Apify's MCP server. The input fields and the Store description are written so an agent can fill them in from a request like "dentists in Austin with emails".

#### I switched from compass/crawler-google-places. Do I need to change my input?

Usually not: `searchStringsArray`, `locationQuery`, `maxCrawledPlacesPerSearch`, `language`, `countryCode`, `city`, `state`, `county`, `postalCode`, `customGeolocation`, `startUrls`, `placeIds`, the filters, `scrapePlaceDetailPage` and `scrapeContacts` work the same, and the output uses the same field names. Inputs for features this Actor does not have (full review history, people leads) are ignored.

#### Why did I get fewer places than I asked for?

The area holds fewer places for that term, filters dropped some (never charged), several searches found the same places (each is delivered once), or the run hit your maximum charge or timeout. `RUN_SUMMARY` says which.

### Related scrapers

- [Website Tech Stack & Lead Enrichment Scraper](https://apify.com/eiv/website-intelligence-scraper): technologies, ad pixels, emails and DNS for the websites you found here.
- [Trustpilot Reviews Scraper](https://apify.com/eiv/trustpilot-reviews-scraper): every review a company has on Trustpilot, with the company's replies.
- [Google Ads Transparency Scraper](https://apify.com/eiv/google-ads-transparency-scraper): which of these businesses advertise on Google, and with what ads.

### Support

Found a bug or missing a field? Open an issue in the **Issues** tab with the run link; issues get an answer within a few days.

# Actor input Schema

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

What you would type into Google Maps: "dentist", "coffee shop", "plumber". One term per line; each is searched in every location below. Different trades find different places; plural variants of one word ("plumber", "plumbers") mostly find the same ones again. A business name works too ("Joe's Pizza").

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

Where to search: a city, district, region or country, as "City, Country" ("Austin, Texas, USA"). One per line to search several. The whole area is covered: it is split into map cells, so a city returns far more than Google's ~120 results per search. Leave empty to let Google go by the search words alone.

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

Stop each search (one term in one location) after this many places. You pay per place, so this is your main cost lever. 100 is a quick list; a large city holds thousands for a broad term like "restaurant". Places a filter drops do not count.

## `maxItems` (type: `integer`):

Optional cap for the whole run, across all searches, URLs and Place IDs. Empty = no cap beyond "Places per search" and your maximum charge per run.

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

The language Google answers in: category names, opening hours, editorial summaries. Place names and addresses stay as the business wrote them.

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

Optional. With no Location set, searches the whole country (or the state, county, city or postal code below). With a Location, keeps that location inside this country and ranks results for it.

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

Optional: a state or region, e.g. "Massachusetts". Used when "Location" is empty.

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

Optional: a county or district, e.g. "New York County". Used when "Location" is empty.

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

Optional: a city, e.g. "Pittsburgh" (state and country go in their own fields). Used when "Location" is empty.

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

Optional: one postal code, e.g. "10001". Combine it with Country. Used when "Location" is empty.

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

An exact area instead of a named place, as GeoJSON with \[longitude, latitude] pairs: a Polygon, MultiPolygon, Feature(Collection), or a Point with "radiusKm", e.g. {"type":"Point","coordinates":\[-74.006,40.7128],"radiusKm":2}. Draw one at geojson.io. Overrides Location and the fields above.

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

Google Maps links, one per line. A place (…/maps/place/…, share links maps.app.goo.gl/…, ?cid=…) is scraped as that one place; a search page (…/maps/search/coffee/@60.17,24.94,13z) is scraped as that search in that map view.

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

Place IDs from the Places API or an earlier run ("ChIJreV9aqYWdkgROM_boL6YbwA"), one per line. CIDs ("7838756667406262025") and feature ids ("0x89c3…:0x6cc8…") work too.

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

Keep only places whose Google category contains one of these words: "dentist" keeps "Dentist" and "Cosmetic dentist". Case and accents do not matter. Empty = all categories.

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

Keep only places whose name contains, or equals, the search term. Handy for every branch of a chain ("Starbucks"). Case, accents and punctuation do not matter.

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

Keep only places rated at least this many stars. Places without a rating are dropped when this is set.

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

Keep places with a website (for contact enrichment) or without one (leads for web design and marketing).

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

Drop places Google marks as permanently or temporarily closed.

## `onlyInsideArea` (type: `boolean`):

Drop places outside the location's boundary (from OpenStreetMap), such as the next town that falls inside the map rectangle. Turn off to keep them; each row says insideSearchArea true or false.

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

One extra request per place for the 1-5 star breakdown, review topics with counts, the top reviews Google shows, the business's own description, plus code, hotel class and the full week of hours. Charged per place where the details loaded.

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

Visits each place's own website for emails, phone numbers and social profiles (Facebook, Instagram, LinkedIn, X, YouTube, TikTok, Pinterest): the homepage, then up to two contact pages. Never a crawl. Charged only where it finds at least one contact.

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

How many photo URLs to list per place in imageUrls (the main photo is always in imageUrl). Search results carry a few; with Place details on, more. Free.

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

With Place details on: include each review author's name, profile link, photo and review count. Turn off to collect reviews without personal data (GDPR).

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

Leave empty: the Actor picks the address itself, starting with the run's own (free) and moving to Apify datacenter proxy when Google hides review counts from it. Set a proxy only to force your own; then only that one is used.

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

How many searches, URLs or Place IDs are processed at the same time.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one response before giving up on it.

## `maxRetries` (type: `integer`):

Retries for blocked, rate-limited or failed requests. Timeouts are not retried.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 100,
  "language": "en",
  "searchMatching": "all",
  "placeMinimumStars": "",
  "website": "allPlaces",
  "skipClosedPlaces": false,
  "onlyInsideArea": true,
  "scrapePlaceDetailPage": false,
  "scrapeContacts": false,
  "maxImages": 0,
  "scrapeReviewsPersonalData": true,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 30,
  "maxRetries": 2
}
```

# Actor output Schema

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

One row per place (recordType "place"), plus one uncharged row per search, URL or Place ID that could not be read (recordType "error").

## `leads` (type: `string`):

The same places as a lead list: phone, website, emails and social profiles.

## `summary` (type: `string`):

Totals for the run: places, add-ons charged, map views, filtered places, failures and why the run stopped.

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

// Run the Actor and wait for it to finish
const run = await client.actor("eiv/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"],
    "locationQuery": "New York, USA",
}

# Run the Actor and wait for it to finish
run = client.actor("eiv/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 '{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA"
}' |
apify call eiv/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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