agenttoolworks

Guide, measured 2026-10-05

How to scrape Google Maps, and where a Google Maps scraper goes wrong

Google Maps shows business listings to anyone, signed in or not, and its own page fills the result list from one JSON request. Reading it is the easy part. The hard parts are the ones users of every Google Maps scraper complain about: fewer places than the city holds, places outside the city, duplicates, and runs that report success on a short result. Here is how each one happens, with our measurements.

The request behind the result list

Open google.com/maps/search/dentist/ in a browser and the page asks /search?tbm=map for its list. The pb parameter is a compact encoding of everything the list depends on:

the search request
GET https://www.google.com/search?tbm=map&hl=en&pb=...
  !1s<query>          what you typed: "dentist"
  !1d<meters>         the viewport's height
  !2d<longitude>      the viewport's centre
  !3d<latitude>
  !7i<page size>      20 on the page, up to 100 works
  !8i<offset>         0, 100, 200...

The answer is JSON behind a short anti-hijacking prefix, one array per place: name, categories, address and its parts, coordinates, phone, website, rating, review count, the week's opening hours, time zone, Place ID, CID and Knowledge Graph ID. Google's robots.txt disallows /search in general but explicitly allows /search?*tbm=map and /maps/search/, which are the only paths a scraper needs. No account, no API key.

One detail cost us review counts before we saw it: sent with a non-browser User-Agent, Google often answers with a lighter record (the rating without its review count, only today's hours). On 100 Lyon dentists, review counts came back on 3% of rows instead of 85%. A desktop Chrome User-Agent gets the record the page renders, and a light answer can simply be asked again.

Who gets an answer

What we askedWhat came back
Search JSON, our laptop, plain HTTP200, to a browser User-Agent and to our own
Search JSON, Apify cloud, no proxy200 at first, 100 places; later the same day, Google's unusual-traffic page on every request
Search JSON, Apify datacenter proxy200, but some addresses capped at about 20 places (18, 22, 22, 100, 19, 100, 18, 100 on one view)
Search JSON, Apify residential, US200, answered in full (89, 87, 100, 100, 53, 100, 87, 100 on the same view)
Maps page, Apify residential, France302 to Google's EU cookie consent page

Two lessons. First, a capped answer is not an error: it is a normal list of about 20 places, indistinguishable from a quiet neighbourhood. A scraper that trusts it reports success on a fraction of the city. Our own datacenter runs of Lyon restaurants returned 196 to 635 places while saying "ok"; on US residential exits the same search returned 1,000. Second, EU residential addresses meet Google's cookie consent page, which a scraper should not click through on anyone's behalf.

What we do about capping: ask a short page again from other addresses and keep the longest answer, stop using addresses Google caps, and when short answers remain after that, mark the search partial instead of ok. The current build on datacenter returned 658 Lyon restaurants and said partial, with the reason (6 capped sessions, 9 short answers replaced). And when Google answers with its unusual-traffic page, the run stops and says so. Solving a challenge is where scraping turns into circumventing an access control, and we do not.

Going past 120 places per view

One view stops at about 120 to 170 places: Paris restaurants in a 21 km view gave 125, Paris dentists in a 3 km view 171. Zooming out does not help, it only spreads the same number over a larger area. The way past it is tiling: cut the area into tiles about 5 km wide, read each tile to the end of its list, and split a tile in four when it was full. Berlin dentists at city scale: 56 places. As 5 km tiles: 1,001.

Two traps in the split rule. Google pads a view that ran out of places with places from around it, so "the answer had 100 places" does not mean the tile is full; count how many fell inside the tile. And Google does not narrow a view below about 1.2 km: ask for a smaller one and you get the 1.2 km view again. Below that size, views shifted onto the quarters of a tile still return different places, so dense centres can be read further.

Only the places inside your city

The padding that fools the split rule also fills your results with places outside the area: on one Lyon walk, 1,200 of 2,209 results lay outside their tile, and 9 of 200 New York plumbers were more than 20 km away. A bounding box does not fix it, because cities are not rectangles. We resolve each location to its real boundary on OpenStreetMap (Nominatim, one request per location, within its usage policy) and drop every place outside the polygon before it is delivered or billed. This is the most reported problem with the leading Google Maps scraper on Apify: 15 geography issues in six months.

The same discipline applies to duplicates: tiles overlap, terms overlap, and the same place comes back many times. Keep one set of Place IDs for the whole run. Our cloud runs: 0 duplicates in 3,000 rows.

Emails come from the business's website, not from Maps

Google Maps lists a website, never an email. To get one, read the homepage, and if it shows no address, the contact, imprint or about pages that site links. Do it in parallel with the map walk, because websites are slow, and check every redirect hop: these URLs come from business listings, and a listing can point anywhere, including private addresses.

Some sites refuse automated readers. With our own User-Agent, 87 of 728 Chicago restaurant websites answered HTTP 403. Since 2026-10-05we send a browser's User-Agent to business websites, and measured the effect twice:

Chicago restaurant websitesOwn User-AgentBrowser User-Agent
Same 689 sites, read twice from one laptop79.7% read, 88 HTTP 403, 28.2% with an email82.0% read, 73 HTTP 403, 28.3% with an email
Two cloud runs of 1,000 places79.1% read, 87 HTTP 403, 27.9% with an email80.3% read, 79 HTTP 403, 28.2% with an email

A few more sites read, barely more emails: the sites still refusing check more than the User-Agent, and we stop there. What does move the email rate is the trade: 78% of Berlin dentists' websites showed an email, against 27% for Chicago restaurants. Whatever the rate, each row should say whether its website was read and why not, so an empty email column is explained rather than billed silently.

Not every address on a business website belongs to the business. In our Chicago runs we found Sentry error-tracking keys in scripts (one on 6 places), a font designer's email from a licence in inline CSS, theme credits, and Linktree's own press address read from its contact-forms page. The Actor now drops those vendor addresses, reads a link-in-bio page alone, and lists the site's own domain first. A first version that also ignored scripts lost 25 real addresses that site builders keep in script data, so scripts are still read. With the cleanup, 1,000 Chicago restaurants gave an email on 27.2% of websites (80.5% of websites read).

An email on a sole trader's website is personal data. Whoever exports it is the controller of what they do with it, and for EU contacts that includes the GDPR Article 14 notice at the first contact.

What it costs in the cloud

Measured on Apify with our Actor: the run's total usage, compute and proxy included.

RunPlacesRun timeApify usage
Restaurants, Lyon, residential proxy1,000230 s$0.129
Dentists, Berlin, residential, with contacts1,000309 s$0.056
Restaurants, Chicago, residential, with contacts1,000338 s$0.052
Restaurants, Chicago, residential, with contacts, browser User-Agent for websites1,000346 s$0.056
Restaurants, Lyon, datacenter proxy (reported partial)65899 s$0.0067

Lyon is the expensive case: its centre is dense, so tiles split down to the smallest view and more residential traffic is spent per place. Datacenter is cheap and fast, and wrong: it is the 658-place run.

What we left out

Or skip the plumbing

Our Google Maps scraper does all of the above in one run: many terms and many locations, the real city boundary, tiles past the cap, one row per place, opening hours and review count in every row, emails and social profiles from each website, and a partial status instead of a silent short result. It costs $1.50per 1,000 places with contacts and filters included, where the leading Actor's comparable row (place, details and contacts) costs $8.00 on its free tier.

Questions

Is there a Google Maps search request that returns JSON?

Yes. The public Maps page fetches its result list from /search?tbm=map with a long pb parameter that encodes the query, the viewport, the page size and the offset. It answers a plain HTTP request, up to 100 places per request, and robots.txt allows that path for every user agent.

Why does a Google Maps scraper stop at about 120 results?

Google stops at about 120 to 170 places for one map view. To go further you cut the area into smaller views (tiles) and split the tiles that are still full. Berlin dentists at city scale returned 56 places; read as 5 km tiles, 1,001.

Why do I get places outside my city?

Google pads every answer with places from around the view: on one Lyon walk, 1,200 of 2,209 results fell outside their tile. Resolve the city to its real boundary and drop every place outside that shape before you store or bill it.

Datacenter or residential proxy for Google Maps?

Residential, in our measurements. Google answered some Apify datacenter addresses with about 20 places where others got 100 for the same request, and a capped answer looks exactly like a sparse area. On datacenter, a Lyon restaurant search returned 196 to 635 places; on US residential, 1,000.

Can you get emails from Google Maps?

Google Maps lists a website, not an email. The emails come from the business's own site: its homepage and its contact or imprint pages. On 1,000 places, 78% of Berlin dentists' websites showed an email, and 27% of Chicago restaurants' websites.

Public business listings only: no login, no account, no reviewer data. Area boundaries: Data © OpenStreetMap contributors. Not affiliated with Google.