Google Maps Business Scraper — Country-Scale
Every business in an area, not the first two hundred.
Google caps any single Maps query at about 200 results, so "all the restaurants in Miami" is not one query — it is a tiling problem. This Actor splits the map into tiles and splits again wherever a tile comes back full, until every tile returns under the cap. Then it tells you honestly whether it finished.
oswaldocarabano/google-maps-scraper
{
"country": "US",
"categories": ["restaurant"],
"bbox": { "lat_min": 25.70, "lng_min": -80.32, "lat_max": 25.85, "lng_max": -80.13 },
"maxPlaces": 1000
}- Version
- v0.1.18
- Memory
- 512 MB
- Browser
- none
- Proxy
- Residential, required
Short answer
The Google Maps Business Scraper Actor extracts every business Google Maps publishes inside a bounding box by recursive geographic tiling, rather than the roughly 200 results a single query returns. It needs no API key, no login, no cookies and no browser. Rows are deduplicated by `feature_id`, Google's own stable identifier, and carry `place_id` — the same identifier the official Places API uses. Fill rates were measured on 12,693 businesses across 10 regions and 12 verticals plus 4,032 businesses across 8 cities, and they are a property of the market: `phone_e164` runs from 96.7% in Toronto to 41.4% in remote regions. Pricing is $0.001 per delivered business on the Free plan, falling to $0.0005 on Gold and above, with phone, hours, website, rating, categories and coordinates all included at that price.
Key points
- Google caps a single Maps query at about 200 results. This Actor tiles the area instead of paginating it, splitting any tile that comes back full until every tile is under the cap.
- A census pass classifies one request per 1-degree cell before anything is extracted: Venezuela is 180 cells and was classified in 172 requests in about 20 seconds, showing that only 11.7% of the country needed deeper work.
- Coverage is a property of the market, not of the tool. `phone_e164` was 96.7% in Toronto, 77.7% in Miami, 68.2% in Mexico City and 41.4% in remote regions — measured on 12,693 businesses across 10 regions and 12 verticals, plus 4,032 across 8 cities.
- One price per delivered business with no add-ons: phone, opening hours, website, rating, categories and coordinates are included, and the tiling overhead is absorbed rather than billed.
- `place_id` is the same identifier the official Places API uses, so the dataset joins to anything you already have from Google without extra work. Deduplication is by `feature_id`, Google's own stable id, across runs as well as within one.
- The Actor refuses to run on 8 of its 24 catalogued countries — Spain, Portugal, France, Germany, Italy, the United Kingdom, Brazil and Cuba — a limit enforced in code rather than a note in the README.
What it does
Google caps any single Maps query at about 200 results, so "all the restaurants in Miami" is not one query — it is a tiling problem. This Actor splits the map into tiles and splits again wherever a tile comes back full, until every tile returns under the cap. Then it tells you honestly whether it finished.
{
"name": "Panadería La Mérida",
"address": "Av. 4 Bolívar, Mérida 5101, Venezuela",
"latitude": 8.5897,
"longitude": -71.1561,
"place_id": "ChIJXxxxxxxxxxxxxxxxxxxxxxx",
"feature_id": "0x8e7xxxxxxxxxxx:0x9axxxxxxxxxxxxx",
"phone_e164": "+582742525252",
"website": null,
"rating": 4.4,
"reviews_count": null,
"categories": ["Panadería", "Cafetería"],
"opening_hours": { "mon": "06:00-19:00", "tue": "06:00-19:00" },
"language": "es",
"from_cache": false,
"data_age_hours": 0
}Why this one
A census you can run before you spend anything
Pass 1 is one request per 1-degree cell and it classifies each as dense, border, foreign, exhausted or empty. Run it alone with `censusOnly` and you learn what an area will cost before committing to it: Venezuela classified in 172 requests and about 20 seconds, and showed that 88.3% of the country needed no deeper work at all. Nothing else in this category lets you price a run before starting it.
Cells that belong to a neighbour get dropped, not split
A 1-degree cell over a border is mostly somebody else's country, and splitting it four ways spends four requests to rediscover that. Classifying it as foreign and dropping it saved about 682 wasted requests on Venezuela alone. That saving is what makes the tiling overhead affordable enough to absorb into the per-place price instead of billing it.
A run summary that admits a gap
`RUN_SUMMARY` is part of the output rather than telemetry. `census_complete` false means pass 1 did not finish and what you have is the corner of the grid the sweep started from. `tiles_unresolved` above zero means the census has holes. `max_depth_reached` equal to `max_depth_limit` means it is truncated. A census with gaps is legitimate; a census with gaps that does not say so is not.
The review count it refuses to promise
`reviews_count` came back on 8.5% of rows, because Google serves it only in some responses. It is delivered when it is present and it is never promised. Review text is out of scope entirely — it is writing by identifiable people — and there are no email addresses, because Google Maps does not publish them: measured at zero email strings across 498 records in three verticals, and none on the place page either. Any tool promising emails is getting them somewhere else.
Eight countries it will not run on
Aggregating name, phone and coordinates for hundreds of thousands of small businesses produces a personal-data file. Spain, Portugal, France, Germany, Italy and the United Kingdom are refused under GDPR and UK GDPR, Brazil under the LGPD, and Cuba because the regime was not reviewed. It is enforced in code, not documented and then left to the operator.
Use cases
- Build a complete business census of a city or a country for a vertical, rather than the first page of results.
- Price an area before extracting it, using the census pass alone.
- Generate lead lists with phone and website, knowing the fill rate for that market before you buy.
- Join Google Maps data to an existing Places API dataset on `place_id` without a matching step.
- Measure how a category is distributed geographically, using coordinates present on 100% of rows.
Input
Every field has a default, and the defaults are deliberately small so a first run is cheap enough to inspect before you commit to a sweep. This table mirrors the Actor's own input schema field for field.
| Field | Default | What it does |
|---|---|---|
countrystring | "US" | Countrypersonal dataSupplies the bounding box, the country bias and the language. Search in English in the US and Canada and in Spanish elsewhere. Countries under GDPR, UK GDPR and the LGPD are refused by design. |
categoriesstring[] | ["restaurant"] | Search termsOne term per category, in the country's language. Terms barely overlap — 5.5% measured across 30 of them — so each one you add is close to a full extra pass in both time and cost. |
bboxobject | {} | Custom area (bounding box)Overrides the country's own box, so you can scrape one city or run one batch of a large country. Four decimal degrees, south-west corner first. |
maxPlacesinteger | 0 | Maximum placesStop after this many unique businesses; 0 means no limit. The safest way to cap what a run costs. |
requestBudgetinteger | 5000 | Request budgetMaximum requests for this run. Tiles already visited are skipped for free, so re-running continues where the last run stopped rather than starting over. |
censusOnlyboolean | false | Census only — classify, do not extractRuns only the cheap first pass: one request per 1-degree cell, telling you which areas are dense, which belong to a neighbour and which are empty. This is how you learn what a country costs before spending it. |
useCacheboolean | true | Use the shared cacheSkips tiles an earlier run already exhausted. Their businesses are still delivered, flagged with `from_cache` and `data_age_hours` so a cached row never passes as a fresh one. |
maxDepthinteger | 8 | Maximum tile depthA safety guard, not the stopping rule — the stopping rule is saturation. A run that reaches this depth has a truncated census and both the log and the run summary say so. A dense cell over a capital was still splitting at depth 8. |
saturationThresholdinteger | 190 | Saturation thresholdA tile counts as full at this many results and gets split into four. 190 leaves margin under Google's hard ceiling of about 200, because the same query does not return the same count twice. |
gridStepinteger | 1 | Census cell sizeSize in degrees of the first-pass cells. 1 degree is the measured default; larger cells classify faster but hide more inside each one. |
maxConcurrencyinteger | 10 | Maximum concurrency10 is the measured default. 617 requests at 12 ran without a single block. |
Output and fill rates
A field being in the schema is not the same as it having a value. The percentages below were counted on real runs; the sample sizes are in Measurements. Anything not listed here is not promised.
| Field | Filled | Meaning |
|---|---|---|
namestring | 100% | Kept exactly as Google publishes it, in its own alphabet and with its own accents. |
addressstring | 100% | The formatted address. The postal code is embedded in it and is deliberately not parsed out, because its format varies by region. |
latitudenumber | 100% | With `longitude`. Present on every row in every market measured. |
place_idstring | 100% | The same identifier the official Places API uses, so this dataset joins to anything you already have from Google. |
feature_idstring | 100% | Google's own stable identifier and the deduplication key, across runs as well as within one. |
phone_e164string | not measured | 96.7% Toronto, 77.7% Miami, 68.2% Mexico City, 41.4% remote regions. By vertical: dentists 99.5%, law firms 100%, gyms 28.0%. |
opening_hoursobject | not measured | 96.1% Toronto, 74.3% Miami, 81.9% Mexico City, 48.9% remote regions. In the country's language. |
ratingnumber | not measured | 97.5% Toronto, 77.0% Miami, 92.0% Mexico City, 55.5% remote regions. |
websitestring | not measured | 87.4% Toronto, 74.0% Miami, 40.0% Mexico City, 5.7% remote regions — the field that varies most by market. |
reviews_countinteger | 8.5% | Google serves it only in some responses. Delivered when present, never promised. |
categoriesstring[] | not measured | In the language the country selected, which is why two runs of the same area in different languages produce different rows. |
languagestring | 100% | The language the run used, on every row, so the previous line is auditable. |
from_cacheboolean | 100% | With `data_age_hours`. Cached rows are charged the same and always declare their age. |
Every key is always present. A field that exists but is empty comes back as explicit null, so a parser never has to guess.
Datasets
Different record types go to different datasets, so the main table never carries columns that are blank on most rows.
defaultOne row per business, deduplicated by `feature_id` across runs as well as within one.billedRUN_SUMMARY (key-value store)Census completeness, unresolved tiles, depth reached against the limit, stop reason and cache hits. Part of the output, not telemetry.never billed
Pricing
Pay per delivered result. Charges are applied as each row is produced rather than in a lump at the end, so an aborted run bills only for what it actually gave you.
| Event | Price | Notes |
|---|---|---|
apify-actor-startActor start | $0.00001 | Charged per gigabyte of run memory, minimum one event. Effectively free. |
place-scrapedScraped place | $0.001 | One business delivered, on the Free plan. $0.0008 on Bronze, $0.0006 on Silver, $0.0005 on Gold and above. Phone, opening hours, website, rating, categories and coordinates are included — there are no add-on charges for filters or place details, and failed tiles are never charged. |
Measurements
Each figure is shown with the method that produced it. A benchmark without a method is a marketing claim wearing a number's clothes.
Fill-rate sample
12,693 businesses and 4,032 businesses
Two independent measurements on 3 Sep 2026: 10 regions and 12 verticals in a Latin American market, and 8 cities across the US, Canada, Mexico, Colombia, Peru, Chile and Argentina.
Phone by market
96.7% Toronto, 41.4% remote regions
The same field, the same tool, a 55-point spread. Coverage is a property of the market rather than of the scraper, which is why it is published as a table instead of an average.
Phone by vertical
dentists 99.5%, law firms 100%, gyms 28.0%
Gyms publish a booking site instead of a number, which is a fact about gyms rather than a failure of extraction.
Census cost
180 cells, 172 requests, about 20 seconds
Venezuela, pass 1 only. It showed that 11.7% of the country needed deeper work, which is the number that decides what pass 2 costs.
Border cells dropped
about 682 requests saved
Cells that turned out to belong to a neighbouring country are dropped rather than split into four and retried, on Venezuela alone.
Resuming a finished batch
8 requests instead of 180
A second run of the same Venezuelan census. Tiles already exhausted are skipped, so re-running a finished batch spends almost nothing.
Search-term overlap
5.5% across 30 terms
Terms barely overlap, so each additional term is close to a full extra pass in both time and cost rather than a marginal addition.
Emails found
0 in 498 records across three verticals
Neither in the search response nor on the place page. Google Maps does not publish them, so any tool promising emails is sourcing them elsewhere.
Requests to Apify IP ranges
HTTP 302, measured 3 Sep 2026
Google redirects Apify's own IP ranges, so a direct run from the platform returns nothing. The residential proxy is a requirement, not an optimisation.
What it will not do
Stated plainly so you can judge fit before spending anything.
- `reviews_count` is present on 8.5% of rows. If you need it on every row, this is not the tool.
- No review text, ever. It is writing by identifiable people and it is out of scope.
- No email addresses. Google Maps does not publish them — zero email strings in 498 records across three verticals, and none on the place page either.
- No postal code field. It is embedded in `address` and its format varies by region, so parsing it out would be guesswork.
- Individual place pages are not fetched: phone, hours and website already arrive in the search response, and fetching each place would multiply the cost roughly 200-fold for nothing.
- It refuses to run on Spain, Portugal, France, Germany, Italy, the United Kingdom, Brazil and Cuba.
- A whole country does not fit in one run because of the run time limit. Split it into batches with a `requestBudget` and a `bbox` each; batches are independent and resumable.
- The language comes from the country and sets the language of `categories` and `opening_hours`, so two runs of the same area in different languages produce different rows.
Privacy
- Aggregating name, phone and coordinates for hundreds of thousands of small businesses produces a personal-data file, which is why eight countries are refused in code rather than merely flagged.
- Whoever runs the Actor is the data controller for the output.
- No review text and no reviewer identities: that is writing by identifiable people and it is out of scope.
- No email addresses are returned, because Google Maps does not publish them.
- Place names are kept exactly as Google publishes them, in their own alphabet and with their own accents. Mérida is not Merida.
- Removal requests: privacy@actorstack.dev
See also the data removal process.
Frequently asked questions
Why can't I just paginate past 200 results?
How do I know what an area will cost before running it?
Do I need a Google API key?
What fill rate should I expect for phone numbers?
Does it return email addresses or review text?
How do I know the run actually covered the area?
Why won't it run on Spain or the UK?
Can I scrape a whole country in one run?
Guides for this Actor
- Scrape Google MapsA walkthrough of the two-pass approach: classify the area first so you know what it costs, then extract only the tiles that need it.
- The 200-result capThe cap is per query, not per page, so the answer is geographic rather than paginated. What saturation means, why the threshold is 190, and how deep the splitting goes.
- The census passThe first pass extracts no businesses at all. It tells you which parts of an area are dense, which belong to a neighbouring country and which are empty — before you spend anything.
- Coverage is the marketPhone, hours, rating and website vary enormously by city and by vertical. Two independent samples, 16,725 businesses, and why an average would be the wrong thing to publish.
- The email questionTools advertising emails with Google Maps data are getting them somewhere else. Zero email strings across three verticals, in the search response and on the place page.
- Joining on place_idTwo identifiers ship on every row and they do different jobs: one deduplicates within and across runs, the other joins to anything you already have from Google.
- API versus scrapingGoogle's own API is the right tool for looking up a place. It is a different proposition when the question is every business in a region, and the difference is structural rather than about price alone.
- Reading the run summaryFour fields in the run summary decide whether your data covers the area you asked for. What each one means and which combinations should stop a pipeline.
- Field referenceEvery field the Actor returns, which are always present, which depend entirely on the market, and the four that are deliberately absent.
- The refused countriesAggregating name, phone and coordinates for hundreds of thousands of small businesses produces a personal-data file. Where that leads under the GDPR, UK GDPR and the LGPD.