# Google Maps Scraper — Business Leads & Places API | $0.60/1k (`glasswing/google-maps-scraper`) Actor

Scrape Google Maps places by keyword + location or by URL, place ID or CID: name, category, address, lat/lng, phone, website, rating, review count, hours.

- **URL**: https://apify.com/glasswing/google-maps-scraper.md
- **Developed by:** [Raffy](https://apify.com/glasswing) (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 $0.48 / 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

### What does Google Maps Scraper do?

Google Maps Scraper extracts **business and place data from Google Maps** and delivers it as clean JSON, CSV or Excel. Type what you are looking for ("dentist", "coffee shop", "hvac contractor") and where ("Austin, TX"), or paste Google Maps URLs, place IDs or CIDs, and get every place back with its name, categories, full address, coordinates, business phone, website, rating, review count, opening hours, closed status, plus code and Google IDs.

It works as a **Google Maps API alternative** and a cheap **Google Places API** substitute: no API key, no browser, no Google account. At **$0.60 per 1,000 places** it is one of the cheapest Google Maps scrapers on Apify.

Typical use cases:

- **Local lead lists**: every plumber, roofer or dental clinic in a city, with phone and website, ready for your CRM.
- **Market research and site selection**: how many competitors, how they are rated, where they cluster on the map.
- **Data enrichment**: turn a list of place IDs, CIDs or Maps links into structured, up-to-date business records.
- **Monitoring**: re-run a search weekly to spot new, closed or re-rated businesses.

It collects **business data only**. It does **not** collect reviews, reviewer names, owner names, photos or e-mail addresses, and it does not log in.

### Why use this Google Maps scraper?

- **Cheap and fast.** Plain HTTP requests to Google Maps' own data feed (no headless browser), so 1,000 places take a few minutes (deep scans longer) and cost $0.60.
- **Drop-in input.** Field names match the popular `compass/crawler-google-places` input (`searchStringsArray`, `locationQuery`, `maxCrawledPlacesPerSearch`, `language`, `countryCode`, `skipClosedPlaces`, `deeperCityScrape`, `startUrls`) and its main output columns (`title`, `categoryName`, `totalScore`, `reviewsCount`, `placeId`, `cid`, `location`...), so switching takes minutes.
- **Past the 120-place limit.** Google lists about 120 places per search; the **deeper scan** splits a city into map tiles to find many more.
- **No duplicates, honest rows.** Places found by several searches are saved once. Every row has a `status` (`ok`, `not_found`, `error`), and you pay only for `ok` rows.
- **Runs anywhere Apify runs**: schedules, API, webhooks, Google Sheets, Zapier, Make, and AI agents through the Apify MCP server.

### What data can Google Maps Scraper extract?

| Field | Type | Description |
|---|---|---|
| `title` | string | Business or place name |
| `categoryName` | string | Main category, e.g. `Dentist` |
| `categories` | array | All Google Maps categories |
| `address` | string | Full address as Google shows it |
| `neighborhood` | string | Neighborhood or district |
| `street` | string | Street and house number |
| `city` | string | City |
| `postalCode` | string | Postal / ZIP code |
| `state` | string | State, region or province |
| `countryCode` | string | Two-letter country code, e.g. `US` |
| `location` | object | Pin coordinates `{ "lat": ..., "lng": ... }` |
| `plusCode` | string | Plus code (Open Location Code), e.g. `862466XV+7J` |
| `phone` | string | The business phone as displayed |
| `phoneUnformatted` | string | The business phone in E.164 form, e.g. `+15127173147` |
| `website` | string | The business website |
| `totalScore` | number | Average Google rating, 1 to 5 |
| `reviewsCount` | integer | Number of Google reviews (a count only) |
| `price` | string | Price level or range, when Google serves one (often empty) |
| `hotelStars` | string | Hotel class, e.g. `5-star hotel` |
| `openingHours` | array | Weekly hours `[{ "day": "Monday", "hours": "8 AM–5 PM" }]` |
| `permanentlyClosed` | boolean | Google marks the place as permanently closed |
| `temporarilyClosed` | boolean | Google marks the place as temporarily closed |
| `timezone` | string | IANA time zone, e.g. `America/Chicago` |
| `placeId` | string | Google place ID (`ChIJ...`), works with the Google Places API |
| `cid` | string | Google Maps CID; `https://www.google.com/maps?cid=<cid>` opens the place |
| `fid` | string | Google Maps feature ID (`0x...:0x...`) |
| `kgmid` | string | Google Knowledge Graph ID, e.g. `/g/11cn2mvqwc` |
| `searchString` | string | The search term (or input URL/ID) that found the place |
| `rank` | integer | Position in that search's results (1 = first) |
| `searchPageUrl` | string | Google Maps page of the search |
| `language` | string | Language the data was read in |
| `url` | string | Google Maps link of the place |
| `status` | string | `ok`, `not_found` or `error` (see below) |
| `error` | string | Reason when `status` is not `ok` |
| `scrapedAt` | string | ISO 8601 time of extraction |

Optional fields are left out when Google does not publish them for a place (for example, many places have no website or phone).

#### Result status (tri-state output)

| `status` | Meaning | Billed? |
|---|---|---|
| `ok` | The place was found and extracted. | Yes |
| `not_found` | Google Maps answered, but the search has no results, the place ID does not exist, or every result was already saved by an earlier search in the run. | No |
| `error` | Google Maps could not be read after retries, or an input entry is not a Maps URL/ID. `error` says why. | No |

### How to scrape Google Maps

1. Open the Actor in Apify Console and click **Try for free**.
2. Enter one or more **Search terms** (e.g. `dentist`) and a **Location** (e.g. `Austin, TX`). Or paste **Google Maps URLs** or **Place IDs or CIDs**.
3. Set **Maximum places** (the Console starts you at 20 to see the output; leave it empty to get every place each search finds, up to 200 per search).
4. For a big city, switch on **Deeper scan of the whole area** to go past Google's ~120 places per search.
5. Click **Start**. The default input finishes in well under a minute.
6. Open the **Output** tab or **Export** the dataset as JSON, CSV, Excel, XML or HTML.

To automate it, use the **API** tab (Node.js, Python, curl examples) or add a **Schedule**.

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

This Actor uses **pay-per-event** pricing:

| Event | Price |
|---|---|
| Actor start | $0.005 per run (at the default 1 GB memory) |
| Place (`status: ok` row) | $0.0006 per place |

Example: 1,000 places cost about **$0.61** ($0.60 + one $0.005 start at the default 1 GB memory). 10,000 places cost about $6. Duplicates, `not_found` and `error` rows are never billed, and proxies are included. The free Apify plan's monthly credit covers several thousand places. You can cap spending with **Maximum places** and with the run's **Max total charge** option; the Actor stops fetching when either limit is reached.

### Input

See the **Input** tab for the full schema. The main options:

| Field | Type | Default | Description |
|---|---|---|---|
| `searchStringsArray` | array of strings | - | What to search for, e.g. `["dentist", "orthodontist"]` |
| `locationQuery` | string | - | Where, e.g. `Austin, TX`; each term is searched as "term in location" |
| `maxItems` | integer | empty | Maximum places saved in the whole run. Empty: up to `maxCrawledPlacesPerSearch` per search, or 200 per search when that is empty too, plus one row per place ID/URL |
| `maxCrawledPlacesPerSearch` | integer | empty | Maximum places per search term |
| `deeperCityScrape` | boolean | `false` | Split the area into map tiles to find more than ~120 places per search |
| `startUrls` | array of strings | one example search | Google Maps search URLs, place URLs, `?cid=` links, maps.app.goo.gl links |
| `placeIds` | array of strings | - | Place IDs (`ChIJ...`) or numeric CIDs |
| `language` | string | `en` | Language of categories, hours and addresses (`hl`) |
| `countryCode` | string | `us` | Country that biases the search (`gl`) |
| `skipClosedPlaces` | boolean | `false` | Leave out permanently or temporarily closed places |

Example input:

```json
{
    "searchStringsArray": ["dentist"],
    "locationQuery": "Austin, TX",
    "maxItems": 500,
    "deeperCityScrape": true,
    "language": "en",
    "countryCode": "us"
}
```

Look up known places directly:

```json
{
    "placeIds": ["ChIJb5gBC-u0RIYRHkntaUcdJGs", "7720327853776914718"],
    "startUrls": ["https://www.google.com/maps/place/Brandenburger+Tor/@52.5162746,13.3777041,17z"]
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. Example rows from a real run (opening hours shortened):

```json
[
    {
        "url": "https://www.google.com/maps/search/?api=1&query=Mozart%27s%20Coffee%20Roasters&query_place_id=ChIJTWZhRFW1RIYRrdGa7DPzT7w",
        "status": "ok",
        "scrapedAt": "2026-10-20T15:27:57.972Z",
        "searchString": "coffee shop",
        "rank": 1,
        "title": "Mozart's Coffee Roasters",
        "categoryName": "Coffee shop",
        "categories": ["Coffee shop", "Bakery", "Cafe", "Event venue", "Tourist attraction"],
        "address": "3825 Lake Austin Blvd, Austin, TX 78703",
        "neighborhood": "Westfield",
        "street": "3825 Lake Austin Blvd",
        "city": "Austin",
        "postalCode": "78703",
        "state": "Texas",
        "countryCode": "US",
        "location": { "lat": 30.2954696, "lng": -97.7839512 },
        "plusCode": "862476W8+5C",
        "phone": "(512) 477-2900",
        "phoneUnformatted": "+15124772900",
        "website": "https://mozartscoffee.com/",
        "totalScore": 4.5,
        "reviewsCount": 10984,
        "openingHours": [
            { "day": "Monday", "hours": "7 AM–12 AM" },
            { "day": "Tuesday", "hours": "7 AM–12 AM" }
        ],
        "permanentlyClosed": false,
        "temporarilyClosed": false,
        "timezone": "America/Chicago",
        "placeId": "ChIJTWZhRFW1RIYRrdGa7DPzT7w",
        "cid": "13569331606629044653",
        "fid": "0x8644b5554461664d:0xbc4ff333ec9ad1ad",
        "kgmid": "/m/020z4hc",
        "searchPageUrl": "https://www.google.com/maps/search/coffee+shop+in+Austin%2C+TX?hl=en",
        "language": "en"
    },
    {
        "url": "https://www.google.com/maps",
        "status": "not_found",
        "error": "7720327853776914718 was already returned earlier in this run.",
        "scrapedAt": "2026-10-20T15:49:12.410Z"
    }
]
```

### Tips

- Put several search terms into one run instead of many single-term runs: you pay the start fee once and duplicates are removed across terms.
- Name the place in the search term itself ("pizza near Times Square") when a city-wide location is too broad, and leave **Location** empty.
- Set **Country** to the country you search in when a city name exists in several countries (Paris, TX vs Paris, France).
- For a whole metro area, turn on the **deeper scan** and raise **Maximum places**; you pay only for the places saved.

### Limitations

- Google lists about 120 places for one search and one map view. Without the deeper scan, that is the most one search term returns.
- The deeper scan covers the area Google shows for the location (a city or region view); places just outside it are left out on purpose.
- Reviews, review texts, reviewer names, owner names, photos, popular times and e-mail addresses are not collected.
- `price` is often empty: Google's logged-out Maps feed serves price levels for few places.
- Results reflect Google Maps at the time of the run. If Google changes its data format, rows come back as `error`; please report it in the **Issues** tab.

### FAQ

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

The Actor collects publicly visible business listings (names, addresses, business phones, websites, ratings). You are responsible for how you use the output: read the legal notice below and Google's terms, and check the rules that apply to you, for example on cold calling or e-mail marketing.

#### How is this different from the Google Places API?

There is no API key, no per-request Google billing and no field masks: one flat price per place, with address parts, hours, plus code and IDs in every row. The `placeId` it returns works with Google's own Places API if you need more.

#### Can I use this Actor from an AI agent or MCP client?

Yes. Inputs are plain strings, every row is self-describing (`status` + `error`), and results can be fetched via the dataset API or the Apify MCP server.

#### Why did I get fewer places than `maxItems`?

Google listed fewer places for the search (turn on the deeper scan for big cities), places were duplicates of earlier searches, `skipClosedPlaces` removed closed ones, or the run hit your **Max total charge** limit. The run log says which.

#### Does it return e-mails or social profiles?

No. It returns the business's own website and phone as Google shows them; it does not crawl websites for e-mails.

### Legal and data-protection notice

This Actor extracts only business and place information that Google Maps shows publicly to any visitor; it does not extract reviews, reviewer or owner names, private e-mail addresses or other private user data, and it does not log in or get around access controls. However, your results could still contain personal data, for example when a business is listed under a person's name or uses a personal phone number as its business number. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you are unsure whether your reason is legitimate, consult your lawyers. You are responsible for complying with Google's terms of service and applicable law when using the extracted data.

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by Google LLC. Google and Google Maps are trademarks of Google LLC. All trademarks belong to their respective owners.

# Changelog

This Actor's version history is a separate document: https://apify.com/glasswing/google-maps-scraper/changelog.md

# Actor input Schema

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

What to look for on Google Maps, one term per line, e.g. "dentist", "coffee shop", "hvac contractor". Each term is searched in the Location below ("dentist in Austin, TX"). You can also type the place into the term itself ("pizza near Times Square") and leave Location empty.

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

City, region, postcode or address the search terms are run in, e.g. "Austin, TX", "Berlin", "SW1A 1AA". Leave empty when the search terms already say where.

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

Stop after this many places have been saved, across all searches. Each saved place with status `ok` is one billable result; duplicates across searches are removed and never billed. Leave empty to get up to "Maximum places per search" for every search (200 per search when that is empty too), plus one row per place ID or place URL.

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

Optional cap for each search term. Google lists about 120-160 places for one search; turn on the deeper scan below to go past that. Leave empty for no per-search cap (when Maximum places is empty too, each search stops at 200 places).

## `deeperCityScrape` (type: `boolean`):

When a search hits Google's ~120-place limit, split the area into smaller map tiles and search each one, to find many more places in a big city. Only places inside the searched area are kept. Uses more requests, but you pay only per saved place. With both place limits empty, a search stops at 200 places; set Maximum places (or Maximum places per search) higher to collect more.

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

Optional. Google Maps search URLs (https://www.google.com/maps/search/plumber+in+Denver/@39.73,-104.99,12z), place URLs (https://www.google.com/maps/place/...), ?cid= links or maps.app.goo.gl short links. A search URL keeps its map position.

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

Optional. Google place IDs (ChIJ...) or numeric CIDs to look up directly, one per line. Returns one row per place.

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

Language of category names, opening-hours text and addresses (Google's hl code), e.g. en, de, fr, es, pt-BR.

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

Two-letter country code Google uses to interpret the search (gl), e.g. us, gb, de. Set it to the country you search in when a city name exists in several countries.

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

Leave out places Google marks as permanently or temporarily closed. They are not saved and not billed.

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

Leave as is: the Actor uses Apify's datacenter proxy and retries through residential IPs on its own, at no extra cost to you. Only your own proxy URLs (if you add them) override this.

## Actor input object example

```json
{
  "searchStringsArray": [
    "coffee shop"
  ],
  "locationQuery": "Austin, TX",
  "maxItems": 20,
  "deeperCityScrape": false,
  "startUrls": [
    "https://www.google.com/maps/search/coffee+shop+in+Austin,+TX"
  ],
  "language": "en",
  "countryCode": "us",
  "skipClosedPlaces": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

# 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": [
        "coffee shop"
    ],
    "locationQuery": "Austin, TX",
    "maxItems": 20,
    "startUrls": [
        "https://www.google.com/maps/search/coffee+shop+in+Austin,+TX"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("glasswing/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": ["coffee shop"],
    "locationQuery": "Austin, TX",
    "maxItems": 20,
    "startUrls": ["https://www.google.com/maps/search/coffee+shop+in+Austin,+TX"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("glasswing/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": [
    "coffee shop"
  ],
  "locationQuery": "Austin, TX",
  "maxItems": 20,
  "startUrls": [
    "https://www.google.com/maps/search/coffee+shop+in+Austin,+TX"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call glasswing/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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