Google Maps Scraper — Local Business Leads & GBP Gaps avatar

Google Maps Scraper — Local Business Leads & GBP Gaps

Pricing

from $3.00 / 1,000 results

Go to Apify Store
Google Maps Scraper — Local Business Leads & GBP Gaps

Google Maps Scraper — Local Business Leads & GBP Gaps

Scrape Google Maps businesses with name, address, phone, website, categories, rating, hours and coordinates, plus a missing-fields audit of each Business Profile. Filter to businesses with no website, no phone or an incomplete profile, so you get a qualified prospect list, not a place dump.

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

mochi

mochi

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Scrape Google Maps business listings and get, for every business, which parts of its Google Business Profile are missing. Filter the run down to only the businesses with no website, no phone number or a neglected profile, and the dataset you pay for is a prospect list rather than a place dump you still have to qualify by hand.

Built for local SEO agencies, web-design and GBP-management sellers, franchise development teams and anyone doing territory research.

Pricing: Pay per result, at the rate shown in the pricing panel of this page, plus a very small per-run start fee. You are charged only for the results delivered.


What you get per business

FieldNotes
name, categoryPrimary, categories, categoryCountLocalised to your language setting (friseur in Leipzig with de returns Friseursalon)
address, addressLines, neighborhood, cityRegion, countryCodeNull for the few businesses that publish no address, see limitations
latitude, longitude, timezoneIANA timezone, e.g. America/Chicago
phone, phoneE164phoneE164 is E.164: a +, the country code and the digits (+493412382180). It is built from the international form Google shows, so it is right outside the US too: valid on every row with a phone in our live runs for Germany (21 of 21), Singapore (19 of 19), the UK (12 of 12) and the US (20 of 20)
website, websiteDomainwebsite is the business's own address; when Google sends its redirect wrapper instead, the real address is recovered (see limitations). websiteDomain is the registrable domain
otherLinksBooking pages, WhatsApp, Facebook: [{domain, url}]
ratingStar rating, 1 to 5
reviewCountNumber of reviews behind the rating, as shown on the listing. An integer, or null for unrated businesses, never 0
openingHoursThe whole week as Google lists it: [{day, hours}], seven entries in Google's order, starting with the business's current local day. hours is Google's own text: 9 AM–5 PM, 9 AM–1:30 PM, 2:30–6 PM (split shifts joined by , ), Closed, Open 24 hours. null when Google lists no hours
hoursToday{day, date, ranges} for the first day Google lists, which is the business's current local day
openStatusTexte.g. "Open · Closes 3 PM", in your language
photoUrlThe listing's profile picture: always a 44×44 pixel thumbnail on Google's image CDN, so a logo rather than a photo. About 28% of the links returned 404 in a 200-link check, see limitations
listingLanguageLanguage of the listing text
missingFields, missingFieldCountThe audit: which of website / phone / hoursToday / logo / rating are absent
placeId, featureId, googleMapsUrlStable identifiers for joining across runs
searchTerm, searchRank, searchCountry, source, scrapedAtEvery row says where it came from

How full those columns were in our test runs (396 distinct businesses from six unfiltered runs, 10 search terms, US and Germany, weighted towards trades that have profile gaps):

FieldNon-empty
name100.0%
address98.7%
phone92.4%
rating97.2%
reviewCount97.2%
hoursToday89.4%
openingHours89.4%
photoUrl87.6%
website74.5%
name + address + phone + rating, all four89.1%

That sample is pooled and mostly US trades; other countries and trades look different. A Berlin café search had a phone number on 21 of 40 listings (53%), because Google lists none for the rest.


Filters (this is the point of the actor)

InputEffectChecked in a live run
onlyMissingWebsiteKeep only businesses with no website on the profile60 requested, 60 returned, 0 with a website
onlyMissingPhoneKeep only businesses with no phone number10 requested, 10 returned, 0 with a phone
minMissingFieldsKeep only businesses missing at least N of the 5 audited elements2: 30 of 30 rows had 2 or more gaps
maxRating / minRatingRating ceiling / floor in whole stars only (1 to 5, so no 4.5), 0 disablesminRating: 5: 20 of 20 rated exactly 5

Filters are applied before the result cap, so maxResults: 60 with onlyMissingWebsite gives you 60 website-less businesses, not 60 businesses of which a few happen to lack a website. The run scans as many result pages as it needs: 6 pages for those 60 rows.

One asymmetry worth knowing: maxRating keeps businesses that have no rating at all — an unrated profile is the strongest neglect signal in this data, so a ceiling should not exclude it. minRating does drop them, because a floor is a quality bar. In a live maxRating: 4 run, 13 of the 22 rows returned were unrated. That run returned 22 rows, not the 40 requested, because only 22 businesses passed the filter before the search term ran out (terminationReason: finished).


How to scale a run

One search term ends after a couple of hundred businesses. Three live runs of hair salon in Tulsa, OK returned 196, 215 and 221 unique businesses (242, 260 and 241 results including repeats, 13 to 14 pages, 28 to 43 seconds) and then ran dry. Scale by adding terms and locations, not by raising maxResults alone.

Two ways to say where:

// 1. the place is in the term
{ "searchTerms": ["auto repair in El Paso, TX", "auto repair in Lubbock, TX"], "maxResults": 200 }
// 2. a generic term swept over a coordinate grid
{ "searchTerms": ["coffee"],
"locations": [ {"latitude": 36.1539, "longitude": -95.9928, "radiusKm": 5},
{"latitude": 25.7617, "longitude": -80.1918, "radiusKm": 5} ],
"maxResults": 400 }

Every term × location pair is paginated independently and the result cap is shared evenly across them, row by row: each pair gets the cap divided by the number of pairs, and when it does not divide evenly the first pairs get one extra row. Seven terms at maxResults: 140 returned exactly 20 rows each, and five terms at maxResults: 8 returned 2, 2, 2, 1 and 1 (live runs). A term that runs dry early hands its share to the others rather than wasting it: with one term that found nothing and one that found plenty, a cap of 30 returned 30 rows.

In a grid run searchRank restarts at 1 in every cell and rows carry no cell index; tell the cells apart by latitude / longitude or cityRegion.

Google does not strictly bound results to the viewport: in three of four live runs of the coffee example above, 2 of the 40 rows were in Brooklyn, NY. An invalid coordinate is skipped and counted in the run summary, and the run continues with the valid ones; if every coordinate is invalid the run fails with a message.

Speed in our runs of the final version: 20 rows in 2 to 4 seconds, 140 rows from seven terms in about 7 seconds, 589 rows from 30 terms in 34 seconds. Pages are fetched one after another and rows are written to the dataset in batches. A very large run stops itself before Apify's run timeout and says so, see the run summary below.


The finding that should shape how you use this

Incomplete profiles are concentrated deeper in the results, not on page one. Measured over all 196 unique businesses returned by one run of hair salon in Tulsa, OK (one trade in one city, 2026-10-17), by position in Google's result list:

Result positionBusinessesNo websiteAvg. missing fields
1–40406 (15%)0.28
41–1005614 (25%)0.57
101–1806520 (31%)0.77
181–2423517 (49%)1.77

Two earlier runs of the same term (215 and 221 unique) showed the same shape: 0.30 average missing fields in the first 40 results against 1.60 and 1.42 in the last band. All 10 unrated businesses in the run above sat in the last band. For lead generation set maxResults to 100+. A 20-row run shows you the incumbents.

Gap density also varies enormously by trade and city, and the counts move from run to run (

auto repair in El Paso, TX
had between 7 and 11 in four runs). Businesses with no website among the first 20 results, in one run on the same day:

Search termNo website in top 20
massage in Fresno, CA11
auto repair in El Paso, TX7
food truck in Memphis, TN7
barber shop in Lubbock, TX4
landscaping in Toledo, OH3
nail salon in Bakersfield, CA2
hair salon in Tulsa, OK1
dentist in Austin, TX0

Limitations, stated plainly

  • reviewCount is null for unrated businesses. Google sends no review count for a business with no rating, so it is null for those (11 of 396 sampled businesses, 2.8%) and never 0. Where present it is the count Google shows next to the rating: a minimum of 1 and a median of 89 in the sample, and 70 of the 385 rated businesses had fewer than 25 reviews. The actor does not fetch individual reviews.
  • The week starts on the business's current local day. openingHours lists seven days in Google's order, beginning with the day it is now at the business, in the business's own time zone: a Singapore business is already on Sunday while it is still Saturday in UTC (live run: 20 of 20 rows). hoursToday is the first entry, with the date it describes, so an export is easy to place in time.
  • Very rarely the list starts on the previous day. Of 600 distinct businesses with hours in our test runs, one (a pharmacy that is open nearly round the clock) was listed starting on yesterday, so its hoursToday is that day's hours, labelled with that day's name and date.
  • Opening-hours text is Google's own, unmodified. Times have a narrow no-break space (U+202F) before AM/PM and an en dash (U+2013) between times, so a search for 8 AM with an ordinary space will not match; normalise the text if you parse it. It follows your language setting: Geschlossen and 24 Stunden geöffnet for de.
  • "No website" means no website field on the profile. Some of those businesses still have a booking page or a Facebook link; those appear in otherLinks so you can decide yourself. In a three live runs of the same seven searches (140 listings each), 25% to 30% had no website, and 3 to 5 of those listings per run still had another link. Google's own image-CDN and redirect URLs are stripped from otherLinks, so an empty list means nothing else was published.
  • Google sometimes wraps the website in its own redirect. In some runs Google sends the website as /url?q=<site>&opi=…&usg=… instead of the address: all 13 websites of one default run were wrapped and the next runs had none. The actor turns the wrapper back into the real address (checked on 33 real wrappers) and RUN_SUMMARY.websitesUnwrapped counts how often that happened. A website value that cannot be turned into an address is left empty, listed as a website gap and counted in websitesUnusable; it is never the raw /url?… text.
  • websiteDomain is the registrable domain Google reports, not always the full host: for https://goldensalontulsa.glossgenius.com/ it is glossgenius.com. It equalled the host on 289 of 295 websites; the other 6 were subdomains on site-builder platforms.
  • Some businesses publish no address. 5 of 396 sampled businesses (1.3%), three landscapers and two hair salons, have no address in their listing, so address, cityRegion and countryCode are null for them. They still carry a phone and rating where Google shows one.
  • Single-category is not counted as a gap. About 60% of the listings in those runs (58% to 60%) had exactly one category, so counting it would mark most of a result set "incomplete" and make the score useless.
  • photoUrl is a logo thumbnail, and many links are dead. It is always the listing's 44×44 profile picture, so it counts as the logo gap (missingFields says logo, not photo), and a business can have a gallery of real photos that this actor does not return. In a seeded sample of 200 links, 56 (28%) returned HTTP 404, ranging from 0% to 36% by search term. Do not rely on the link being alive.
  • A very large run may stop early, and the summary says so. The actor stops itself shortly before Apify's run timeout (a minute before it for the default one-hour timeout), keeps every row it has and ends normally with terminationReason: time_budget and complete: false. In a live test with a 45-second run timeout, 30 search terms stopped by themselves at 34 seconds with 589 rows.
  • Not every business is reachable by search. This actor finds what Google's own search returns for your term; it is not an exhaustive registry of a city.
  • Proxy. Requests go through Apify Proxy and each retry takes a fresh proxy session. The default datacenter pool fetched all 279 result pages in our 59 completed test runs without a failure; switch to RESIDENTIAL if you see failed pages at high volume.

Scope and privacy

  • Public Google Maps search results only: no login, account or cookies are used.
  • Business-level data only. Individual reviews and reviewer profiles are never requested (the only review data is the count Google shows on the listing), so no reviewer name can enter the dataset, and no output field is reviewer-, author- or owner-shaped.
  • In our test runs no email address appeared anywhere in the output (0 across 396 businesses).
  • This actor is not affiliated with or endorsed by Google.

Output sample

A real row from a live run (onlyMissingWebsite: true), trimmed to the interesting fields:

{
"name": "LoneStar Automotive",
"categoryPrimary": "Auto repair shop",
"categories": ["Auto repair shop"],
"address": "900 Hawkins A #1, El Paso, TX 79915",
"neighborhood": "Mission Valley",
"cityRegion": "El Paso, TX",
"countryCode": "US",
"latitude": 31.7642618,
"longitude": -106.3802455,
"timezone": "America/Denver",
"website": null,
"websiteDomain": null,
"phone": "(915) 234-7989",
"phoneE164": "+19152347989",
"otherLinks": [],
"rating": 4.9,
"reviewCount": 75,
"hoursToday": { "day": "Saturday", "date": "2026-10-17", "ranges": ["8 AM–3 PM"] },
"openingHours": [
{ "day": "Saturday", "hours": "8 AM–3 PM" },
{ "day": "Sunday", "hours": "Closed" },
{ "day": "Monday", "hours": "Closed" },
{ "day": "Tuesday", "hours": "8 AM–5 PM" },
{ "day": "Wednesday", "hours": "8 AM–5 PM" },
{ "day": "Thursday", "hours": "8 AM–5 PM" },
{ "day": "Friday", "hours": "8 AM–5 PM" }
],
"openStatusText": "Open · Closes 3 PM",
"missingFields": ["website"],
"missingFieldCount": 1,
"placeId": "ChIJc6mrpidb54YROEs9oJbSS4k",
"googleMapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJc6mrpidb54YROEs9oJbSS4k",
"searchTerm": "auto repair in El Paso, TX",
"searchRank": 11
}

That row is the product in one screen: an auto shop with a 4.9 rating from 75 reviews, a phone number and a full week of opening hours, and no website.

Run summary

Every run that starts scraping writes a RUN_SUMMARY record to the key-value store, including a run that stops early. Its main fields:

FieldMeaning
completetrue only when terminationReason is finished or max_results
terminationReasonfinished (every search ran out of results), max_results (your cap was reached), charge_limit (your maximum charge was reached), time_budget (stopped before the run timeout), blocked (no page could be fetched) or request_failed (some requests failed after retries, so the result is partial)
rowsPushedRows written to the dataset

It also counts pagesFetched, pagesFailed, filteredOut, duplicatesSkipped, shapeUnrecognised, websitesUnwrapped, websitesUnusable and errors. A run that stops early or loses requests says so in complete and terminationReason; one whose searches simply ran out of results is finished and complete.

Input that cannot be used fails the run at once, before any request and with no rows, and the message says what to fix: no non-blank search term, a country or language that is not a 2-letter code, or locations that are all invalid. An absent field means its default, so {} runs the demo search.