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

Extract business listings from Google Maps — name, address, phone, website, rating, reviews, opening hours, category and coordinates. Search by keyword and location.

- **URL**: https://apify.com/prattsolutions/google-maps-scraper.md
- **Developed by:** [John Pratt](https://apify.com/prattsolutions) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 scraped places

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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** into a clean dataset: **name, category, address, phone, website, star rating, review count, opening hours, coordinates and Google Place ID**, plus a sample of reviews. That's 37 fields per place. Type a search the way you would in Google Maps, such as `coffee shops in Austin, TX`, or a business name with its address, and get one row per place back. Export the results as JSON, CSV or Excel, or read them from the API.

### What can Google Maps Scraper do?

- **Search the way you search Google Maps.** A business type plus a location returns the places Google lists for it. A business name plus its address returns that one listing.
- **Collect contact and listing details:** phone, website, the full address and its parts, category, price range, opening hours, popular times and the attributes on the place's About tab.
- **Collect ratings and reviews:** average rating, total review count, the number of reviews for each star rating, and a sample of reviews.
- **Collect location data:** latitude, longitude, Plus Code, time zone, Google Place ID and CID.
- **Get results in any Google interface language**, so categories, opening hours and other text come back in the language you need.
- **Use the Apify platform:** run it from Apify Console or the API, schedule runs, get a webhook when a run finishes, and connect the results to Apify integrations.

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

| Data | Fields |
| --- | --- |
| Business | `title`, `category`, `descriptions`, `status`, `price_range` |
| Contact | `phone`, `website`, `address`, `complete_address` |
| Ratings and reviews | `review_rating`, `review_count`, `reviews_per_rating`, `user_reviews` |
| Hours and traffic | `open_hours`, `popular_times` |
| Location | `latitude`, `longitude`, `plus_code`, `timezone` |
| Google IDs and links | `place_id`, `cid`, `data_id`, `link`, `reviews_link`, `street_view_url` |
| Extras | `thumbnail`, `images`, `menu`, `reservations`, `order_online`, `owner`, `about`, `credit_cards_accepted` |

Every field is described in [Output fields](#output-fields).

### How to scrape Google Maps

1. Open the Actor in Apify Console and go to the **Input** tab.
2. Enter your searches in **Search queries**, one per line.
3. Optionally raise **Results depth** to load more places for broad searches, or change the **Language**.
4. Click **Start**. Searches run one after another.
5. When the run finishes, open the **Output** tab. Switch between the **Overview**, **Location** and **Reviews** views, or export the dataset as JSON, CSV, Excel and other formats.

### Input

The input is a JSON object with three fields. Only `keywords` is required.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `keywords` | array of strings | required | Google Maps searches, one per item, worded as you would type them into Google Maps. A business type plus a location (`coffee shops in Austin, TX`) returns the places Google lists for it. A business name plus its address returns that single listing. |
| `depth` | integer, 1 or more | `10` | How many times to scroll each search's results list. Each scroll loads more places, so a higher value returns more businesses for broad searches but makes each search take longer. |
| `language` | string | `en` | Google interface language code, such as `en`, `es`, `de` or `fr`. It sets the language of categories, opening hours and other text. |

```json
{
    "keywords": ["coffee shops in Austin, TX", "Katz's Delicatessen, 205 E Houston St, New York"],
    "depth": 10,
    "language": "en"
}
```

#### Tips for writing searches

- **Put a location in every broad search**, such as `plumbers in Denver, CO` rather than `plumbers`. Without one, the results depend on where Google thinks the search comes from.
- **Split big areas into smaller searches.** Google Maps lists only a limited number of places per search (around 120), so search one neighborhood or ZIP code at a time to cover a whole city.
- **Add the street address when you want one business**, so Google opens that exact listing.

### Output

Each place is one item in the run's dataset. Every item has all 37 fields; this example is shortened:

```json
{
    "query": "Katz's Delicatessen, East Houston Street, New York",
    "title": "Katz's Delicatessen",
    "category": "Deli",
    "address": "205 E Houston St, New York, NY 10002",
    "phone": "(212) 254-2246",
    "website": "https://katzsdelicatessen.com/",
    "review_rating": "4.500000",
    "review_count": "56120",
    "reviews_per_rating": { "1": 1846, "2": 1440, "3": 3392, "4": 8901, "5": 40541 },
    "open_hours": { "Monday": ["8 AM–11 PM"], "Saturday": ["Open 24 hours"] },
    "complete_address": {
        "borough": "Manhattan",
        "street": "205 E Houston St",
        "city": "New York",
        "postal_code": "10002",
        "state": "New York",
        "country": "US"
    },
    "latitude": "40.722233",
    "longitude": "-73.987429",
    "plus_code": "P2C7+V2 New York",
    "timezone": "America/New_York",
    "place_id": "ChIJCar0f49ZwokR6ozLV-dHNTE",
    "cid": "3545819340560108778",
    "menu": "{\"link\":\"\",\"source\":\"\"}",
    "emails": ""
}
```

#### Good to know about the output

- **Numbers come as text.** `review_rating` (for example `"4.500000"`), `review_count`, `latitude` and `longitude` are strings, so convert them before doing math.
- **Four fields are JSON text.** `menu`, `owner`, `reservations` and `order_online` are JSON-encoded strings, for example `"null"` or `"{\"link\":\"\",\"source\":\"\"}"`. Parse them with `JSON.parse` or `json.loads`.
- **Missing details are empty.** Text fields are `""` and object or array fields are `null` when Google has no data for them.
- **Two fields are always empty.** `emails` is `""` because the Actor doesn't visit business websites. `user_reviews_extended` is `null` because the Actor doesn't load extra review pages.
- **Reviews are a sample**, taken from the place page, not the full review history.

#### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `query` | string | The search from `keywords` that returned this place. |
| `input_id` | string | ID of the search that found this place. Places from the same search share it. |
| `title` | string | Business name as shown on Google Maps. |
| `category` | string | Main category Google assigns to the place. |
| `address` | string | Full address on one line. |
| `complete_address` | object | Address parts: `borough`, `street`, `city`, `postal_code`, `state`, `country`. |
| `phone` | string | Phone number as Google displays it. |
| `website` | string | Business website URL. |
| `link` | string | Link to the place on Google Maps. |
| `review_rating` | string | Average star rating from 1 to 5, with six decimals. |
| `review_count` | string | Total number of Google reviews. |
| `reviews_per_rating` | object | Number of reviews for each star rating, keyed `"1"` to `"5"`. |
| `user_reviews` | array | Sample reviews. Each has `Name`, `ProfilePicture`, `Rating`, `When`, `Description`, `Images`, `review_id`, `author_url`, `published_at`, `language`, `text_original`, `text_translated` and related fields. |
| `user_reviews_extended` | null | Always `null` in this Actor. |
| `open_hours` | object | Opening hours per weekday. Each day maps to a list of time ranges. |
| `popular_times` | object | How busy the place usually is: weekday, then hour of the day (0–23), then a relative busyness value. Empty when Google doesn't show it. |
| `status` | string | Status or summary line Google shows for the place. |
| `descriptions` | string | Google's description of the place. |
| `price_range` | string | Price level or range, such as `$$`. |
| `latitude` | string | Latitude in decimal degrees. |
| `longitude` | string | Longitude in decimal degrees. |
| `plus_code` | string | Google Plus Code for the location. |
| `timezone` | string | IANA time zone, such as `America/New_York`. |
| `place_id` | string | Google Place ID. Works with the Google Places API and as a stable key for the place. |
| `cid` | string | Google's numeric ID for the place. `https://maps.google.com/?cid=<cid>` opens it. |
| `data_id` | string | Google's internal place identifier. |
| `reviews_link` | string | Link to the place's reviews. Often empty. |
| `thumbnail` | string | URL of the place's main photo. |
| `images` | array | Photos from the place page, each with `title` and `image` (URL). Often empty. |
| `street_view_url` | string | Street View link. Often empty. |
| `reservations` | string (JSON) | Reservation links, each with `link` and `source`. `"null"` when there are none. |
| `order_online` | string (JSON) | Online ordering links, each with `link` and `source`. `"null"` when there are none. |
| `menu` | string (JSON) | Menu link with `link` and `source`. Both are empty when there is no menu. |
| `owner` | string (JSON) | Google account that manages the listing, with `id`, `name` and `link`. |
| `credit_cards_accepted` | string | Comma-separated list of accepted cards. |
| `about` | array | Attributes from the About tab, grouped by section. Each section has `id`, `name` and `options`; each option has `name` and `enabled`. |
| `emails` | string | Always empty in this Actor. |

The same field descriptions, with examples, are in the Actor's dataset schema (`.actor/dataset_schema.json`), which Apify shows to API clients and AI agents.

### How to use the Google Maps Scraper API

Run the Actor and read its results with the [Apify API](https://docs.apify.com/api/v2). Get your API token from **Apify Console → Settings → API & Integrations**. The examples use the Actor ID `qo6wnBx3a0y8TCEjZ`. The **API** tab on the Actor's page in Console has ready-made snippets for more languages.

#### Run and get results in one request

The `run-sync-get-dataset-items` endpoint starts a run and returns its dataset items in the same response. Apify ends the request with HTTP `408` if the run takes longer than 300 seconds, so use this endpoint only for a few targeted lookups:

```bash
curl -X POST "https://api.apify.com/v2/acts/qo6wnBx3a0y8TCEjZ/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"keywords\": [\"Katz's Delicatessen, 205 E Houston St, New York\"]}"
```

#### Start a run and fetch the results when it finishes

For broad searches or many keywords, start the run, wait for it to finish, then read the dataset:

1. `POST https://api.apify.com/v2/acts/qo6wnBx3a0y8TCEjZ/runs?token=YOUR_APIFY_TOKEN` with the input as the JSON body. The response's `data.id` is the run ID and `data.defaultDatasetId` is the dataset ID.
2. `GET https://api.apify.com/v2/actor-runs/RUN_ID?token=YOUR_APIFY_TOKEN&waitForFinish=60` waits up to 60 seconds and returns the run. Repeat until `data.status` is `SUCCEEDED`, `FAILED`, `TIMED-OUT` or `ABORTED`.
3. `GET https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_APIFY_TOKEN&format=json` returns the places. Use `format=csv` or `format=xlsx` for spreadsheets.

Python:

```python
import requests

TOKEN = "YOUR_APIFY_TOKEN"
API = "https://api.apify.com/v2"

run = requests.post(
    f"{API}/acts/qo6wnBx3a0y8TCEjZ/runs",
    params={"token": TOKEN},
    json={"keywords": ["coffee shops in Austin, TX"], "depth": 10, "language": "en"},
).json()["data"]

while run["status"] in ("READY", "RUNNING", "TIMING-OUT", "ABORTING"):
    run = requests.get(
        f"{API}/actor-runs/{run['id']}",
        params={"token": TOKEN, "waitForFinish": 60},
    ).json()["data"]

places = requests.get(
    f"{API}/datasets/{run['defaultDatasetId']}/items",
    params={"token": TOKEN, "format": "json"},
).json()

for place in places:
    print(place["title"], "|", place["phone"], "|", place["website"])
```

Node.js, with the [`apify-client`](https://docs.apify.com/api/client/js) package. `call()` waits for the run to finish:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('qo6wnBx3a0y8TCEjZ').call({
    keywords: ['coffee shops in Austin, TX'],
    depth: 10,
    language: 'en',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const place of items) {
    console.log(place.title, '|', place.phone, '|', place.website);
}
```

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

It costs **$2.50 per 1,000 places** ($0.0025 per place). Each place saved to the dataset is one charge, and the price already includes the details (opening hours, popular times, About attributes, review sample and photos). There's no separate charge for compute; the Actor covers its own platform usage. Each run also has a start fee of $0.00005, Apify's standard Actor start charge.

Examples:

- A broad search like `coffee shops in Austin, TX` that returns 76 places costs about **$0.19**.
- A single business lookup costs **$0.0025**.
- 10,000 places cost **$25**.

To cap what a run can cost, set **Maximum cost per run** in the run options (or `maxTotalChargeUsd` in the API). The lowest cap you can set is $0.25. When a run reaches its cap, it stops saving places and ends.

### FAQ

#### How many places can I get per search?

Google Maps lists around 120 places per search at most, so a higher `depth` stops helping past that point. To get more, split the area into several smaller searches.

#### How long does a run take?

Searches run one after another. A single business lookup takes about 20–35 seconds. Broad searches take longer, because the scraper opens every place on the list. Each search is capped at 10 minutes: places not reached by then are left out, and the run still succeeds.

#### Why was my first run in a while slow to start?

If nobody has run the Actor for a few weeks, its backend needs to wake up first. For a few minutes the log shows `Lambda is waking up (...); retrying in 15s.`, then the run continues normally. Runs after that start right away.

#### What happens if a search fails?

The run stops at the failed search and is marked as failed. Places from the searches that finished before it stay in the dataset.

#### Do I pay for searches that return nothing?

No. You pay only for places saved to the dataset, plus the $0.00005 start fee per run.

#### Why are some fields empty?

Google doesn't show every detail for every place. Landmarks and areas, such as a market or a park, come back with few fields because Google Maps doesn't treat them as businesses. `emails` and `user_reviews_extended` are always empty in this Actor.

#### Can I connect the results to other tools?

Yes. Every run's dataset can be downloaded as JSON, CSV, Excel, XML, HTML or RSS, or read through the API. You can also schedule runs, get webhooks when a run finishes, and send the results to the integrations Apify supports.

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

The Actor extracts only data that Google Maps shows publicly. Reviews can contain personal data, such as reviewer names and profile photos, which laws like the GDPR protect. Collect and store personal data only if you have a legitimate reason, and check with a lawyer if you're unsure.

# Actor input Schema

## `keywords` (type: `array`):

What to search on Google Maps, one query per line, worded as you would type it into Google Maps. Use a business type plus a location ("coffee shops in Austin, TX") to get the places Google lists for it, or a business name plus its address to get that single listing. Queries run one after another, and each result is tagged with the query that found it.

## `depth` (type: `integer`):

How many times to scroll each query's results list. Each scroll loads more places, so a higher value returns more businesses for broad queries but makes each query take longer.

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

Google interface language code, such as en, es, de or fr. It sets the language of the text Google returns, such as categories and opening hours.

## `mode` (type: `string`):

Detailed opens every place page and returns all fields, up to about 120 places per query. Fast reads Google's search results directly: up to 20 places per query around the search center below, with basic fields (name, address, phone, website, rating, review count, opening hours, coordinates).

## `coordinates` (type: `string`):

Required in fast mode: the point to search around, as "latitude,longitude", for example "30.2672,-97.7431" for Austin, TX. Ignored in detailed mode.

## `radiusMeters` (type: `integer`):

Fast mode keeps only places within this distance of the search center. Ignored in detailed mode.

## `zoom` (type: `integer`):

Google Maps zoom level for fast-mode searches, from 1 (whole world) to 21 (single building). Ignored in detailed mode.

## Actor input object example

```json
{
  "keywords": [
    "coffee shops in Austin, TX",
    "Katz's Delicatessen, 205 E Houston St, New York"
  ],
  "depth": 10,
  "language": "en",
  "mode": "detailed",
  "coordinates": "30.2672,-97.7431",
  "radiusMeters": 5000,
  "zoom": 15
}
```

# Actor output Schema

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

All places in the run's default dataset. Each field is described in the dataset schema.

# 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 = {
    "keywords": [
        "coffee shops in Austin, TX"
    ],
    "coordinates": "30.2672,-97.7431"
};

// Run the Actor and wait for it to finish
const run = await client.actor("prattsolutions/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 = {
    "keywords": ["coffee shops in Austin, TX"],
    "coordinates": "30.2672,-97.7431",
}

# Run the Actor and wait for it to finish
run = client.actor("prattsolutions/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 '{
  "keywords": [
    "coffee shops in Austin, TX"
  ],
  "coordinates": "30.2672,-97.7431"
}' |
apify call prattsolutions/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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