By the end of this Google Maps Places & Ratings Scraper tutorial, we will build a working Python script that drives a reliable google maps scraper from your terminal and turns searches such as pizza barcelona into clean, structured records: business name, primary category, address, star rating, review count, and GPS coordinates, one row per place.
When analyzing local markets or collecting leads, Google Maps provides an enormous directory of places. However, manual collection is impractical at scale, while building a custom scraper requires handling dynamic rendering and proxy rotation.
In this tutorial, we will use Python and the dedicated Apify Actor Google Maps Places & Ratings Scraper (reventadata/google-maps-scraper) to extract structured place records efficiently; proxy handling and retries are built in. The measured benchmark run behind this guide (Apify run) took 5.3 seconds for five places.
⏱️ Time estimate: This takes about 10 minutes. The extraction run takes seconds; installing the client and running the script takes the rest.
Prerequisites
To follow this tutorial, you will need:
-
Python 3.11+ installed locally (
apify-client3.x requires it). - The Apify API client for Python (
pip install apify-client). - An Apify account and API token.
Sign up for an Apify account. Your API token lives under Settings > API & Integrations in Apify Console. We will load it from an environment variable rather than hardcoding it.
What you'll achieve
By completing this guide, you will extract Google Maps places into structured datasets where each record contains the following fields, per the dataset schema:
-
id: Google Maps place identifier string. -
title: Establishment name (e.g. "Restaurant Pizzeria Edén"). -
category: Primary business category (e.g. "Pizza restaurant"). -
address: Full street address. -
rating: Average user rating as a float (1.0 to 5.0), or null when unrated. -
reviewsCount: Total integer count of customer reviews, or null. -
latitude&longitude: Exact decimal geographic coordinates. -
url: Direct Google Maps listing URL. -
searchQuery: Search term used to discover the place. -
scrapedAt: ISO 8601 timestamp of extraction.
Note on Data Fidelity: This scraper extracts aggregate place metrics (overall star rating and total review count) and coordinates. It does not scrape individual review text bodies or reviewer profiles.
Example use case: Say you need every pizza place in a city with its rating and review count to compare neighbourhoods. One query per neighbourhood, one run, one dataset.
Step 1: Access Google Maps Places & Ratings Scraper
Go to the Google Maps Places & Ratings Scraper page on Apify Store to try it for free.
The Store page also has a Try for free button for running it in Apify Console without code; this tutorial uses Python for reproducible automation.
Install the Apify client library via pip:
pip install apify-client
Set your API token in your environment:
export APIFY_API_TOKEN="your_apify_token_here"
Step 2: Configure search queries
The primary input is searchQueries, an array of search strings formatted as you would type them into Google Maps (e.g. "pizza barcelona" or "hotels tokyo").
The Actor URL-encodes each query into a desktop Google Maps search with hl=en, so categories and details return in English regardless of location.
Let's initialize the Apify client and define our search parameters:
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,
}
Step 3: Configure optional settings and filters
The Actor input schema provides optional settings to control run size and filter results:
| Field | Type | Description |
|---|---|---|
searchQueries |
array | Searches to run, e.g. pizza barcelona, hotels tokyo. |
maxItems |
integer | Maximum number of places to save in the whole run (1 to 5000, default 20). |
minRating |
number | Optional minimum star rating threshold (e.g. 4.0). Drops unrated and lower-rated places. |
proxyConfiguration |
object | Apify proxy settings; Unblocker is the default. |
Best practices for optional settings:
-
maxItemsis a run total: With three queries andmaxItems: 10, the run stops once ten total places are saved across all queries. - Built-in deduplication: If a place matches multiple queries, the Actor records it only once by tracking seen place IDs.
-
minRatingsaves budget: SettingminRating: 4.0drops lower-rated and unrated listings during scraping, so you pay only for qualifying places. -
Query granularity: Google Maps returns the first results list for each query. To cover an entire city, supply multiple specific neighbourhood queries rather than just raising
maxItems.
Step 4: Run the Actor
In our Python script, we trigger the run and wait for it using client.actor(...).call(run_input=run_input):
# 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")
Behind the scenes:
- The Actor boots in an isolated 512 MB container using Apify Unblocker proxies.
- It loads the Maps search page, parses preload data, and streams records to the dataset.
- If Google challenges the request, the Actor detects it by inspecting body content (not HTTP status codes, which return 200) and retries on a fresh proxy session after 3 seconds until fewer than 90 seconds remain on the run clock.
⏱️ How long it takes: The benchmark run scraped five places in 5.3 seconds.
⚠️ Tip: You can leave the page or close your terminal and come back; the run continues on Apify's infrastructure.
The
run.status != "SUCCEEDED"check is essential. An Actor that pushes zero rows raises an exception so the platform marks itFAILED; without the check your script would silently read an empty dataset.
Step 5: View and download results
Once the run completes, you can inspect the data in Python or export it from Apify Console.
Iterate through the dataset items in Python to print key fields:
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:
{
"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"
}
In Apify Console under Storage > Datasets:
- Preview: Inspect results in tabular view, JSON, or raw formatting.
- Export formats: Download in JSON, CSV, Excel, XML, HTML table, RSS, or JSONL.
- Filter columns: Use Select fields or Omit fields in the export modal to trim columns.
-
Export via API: From Python,
client.dataset(run.default_dataset_id).get_items_as_bytes(item_format="csv")returns CSV bytes directly.
Unnamed datasets are deleted after your plan's retention period, so name the dataset or download what you want to keep.
Understanding your results
Key dataset fields and how to interpret optional or null values:
-
ratingandreviewsCountare null for places Google shows without a rating. Our script prints "not rated yet" for those listings. -
categoryandaddresscan be null for non-standard listings; the dataset schema marks onlyid,title,url,searchQuery, andscrapedAtas required. -
urluses Google's documented Maps search format (?api=1&query=...&query_place_id=...), opening the listing directly on Google Maps. -
latitudeandlongitudeare served as unrounded decimal floats directly from Google (41.397219899999996in the sample). -
searchQuerytracks which query found the place, which is useful when passing multiple queries in a single run.
Common question: Why is each query capped? Each query reads the initial results list Google Maps returns, not an infinite scroll. Passing multiple specific neighbourhood queries is how you thoroughly cover a city.
Pro tips and advanced usage
Here are five practical tips for getting better results and managing costs:
-
Filter at the source:
minRating: 4.0drops lower-rated and unrated places during scraping, so you pay only for rows you keep. -
Cap spend per run: Pass
max_total_charge_usdtocall()to set a hard spending cap on a run. -
Audit your bill:
run.charged_event_countslists charged events; our test run shows oneapify-actor-startand fiveapify-default-dataset-itemevents. -
Handle large datasets:
list_items()returns every item in one call; for very large datasetsclient.dataset(...).iterate_items()pages through them instead. -
Built-in retries: Do not wrap
call()in outer retry loops; the Actor automatically detects challenges, rotates proxy sessions, and stops on its run clock.
Pricing
For current pricing and details, see the Store page.
Troubleshooting common issues
-
KeyError: 'APIFY_API_TOKEN': The environment variable is not set in your active terminal. Re-runexport APIFY_API_TOKEN="your_token"in that shell. -
Run did not succeed (status: FAILED): The Actor extracted zero rows and raised an exception on purpose. Open the run log in Apify Console to see whether it logged "Challenge / captcha detected" or "Place data not in the expected shape". A Google Maps layout change logs neither line, only the zero-row error. AminRatingthat filters out every place fails the run the same way. -
Zero rows but status
SUCCEEDED:searchQuerieswas empty. The Actor logs "No searchQueries provided in input." and exits without pushing rows. -
Fewer rows than
maxItems: AminRatingfilter dropped listings, or Google returned fewer places for that query.
Actor errors appear in Console run logs; script errors appear as Python exceptions in your terminal. For support, use the Issues tab on the Apify Store page.
Next steps
Ways to automate and extend your scraping workflow:
- Schedule runs: Use Apify Schedules to run the Actor on a cron expression from Console, the API, or the Python client.
- Webhooks: Configure webhooks under the Integrations tab to POST data to your endpoint when runs finish.
-
Go wider: Split large metro areas into neighbourhood queries and set
minRatingto match your target criteria.
For full parameter details and live runs, visit the Google Maps Places & Ratings Scraper on Apify Store.
Disclaimer: This is an unofficial tool and is not affiliated with, authorised by, or endorsed by Google LLC.
What are you planning to scrape from Google Maps? Drop a comment! 💬
Top comments (0)