# Google Maps Scraper — Leads, Emails & Reviews (`amrhassan25/google-maps-scraper`) Actor

Extract every Google Maps place in any city, polygon or radius: phones, websites, emails & socials, hours, ratings and reviews. Fast HTTP engine, no browser, no login. Filters are free, out-of-area places are never charged, and your spending cap is never exceeded.

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

## Pricing

from $1.20 / 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 Scraper — Leads, Emails & Reviews

**Google Maps Scraper** extracts every business Google Maps has for a search in any city, district, postal code, polygon or radius. For each place you get its name, category, address, phone, website, rating, review count, opening hours, coordinates and photos. Optionally you also get **emails and social profiles** from the business's website, **reviews** with owner replies, and the review histogram. Export to JSON, CSV or Excel, or pull the data through the Apify API.

- 🗺️ **The whole area, not just the first 120 results.** Google Maps shows about 120 places per search. This scraper covers your location with an adaptive map grid: dense downtowns get fine cells, empty areas cost one request. In a test on all coffee shops in Manhattan it found **1,400+ places**, versus ~120 from a single search.
- 🎯 **Only places inside your area, and you never pay for the rest.** The location's real boundary comes from OpenStreetMap. A search for "Austin" returns Austin, not Round Rock, and places outside the boundary are skipped **before** they are charged.
- 💸 **Less than half the price of the most popular Google Maps scraper.** Filters are free, and contacts are charged only when an email or profile is actually found. See [pricing](#how-much-does-it-cost-to-scrape-google-maps).
- 🛑 **Your spending limit is a hard limit.** Every place is charged before it is delivered, and only what your budget covers is delivered. A run cannot overshoot *Maximum places* or your limit.
- ⚡ **Fast.** It uses plain HTTP with no browser: about 1,400 places in under 3 minutes on 1 GB of memory.
- 🧩 **Drop-in compatible.** Input and output field names match Apify's own Google Maps Scraper (`searchStringsArray`, `locationQuery`, `title`, `totalScore`, `reviewsCount`, `placeId`, `reviews`, …), so existing integrations keep working.
- 🔔 **Monitoring built in.** Turn on *Only new places*, schedule the run, and each run delivers only businesses that were not there before, such as newly opened restaurants.
- 🔓 **No login and no cookies.** It reads Google Maps the way a logged-out visitor sees it.

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

| Place | Contacts (optional) | Reviews (optional) | Details (optional) |
|---|---|---|---|
| Name, category, all categories | Emails | Text and stars | Review histogram (1★–5★) |
| Full address and its parts | Phones from the website | Exact date | "People often mention" keywords |
| Phone, website, domain | Facebook, Instagram, LinkedIn | Owner reply and its date | Plus code |
| Rating and review count | X / Twitter, YouTube, TikTok, Pinterest | Review photos | "Located in" (mall, airport) |
| Opening hours, kitchen / delivery hours | Page the contacts came from | Food / service / atmosphere ratings | More photos |
| Coordinates, time zone | | Reviewer name, profile, Local Guide | |
| Place ID, CID, feature ID, Knowledge Graph ID | | Original language and translation | |
| Amenities and accessibility, in readable and language-independent form | | Review likes | |
| Booking and ordering links, claimed / unclaimed | | | |
| Open / closed status, photos | | | |

Unclaimed listings (`claimThisBusiness: true`) and businesses **without a website** (the *Website* filter) are popular lead lists for local marketing agencies.

### How to scrape Google Maps

1. Click **Try for free** and sign in to Apify. The free plan includes monthly credit.
2. Enter **Search terms**, for example `dentist` or `coffee shop`.
3. Enter a **Location**, for example `Austin, Texas, USA`. You can also use the country / city fields, or draw a custom area.
4. Optionally turn on **Emails & social profiles**, **Place details** or **Reviews per place**, and set filters such as a minimum rating.
5. Click **Start**. Open the **Leads & contacts** or **Reviews** tab of the results, or download everything as CSV, Excel or JSON.

You can also paste **Google Maps URLs** (place links, search links, share links from the app) or **place IDs** to scrape specific places.

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

You pay for what you get, and higher Apify plans pay less. Proxies and compute are included, and there is no start fee.

| Event (per 1,000) | Free | Starter | Scale | Business |
|---|---|---|---|---|
| **Place** | $1.50 | $1.30 | $1.20 | $1.20 |
| Place details | $0.50 | $0.40 | $0.30 | $0.25 |
| Contacts (only when found) | $1.20 | $1.00 | $0.80 | $0.60 |
| Review | $0.25 | $0.20 | $0.15 | $0.12 |

These are **free**: filters, places outside your area, duplicates found by several search terms, places already seen in monitoring mode, retries, and rows for inputs that failed.

| Example (Starter plan) | Price |
|---|---|
| 1,000 restaurants with phone, website, rating and hours | **$1.30** |
| 1,000 leads with emails and socials (≈60% have a website with contacts) | $1.30 + $0.60 = **$1.90** |
| 1,000 places with details and 20 reviews each | $1.30 + $0.40 + $4.00 = **$5.70** |
| All 20,000 reviews of one hotel | **$4.00** |

For comparison, on the Starter plan Apify's own Google Maps Scraper lists $3.00 per 1,000 places, $2.00 per 1,000 contact lookups, $2.00 per 1,000 place details, $0.50 per 1,000 reviews and $1.00 per 1,000 places **for each filter** you use (September 2026). So 1,000 leads with contacts cost **$5.00** there and **$1.90** here, and a minimum-rating filter adds $1.00 there and nothing here.

### Input example

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

Other ways to say *where*:

```json
{ "customGeolocation": { "type": "Point", "coordinates": [-97.7431, 30.2672], "radiusKm": 3 } }
```

```json
{ "countryCode": "de", "city": "Munich", "postalCode": "80331" }
```

Coordinates in GeoJSON are **\[longitude, latitude]**. You can draw polygons at [geojson.io](https://geojson.io).

### Output example

```json
{
    "title": "Salinas Restaurant",
    "categoryName": "Spanish restaurant",
    "categories": ["Spanish restaurant", "Cocktail bar", "Seafood restaurant"],
    "address": "136 9th Ave, New York, NY 10011, United States",
    "street": "136 9th Ave",
    "city": "New York",
    "postalCode": "10011",
    "state": "New York",
    "countryCode": "US",
    "phone": "+1 212-776-1990",
    "phoneUnformatted": "+12127761990",
    "website": "http://www.salinasnyc.com/",
    "domain": "salinasnyc.com",
    "location": { "lat": 40.7437056, "lng": -74.0031569 },
    "totalScore": 4.8,
    "reviewsCount": 2964,
    "reviewsDistribution": { "oneStar": 84, "twoStar": 31, "threeStar": 46, "fourStar": 183, "fiveStar": 2620 },
    "openingHours": [{ "day": "Tuesday", "hours": "5:30–10 PM" }],
    "additionalOpeningHours": { "Kitchen": [{ "day": "Tuesday", "hours": "5:30–9 PM" }] },
    "openingStatus": "Closed · Opens 5:30 PM Tue",
    "placeId": "ChIJHcejL7lZwokRNtXyXJbJ27g",
    "cid": "13320461970520069430",
    "url": "https://www.google.com/maps/search/?api=1&query=Salinas%20Restaurant&query_place_id=ChIJHcejL7lZwokRNtXyXJbJ27g",
    "timezone": "America/New_York",
    "claimThisBusiness": false,
    "imagesCount": 1529,
    "additionalInfo": { "Accessibility": [{ "Wheelchair-accessible entrance": true }] },
    "attributes": { "has_wheelchair_accessible_entrance": true },
    "reservationLinks": [{ "name": "resy.com", "url": "https://resy.com/cities/new-york-ny/venues/salinas" }],
    "reviewsTags": [{ "title": "octopus", "count": 70 }],
    "facebooks": ["https://www.facebook.com/salinasnyc"],
    "instagrams": ["https://www.instagram.com/salinasnyc"],
    "reviews": [
        {
            "stars": 5,
            "text": "Such a good dinner spot — the cocktails were so good, the steak was cooked perfectly…",
            "publishedAtDate": "2026-09-26T21:34:41.957Z",
            "responseFromOwnerText": null,
            "reviewDetailedRating": { "Food": 5, "Service": 5, "Atmosphere": 5 },
            "reviewContext": { "Order type": "Dine in", "Wait time": "No wait" },
            "isLocalGuide": false,
            "likesCount": 0
        }
    ],
    "searchString": "restaurant",
    "rank": 1,
    "scrapedAt": "2026-09-27T10:18:46.945Z"
}
```

The results have ready-made tabs: **Places**, **Leads & contacts**, **Reviews** (one row per review, ideal for CSV), **Location & hours** and **Errors**.

Every run also saves:

- **RESULTS\_MAP**, an interactive map of all delivered places and the searched area. Use it to check coverage at a glance.
- **RUN\_SUMMARY**, with counts of delivered places, places skipped by each filter, places outside the area and duplicates, plus what was charged.

### Why this Google Maps scraper finds more places

Google Maps never returns more than about 120 places for one search, however large the map is. When you zoom out it also hides less prominent places. This scraper therefore:

1. looks up the real boundary of your location,
2. searches it cell by cell, splitting every cell where Google stops while still returning places inside it,
3. keeps splitting wide cells that are dense, because zoomed-out results leave out smaller businesses,
4. stops paging a cell as soon as Google starts padding the results with places outside the cell.

The result is close to what you would get by zooming into every street, at a fraction of the requests.

### Monitoring new businesses

Turn on **Only new places** and schedule the Actor (for example weekly) with the same search terms and location. The first run delivers everything. Each later run delivers, and charges for, only places that were not delivered before, each marked `isNewPlace: true`. Give the search a **Monitoring name** if you want several tasks to share one memory.

### Use it through the API, MCP and integrations

You can run the scraper from the [Apify API](https://docs.apify.com/api/v2), with the JavaScript or Python client, or from Make, Zapier, n8n, Google Sheets and Slack. AI agents can call it through the [Apify MCP server](https://mcp.apify.com). The input has a small number of plain fields that language models fill in reliably.

### FAQ

#### Do I need a Google account, API key or cookies?

No. The Actor reads public Google Maps data as a logged-out visitor. You don't need a Google Places API key.

#### How many places can I get?

All that Google Maps lists for your search in your area. A whole country works too, but it takes longer, so set *Maximum places per search term* when testing.

#### Why do some places have no email?

Emails come from the business's own website, and many small businesses don't publish one or have no website at all. You pay for contacts only when something is found.

#### Can I get all reviews of a place?

Yes. Set *Reviews per place* to a large number. Use *Only reviews since* with *Newest first* to collect just the recent ones and pay only for those.

#### Why doesn't the output have the price level or popular times?

They are not collected yet. Both are on the roadmap; until then `price` stays empty.

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

The Actor collects publicly available business information. Reviews can contain personal data (reviewer names and photos). You can turn that off with *Include reviewer names and profiles*. Personal data is protected by regulations such as GDPR, so only process it if you have a legitimate reason. If in doubt, consult a lawyer. See also Apify's post on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

#### What if Google changes something?

Unrecognised responses produce a clear `PARSER_MISMATCH` row instead of silent wrong data, so a change is noticed and fixed quickly. Please report it in the **Issues** tab.

#### I found a bug or need a feature

Open an issue in the **Issues** tab. Requests are usually answered within a day.

# Actor input Schema

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

What you would type into Google Maps, e.g. <code>restaurant</code>, <code>dentist</code>, <code>coffee shop</code>. Each term is searched separately across the whole location; a place found by two terms is delivered and charged once. Put the place name in <b>Location</b>, not here, to get full-area coverage.

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

City, district, county, state or country, e.g. <code>Austin, Texas, USA</code> or <code>Paris, France</code>. Its real boundary is looked up on OpenStreetMap, the area is covered with a map grid, and places outside the boundary are skipped and <b>never charged</b>. Leave empty to get what Google shows for the search term alone (up to ~150 places).

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

Stops each search term after this many places. Leave empty to get every place in the area. Filtered-out places do not count.

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

Language of place names, categories, opening hours and review translations.

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

Place URLs (<code>/maps/place/…</code>), search URLs (<code>/maps/search/…</code>, searched in the map view of the link), share links (<code>maps.app.goo.gl/…</code>) and <code>?cid=</code> links.

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

Google place IDs (<code>ChIJ…</code>), CIDs (a long number), or feature IDs (<code>0x…:0x…</code>).

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

Two-letter country code, e.g. <code>us</code>, <code>de</code>, <code>eg</code>. Also sets the Google region for results.

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

E.g. <code>Texas</code>.

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

E.g. <code>Travis County</code>.

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

E.g. <code>Austin</code>.

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

Searches about 2 km around the postal code's centre. Combine with Country for reliable results.

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

A GeoJSON <code>Polygon</code>, <code>MultiPolygon</code>, or a <code>Point</code> with <code>radiusKm</code>. Coordinates are <b>\[longitude, latitude]</b>. A 3 km circle: <code>{"type": "Point", "coordinates": \[-97.7431, 30.2672], "radiusKm": 3}</code>. You can draw polygons at geojson.io.

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

Keep only places whose Google category contains one of these words, e.g. <code>restaurant</code> keeps “Italian restaurant” and drops “Bar”.

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

Useful for brand searches like <code>Starbucks</code>, where Google also shows other coffee shops.

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

Skip places rated below this.

## `minimumReviews` (type: `integer`):

Skip places with fewer reviews.

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

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

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

Skip places marked permanently or temporarily closed.

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

Visits each place's own website (and its contact page when needed) to find emails, phones, and Facebook, Instagram, LinkedIn, X, YouTube, TikTok and Pinterest profiles. <b>Charged only when something is found.</b>

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

Adds the review star histogram, review keywords (“people often mention”), plus code, the building the place is located in, and more photos. One extra request per place.

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

0 = no reviews. Reviews come with text, stars, exact date, owner reply, photos, detailed ratings (food / service / atmosphere) and the reviewer. Set a large number to get all reviews of a place.

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

Order in which reviews are collected.

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

A date (<code>2025-01-31</code>) or a period (<code>3 months</code>, <code>1 year</code>, <code>10 days</code>). With “Newest first”, collection stops at the first older review, so you only pay for what you need.

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

Reviewer name, ID, profile URL and photo. Turn off to receive anonymous reviews.

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

0 = only the main photo. Photo URLs are free.

## `onlyNewPlaces` (type: `boolean`):

Remembers the places delivered by earlier runs of the same search and delivers (and charges) only new ones. The first run delivers everything.

## `monitoringId` (type: `string`):

Optional. Runs with the same name share their memory. By default the memory is tied to the exact search terms and location.

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

Hard cap across all search terms, URLs and place IDs.

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

How many places are enriched (details, reviews, website) at once.

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

Proxy traffic is included in the price. The default Apify datacenter proxy works well for Google Maps; if Google starts refusing it, the run switches itself to residential proxies.

## Actor input object example

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

# Actor output Schema

## `places` (type: `string`):

All scraped places.

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

Contact channels per business.

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

One row per review.

## `map` (type: `string`):

Every place and the searched area on an interactive map.

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

Counts of delivered, filtered and out-of-area places, and what was charged.

# 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,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("amrhassan25/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",
    "maxCrawledPlacesPerSearch": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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