Google Maps Lead Scraper & SMB Audit avatar

Google Maps Lead Scraper & SMB Audit

Pricing

from $2.00 / 1,000 results

Go to Apify Store
Google Maps Lead Scraper & SMB Audit

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

Muhammad Sajid Izzulhaq

Maintained by Community

Actor 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

  1. Pulls Google Maps search results through Google's internal JSON endpoint (tbm=map). No headless browser, so it is much faster and cheaper.
  2. Extracts name, address, rating, review count, category, coordinates, phone number, and website.
  3. Computes a gapScore from 0-100 for every business, with a ready-to-use pitch reason you can read aloud when calling.
  4. 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:

  1. it matches the name against an offline city table (819 entries: 217 Indonesian cities + major world cities), ignoring case, spaces, and the kota prefix;
  2. it uses the match as the search center point, not just as an extra query word;
  3. it also warns when countryCode doesn'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.

FieldTypeDefaultDescription
searchStringslist of string["Kopi"]Keywords. The actor combines them with location.
locationstringrequiredThe city used as the scan center, e.g. Jakarta. Matched against the offline city table.
lat / lngnumberemptyManual center point. Leave empty unless you know the exact coordinates.
radiusKmnumber30Radius around the center point.
maxItemsinteger20Total results per keyword (1-480). Automatically split into multiple requests.
languagestringidGoogle's response language code, e.g. id or en.
countryCodestringidGoogle's response country code, e.g. id or jp.
onlyGapLeadsbooleantrueOnly leads without a website and with a phone number.
minGapScoreinteger0Drop leads below this score.
includeUnprovenbooleanfalseInclude businesses without any rating.
includeRawbooleanfalseReturn every place, not just leads.
debugDumpbooleanfalseSave the raw Google payload to the key-value store.

Output

Three outputs at once:

DestinationLocation on the run pageContents
Spreadsheetkey-value store default, record leads.xlsx2 sheets: Leads and Ringkasan.
Raw datadatasetOne row per place, filterable in the Apify UI.
Run summarykey-value store default, record SUMMARYRun statistics and parameters.

The dataset has two views, chosen from the dropdown above the table:

ViewContents
Leads15 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.me link, 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

FieldDescription
title, address, rating, review_count, categoryBasic business data.
phone, phoneInternational, phoneDigitsPhone number in three formats.
websiteWebsite URL if any.
websiteKindown (has its own website), social (social account only), none.
gapScore0-100. Higher = more worth pursuing.
gapTierhot / warm / cold / none.
isGapLeadtrue when worth contacting for website services.
gapReasonReason in Indonesian, ready to read to the prospect.
suggestedOfferRecommended offer angle.
place_url, reviews_url, lat, lngFor manual verification.

How the score is calculated

Three components, all transparent:

ComponentPointsLogic
Digital gap0-40No web (40), social only (32), already has a website (0).
Reputation0-47Rating only counts above 3.5; reviews use a logarithmic scale.
Contactability0-15Has 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 city
apify run umkm-google-maps \
--input '{"searchStrings":["kopi"],"location":"Jakarta","maxItems":100}'
# Foreign city: adjust the country code, otherwise the Reviews column
# follows the Indonesian language
apify 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 maxItems is split automatically. Hard cap for maxItems = 480 per keyword. If you get fewer results than maxItems, it means there really are no more places in that area — the scrape did not fail.
  • location must be the name of a city in the table. Street addresses, formatted names, or subdistrict names (Kemang, Kemang, Jatiasih) are not recognized. Use lat/lng for 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.