# Complete Guide: How to Scrape Google Maps Places in Python Using a Google Maps Scraper

This complete guide to Google Maps Places & Ratings Scraper shows how to run a google maps scraper from Python through the Apify API and get back one structured row per place: name, category, address, star rating, review count and coordinates. It is written by the Actor's builder: the Actor's behaviour below is read from its source and from a measured run.

When analyzing local business markets or conducting competitive intelligence, Google Maps provides a large directory of points of interest. However, manually aggregating locations, star ratings, review counts, and precise GPS coordinates is impractical at scale.

In this Google Maps Places & Ratings Scraper tutorial, we will use Python and the dedicated Apify Actor `reventadata/google-maps-scraper` to extract structured place records efficiently; proxy handling and retries are built in. Technical prerequisites are minimal: Python 3.11+, one pip package, and an Apify API token. Estimated time to complete: 5 to 10 minutes; the measured run behind the guide finished in 5.3 seconds for five places.

## TL;DR

In this tutorial, you'll learn to start the Google Maps Places & Ratings Scraper Apify Actor from Python, wait for the run, check its status, read the dataset and export it. Key steps: install `apify-client`, set `APIFY_API_TOKEN`, define `searchQueries` and `maxItems`, call the Actor, read `run.default_dataset_id`. The Actor lives at [apify.com/reventadata/google-maps-scraper](https://apify.com/reventadata/google-maps-scraper). Expected outcome: a list of dicts shaped like the sample record further down.

## Prerequisites and setup

To follow this tutorial, you will need:

1. Python 3.11+ installed locally (apify-client 3.x requires it).
2. The Apify API client for Python (`pip install "apify-client~=3.0"`).
3. An Apify account and API token.

You need an Apify account. Apify Console is the web interface where runs, datasets and tokens are managed; the token is on its **Settings > API & Integrations** page. The platform documentation index is at [docs.apify.com](https://docs.apify.com/).

Install the Apify client library via pip:

```bash
pip install "apify-client~=3.0"
```

Set your API token in your environment:

```bash
export APIFY_API_TOKEN="your_apify_token_here"
```

## Understanding Google Maps Places & Ratings Scraper

The Actor is a Python program running in a container on the Apify platform. For each query it builds a desktop Google Maps search URL (`hl=en`), fetches it through Apify Proxy (the Unblocker group by default), reads the place-data link that the search page preloads, fetches that, and parses the place list into rows. One row is pushed per place; a place id seen under an earlier query is not pushed again.

Blocking is detected by the body of the response, never by the HTTP status code: a Google "sorry" or "unusual traffic" page triggers a retry on a fresh proxy session after a 3 second pause. Retries stop when fewer than 90 seconds of run time remain, so a failing run ends with its real error instead of a timeout. A run that pushes zero rows raises on purpose, so the platform marks it `FAILED`.

Advantages over alternatives:

- It needs no Google Cloud project or Places API key: it reads the public Google Maps search page.
- It sends plain asynchronous HTTP requests (`httpx`) with no headless browser, in a 512 MB container; the measured test run took 5.3 seconds.

Limitations to know before you start:

- Each query reads the first Google Maps results list for that search, not an endless scroll.
- `maxItems` caps the whole run, not each query.
- It does not read individual reviews or reviewer profiles.
- `rating`, `reviewsCount`, `category` and `address` can be null.

> **Note on Data Fidelity:** This scraper extracts aggregate place metrics (the overall rating and total review count) and geographic coordinates. It does not scrape individual review text bodies or reviewer profiles.

## Step-by-step guide to using Google Maps Places & Ratings Scraper

### Step 1: Accessing the Actor

Navigate to the [Google Maps Places & Ratings Scraper](https://apify.com/reventadata/google-maps-scraper) page on Apify Store. The page lists the input fields, output columns, and current pricing. The page also offers a "Try for free" button for running it in Apify Console. This tutorial drives the Apify Actor from Python so the workflow is automated and reproducible.

### Step 2: Configuring input parameters

The Actor takes an array of search queries (e.g. `"pizza barcelona"`) and an optional cap on the total number of places saved in the run (20 if unset). Each query returns the first Google Maps results list for that search, so add more specific queries (e.g. by neighbourhood) to collect more places:

```python
import os
from apify_client import ApifyClient

# 1. Initialize ApifyClient with token from environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])

# 2. Define the extraction parameters
run_input = {
    "searchQueries": ["pizza barcelona"],
    "maxItems": 10,
}
```

Every input field, with the exact names from the input schema:

| Field | Type | Behaviour |
| --- | --- | --- |
| `searchQueries` | array of strings | Searches, written as you would type them into Google Maps. Empty list: the run logs a warning and ends with no rows. |
| `maxItems` | integer, 1 to 5000 | Total places saved in the run. Default 20. |
| `minRating` | number, 0 to 5 | Optional. Places rated below it are skipped; unrated places are skipped too. |
| `proxyConfiguration` | object | Apify proxy settings. Default: Apify Proxy, group `UNBLOCKER`. |

Three complete configurations, as inline values you can drop into `run_input`:

| Use case | `searchQueries` | `maxItems` | `minRating` |
| --- | --- | --- | --- |
| Quick check | `["pizza barcelona"]` | `5` | unset |
| One city, several districts | `["pizza eixample barcelona", "pizza gracia barcelona", "pizza born barcelona"]` | `60` | unset |
| Only well-rated hotels | `["hotels tokyo"]` | `20` | `4.5` |

Best practice: write queries the way a person would type them into Google Maps, including the city, and let `searchQuery` in the output tell you which query found each row.

### Step 3: Advanced configuration

`proxyConfiguration` accepts the standard Apify proxy object. Unblocker is the measured default; the Actor turns TLS verification off only on that hop, because Unblocker terminates TLS with its own certificate, and verifies on every other proxy group.

Performance against thoroughness is a query question, not a setting: more specific queries cover more places; a larger `maxItems` on one broad query does not. Cost scales with rows saved, so `minRating` is the lever that trims the bill before a row exists.

### Step 4: Optional enrichment features

There are none. The Actor has no paid add-ons and no enrichment tier; every row carries the same eleven fields at the same price.

### Step 5: Running the Actor

Run the Actor on the Apify platform. Execution runs in an isolated container with Apify proxy handling (Unblocker by default) and retries:

```python
# 3. Start the Actor run and wait for it to complete
print("Starting Google Maps extraction...")
run = client.actor("reventadata/google-maps-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
    status = run.status if run else "unknown"
    raise SystemExit(f"Run did not succeed (status: {status}).")

# 4. Fetch the resulting dataset items
dataset = client.dataset(run.default_dataset_id).list_items().items

print(f"Extraction complete! Retrieved {len(dataset)} places.\n")
```

`call()` starts the run and waits for completion before returning the `Run` object; it returns `Run | None`, so the code checks for `None` before reading `run.status`. The run lifecycle in the Console goes from `RUNNING` to `SUCCEEDED` or `FAILED`; the measured run took 5.336 seconds at 512 MB of memory.

### Step 6: Examining the results

Iterate through the returned dataset items to display business names, ratings, review counts, and coordinates:

```python
for item in dataset:
    title = item.get("title")
    # category, address, rating and reviewsCount can be null
    category = item.get("category") or "N/A"
    rating = item.get("rating")
    reviews_count = item.get("reviewsCount")
    address = item.get("address") or "N/A"
    lat = item.get("latitude")
    lon = item.get("longitude")

    print(f"Place: {title} ({category})")
    if rating is None:
        print("  Rating: not rated yet")
    else:
        print(f"  Rating: {rating} / 5.0 ({reviews_count} reviews)")
    print(f"  Address: {address}")
    print(f"  Coordinates: ({lat}, {lon})")
    print("-" * 50)
```

Here is a verbatim record returned by the benchmark run for Barcelona:

```json
{
    "id": "0x12a4a2eb343246fb:0x4db9be4af396861f",
    "title": "Restaurant Pizzeria Edén",
    "url": "https://www.google.com/maps/search/?api=1&query=Restaurant+Pizzeria+Ed%C3%A9n&query_place_id=ChIJ-0YyNOuipBIRH4aW80q-uU0",
    "category": "Pizza restaurant",
    "address": "Carrer de Provença, 332, Eixample, 08037 Barcelona, Spain",
    "rating": 4.8,
    "reviewsCount": 5778,
    "latitude": 41.397219899999996,
    "longitude": 2.1649694,
    "searchQuery": "pizza barcelona",
    "scrapedAt": "2026-10-22T01:35:03.953011+00:00"
}
```

### Step 7: Downloading and exporting data

In Apify Console, **Storage > Datasets > Export** offers JSON, CSV, XML, Excel, HTML table, RSS and JSONL, with field selection to trim columns. The same export is available via API with `?format=csv` (or `json`, `xlsx`, `xml`) or from Python via `get_items_as_bytes(item_format="csv")` on the dataset client. Unnamed datasets are deleted after your plan's retention period; name the dataset or download it if you need it longer. JSON keeps nulls as nulls; CSV is the format for a spreadsheet.

## Working with the API

Authentication is the API token, passed to `ApifyClient` once. The three Python blocks above are the whole integration: start and wait, read the dataset, loop over the rows.

Error handling that matches what the code does: `KeyError` if the token variable is unset, a `None` return from `call()`, and a `FAILED` status when the Actor extracted zero rows and raised. The run log in Console carries the Actor's own messages: "Challenge / captcha detected" for a Google block, "Place data not in the expected shape" for a parse failure, "No searchQueries provided in input." for an empty query list.

Webhooks: on the Actor's **Integrations** tab in Console you can register a webhook that sends a POST request to your URL when a run event such as success or failure occurs; `call()` also accepts ad-hoc `webhooks` for a single run. Scheduling: Apify Schedules run an Actor on a cron expression with optional input overrides, from Console, the REST API or the Python client. Rate limits: the official clients implement retries with exponential backoff for failed or rate-limited requests.

## Advanced usage patterns

- **Batch queries in one run.** Several `searchQueries` share one start event and one dataset; dedupe across queries is built in. One query per neighbourhood covers a city better than one broad query.
- **Cap spend per run.** `call()` accepts `max_total_charge_usd`, a limit on the total charged amount.
- **Read the bill from the run object.** `run.charged_event_counts` lists the events charged; the measured run shows `apify-actor-start: 1` and `apify-default-dataset-item: 5`.
- **Paging.** `list_items()` makes one API call; `iterate_items()` pages through larger datasets for you.
- **Scheduled runs plus webhooks.** Combine a cron schedule with an Apify webhook to deliver fresh place data to your application when a run finishes.
- **Integrating with no-code tools.** Connect the scraper to Zapier or Make to insert new place records directly into apps such as Google Sheets.
- **Monitoring and alerts.** Configure run failure notifications in Apify Console to receive email alerts if a scrape fails.
- **Scaling.** `maxItems` accepts up to 5,000 places per run; add more targeted queries to reach more places.

## Understanding your data

- `id` is Google's feature id for the place; it is what the Actor dedupes on.
- `url` uses Google's documented Maps URL format (`?api=1&query=...&query_place_id=...`) and opens the place in Google Maps.
- `rating` is 1.0 to 5.0 or null; `reviewsCount` is an integer or null.
- `category` is the main category label in English, because the search is requested with `hl=en`; the sample rows show "Pizza restaurant" and "Gluten-free restaurant" for Barcelona.
- `latitude` and `longitude` are unrounded as served.
- `searchQuery` and `scrapedAt` are added by the Actor, not read from Google.

Example analysis: group rows by `searchQuery`, average `rating` weighted by `reviewsCount`, and you have a per-district comparison from one run.

## Troubleshooting guide

- `KeyError: 'APIFY_API_TOKEN'`: export the variable in the same shell that runs the script.
- **Status** `FAILED`**, log says "Extracted 0 valid records"**: the Actor raised because nothing was pushed. Read the lines above it in the log for the cause (block or parse shape). A Google Maps layout change shows only this zero-row error. A `minRating` that drops every place also ends this way.
- **Status** `SUCCEEDED` **with zero rows**: `searchQueries` was empty.
- **Fewer rows than** `maxItems`: `minRating` dropped places, or the queries returned fewer results than the cap.
- **Slow run**: the Actor retries a blocked fetch every 3 seconds on a new proxy session until 90 seconds of run time remain, so a run that is being blocked takes longer before it fails.

Logs are on the run detail page in Console. For platform questions, check Apify's documentation at [docs.apify.com](http://docs.apify.com), visit the Apify Community Discord, or open an issue on the Actor's Apify Store page.

## Best practices

- Keep `maxItems` at the number you will actually use; it is the cost.
- Use `minRating` when you only want rated places; it also removes nulls.
- Write queries with the city in them and let `searchQuery` label the rows.
- Treat `rating`, `reviewsCount`, `category` and `address` as nullable in your own code, as the loop above does.
- Do not add your own retry loop around `call()`; the Actor retries inside the run on its own clock.

## Pricing

For current pricing and details, see the [Store page](https://apify.com/reventadata/google-maps-scraper).

## Conclusion and next steps

You now have a script that runs a Google Maps scraper on the Apify platform, checks the run status, reads the dataset and knows which fields can be null. Try Google Maps Places & Ratings Scraper yourself and let me know what you build. A follow-up in this series could cover scheduling the same run and pushing the dataset into a spreadsheet.

This is an unofficial tool, not affiliated with or endorsed by Google.

## Resources

- Actor page: [Google Maps Places & Ratings Scraper](https://apify.com/reventadata/google-maps-scraper)
- Apify documentation: [docs.apify.com](https://docs.apify.com/)
- Python client reference: [ActorClient.call](https://docs.apify.com/api/client/python/reference/class/ActorClient#call) and [DatasetClient](https://docs.apify.com/api/client/python/reference/class/DatasetClient)
- Dataset storage and export formats: [Dataset](https://docs.apify.com/platform/storage/dataset)
- Schedules: [Schedules](https://docs.apify.com/actors/running/schedules)
- Webhooks: [Webhook integration](https://docs.apify.com/platform/integrations/webhooks)
- Apify Community Discord: [discord.com/invite/apify](https://discord.com/invite/apify)

About the author: reventadata builds and publishes scrapers on the Apify Store.
