Google Maps Lead Scraper & SMB Audit
Pricing
from $2.00 / 1,000 results
Google Maps Lead Scraper & SMB Audit
Fast, pure-HTTP Google Maps scraper to extract leads, phone numbers, and websites for local business prospecting. Automatically audits online presence and calculates SMB outreach priority scores without running a heavy browser.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
Muhammad Sajid Izzulhaq
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
3 days ago
Last modified
Categories
Share
Google Maps Website-Lead Finder
An Apify actor that finds local businesses without a website on Google Maps and ranks them by priority, so you know exactly whom to contact first.
Best for: freelance web developers, digital agencies, and anyone selling website or landing page services to small local businesses.
The problem it solves
Looking for prospects manually on Google Maps is exhausting. Search for "coffee shop" and on average 19 out of 20 results have no website at all — and most have no reachable phone number either. This actor filters out both problems in one pass and flags which businesses are worth chasing.
What the actor does
- Pulls Google Maps search results through Google's internal JSON endpoint (
tbm=map). No headless browser, so it is much faster and cheaper. - Extracts name, address, rating, review count, category, coordinates, phone number, and website.
- Computes a
gapScorefrom 0-100 for every business, with a ready-to-use pitch reason you can read aloud when calling. - Returns only viable leads: businesses with no own website and a public phone number.
Input
location is not a decoration: the city name there moves the search center. Google's geographic filter lives in pb under !2d{lng}!3d{lat}; if the city is not mapped to the right coordinates, you get results from the wrong city without any warning.
How the actor handles location:
- it matches the name against an offline city table (819 entries: 217 Indonesian cities + major world cities), ignoring case, spaces, and the
kotaprefix; - it uses the match as the search center point, not just as an extra query word;
- it also warns when
countryCodedoesn't match that city's country.
If the city name is not in the table, the actor stops with a clear message — failing is better than delivering leads from the wrong city. If the place you need is missing, pass lat/lng directly, or add the city to packages/gmaps-core/src/gmaps_core/data/cities.json.
| Field | Type | Default | Description |
|---|---|---|---|
searchStrings | list of string | ["Kopi"] | Keywords. The actor combines them with location. |
location | string | required | The city used as the scan center, e.g. Jakarta. Matched against the offline city table. |
lat / lng | number | empty | Manual center point. Leave empty unless you know the exact coordinates. |
radiusKm | number | 30 | Radius around the center point. |
maxItems | integer | 20 | Total results per keyword (1-480). Automatically split into multiple requests. |
language | string | id | Google's response language code, e.g. id or en. |
countryCode | string | id | Google's response country code, e.g. id or jp. |
onlyGapLeads | boolean | true | Only leads without a website and with a phone number. |
minGapScore | integer | 0 | Drop leads below this score. |
includeUnproven | boolean | false | Include businesses without any rating. |
includeRaw | boolean | false | Return every place, not just leads. |
debugDump | boolean | false | Save the raw Google payload to the key-value store. |
Output
Three outputs at once:
| Destination | Location on the run page | Contents |
|---|---|---|
| Spreadsheet | key-value store default, record leads.xlsx | 2 sheets: Leads and Ringkasan. |
| Raw data | dataset | One row per place, filterable in the Apify UI. |
| Run summary | key-value store default, record SUMMARY | Run statistics and parameters. |
The dataset has two views, chosen from the dropdown above the table:
| View | Contents |
|---|---|
Leads | 15 columns for contacting prospects, sorted from the highest score. |
Semua tempat (All places) | All 24 columns, including businesses that already have their own website. |
Column headers in the exported files are in Indonesian, and the Prioritas (Priority) column uses Panas / Hangat / Dingin (Hot / Warm / Cold) — the English values only matter inside the code.
Spreadsheet contents
The Leads sheet contains every scanned place (not just leads), sorted by score — handy because the people who need to filter it can use the Autofilter dropdown:
No, Nama (Name), Kategori (Category), Rating, Ulasan (Reviews), Telepon (Phone), WhatsApp, Kota (City), Kecamatan (District), Alamat (Address), Website, Skor Gap (Gap Score), Prioritas (Priority), Alasan (script jualan) (Reason - pitch script), Buka di Maps (Open in Maps), Penawaran (Offer).
Some deliberate details:
- Phone numbers are stored as text. If they were numbers, Excel would drop leading zeros and the number would be undialable.
- The WhatsApp column is a ready-to-click
wa.melink, but only for mobile numbers. Landline numbers can't be used with WhatsApp, so they're left empty. - Reason and Offer columns are in Indonesian, ready to be read aloud when calling.
The Ringkasan (Summary) sheet contains the run configuration, the number of leads per priority, and how many have a phone number.
Dataset fields
| Field | Description |
|---|---|
title, address, rating, review_count, category | Basic business data. |
phone, phoneInternational, phoneDigits | Phone number in three formats. |
website | Website URL if any. |
websiteKind | own (has its own website), social (social account only), none. |
gapScore | 0-100. Higher = more worth pursuing. |
gapTier | hot / warm / cold / none. |
isGapLead | true when worth contacting for website services. |
gapReason | Reason in Indonesian, ready to read to the prospect. |
suggestedOffer | Recommended offer angle. |
place_url, reviews_url, lat, lng | For manual verification. |
How the score is calculated
Three components, all transparent:
| Component | Points | Logic |
|---|---|---|
| Digital gap | 0-40 | No web (40), social only (32), already has a website (0). |
| Reputation | 0-47 | Rating only counts above 3.5; reviews use a logarithmic scale. |
| Contactability | 0-15 | Has a public phone number. |
Two deliberate adjustments:
- Ratings below 3.5 score 0 points. A weak business is a risk, not an opportunity.
- Very high review counts (>3,000) lose 8 points. The business is already established, so selling a personal website service is harder.
Example output
{"no": 1,"title": "BARBIE LAUNDRY","address": "Jl. Puntik Kemuning No.88, Peanut, Tj. Karang Timur","rating": 4.7,"review_count": 464,"phone": "0812-7923-7715","phoneInternational": "+62 812-7923-7715","phoneDigits": "081279237715","website": "","websiteKind": "none","hasPhone": true,"hasWebsite": false,"contactable": true,"gapScore": 93,"gapTier": "hot","isGapLead": true,"gapReason": "Tidak punya website sama sekali. Reputasi sudah bagus (rating 4.7, 464 ulasan). Ada nomor telepon publik sehingga bisa langsung dihubungi.","suggestedOffer": "Website + Google Business Profile + landing page promo - tawarkan social proof dari ulasan yang sudah banyak"}
Example run
apify run umkm-google-maps \--input '{"searchStrings":["warung kopi","laundry"],"location":"Bandar Lampung","maxItems":40}'# Different cityapify run umkm-google-maps \--input '{"searchStrings":["kopi"],"location":"Jakarta","maxItems":100}'# Foreign city: adjust the country code, otherwise the Reviews column# follows the Indonesian languageapify run umkm-google-maps \--input '{"searchStrings":["coffee"],"location":"Tokyo","countryCode":"jp","language":"en"}'
Proxy (optional)
If you hit rate limits, the actor can scrape through a proxy. Credentials are never placed in the input schema — only passed through the SCRAPER_PROXY environment variable in http://user:pass@host:port format.
For Apify runs, set it via Console → this actor → Environment variables, not through actor.json and not through run input. The actor.json value is only used when deploying with the Apify CLI; deploys from GitHub take the value from Console.
For local runs, just set it in the shell:
$export SCRAPER_PROXY="http://user:pass@host:port"
If empty, the actor runs without a proxy.
Technical notes
Scraping uses curl_cffi to spoof a Chrome-like TLS fingerprint. Without it, Google blocks the request at the network level. Rate limiting of 1.5-3 seconds with exponential backoff is baked into the code and cannot be turned off.
Google's field index structure lives in packages/gmaps-core/src/gmaps_core/indexes.py — the only place you need to touch if Google changes its response.
Limitations
- Maximum 40 results per request (Google's limit), so
maxItemsis split automatically. Hard cap formaxItems= 480 per keyword. If you get fewer results thanmaxItems, it means there really are no more places in that area — the scrape did not fail. locationmust be the name of a city in the table. Street addresses, formatted names, or subdistrict names (Kemang,Kemang, Jatiasih) are not recognized. Uselat/lngfor those cases.- Phone numbers only exist when the business owner publicly posted them on their Google Business profile. The actor never guesses or makes up numbers.
- Data comes from Google Maps. Respect Google's terms of service, and do not use this actor to collect personal data the business owner has not published.