Skip to content

PRO100CHOK/google-maps-leads-scraper-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Google Maps Leads Scraper — Python Example

Extract local business leads from Google Maps in Python — names, addresses, phones, emails, websites, ratings, reviews, photos, social handles. Geo-drilling unlocks past Google's 120-place cap per query. Built for B2B local-business outreach, market mapping, and review-monitoring workflows.

Apify Actor Python 3.10+ License: MIT

Working Python project that drives the Google Maps Scraper Apify actor — a focused local-leads extractor optimized for B2B prospecting workflows. Pulls business profile data, reviews, photos, and (its standout feature) emails + social-media handles harvested from each place's own website.

What this does

The official Google Places API exists but it's expensive ($17 per 1,000 detailed lookups after free tier), rate-limited, and (critically) does not return business email addresses, social handles, or any deep contact data. It also caps results at 60 places per query no matter what you do. For lead-gen workflows you typically need the email, not just the phone.

This actor scrapes Google Maps' public results, follows each business's website link, runs a contact-extraction pass on the homepage + contact / about pages, and returns the email + social handles inline with each place record. It also breaks through Google's 120-place query cap via geo-drilling: when a search area saturates (≥20 results), the actor automatically subdivides the area into sub-areas and re-queries. This is how you go from "the top 60 plumbers in Chicago" to "every plumber in Chicago".

Use cases

  • Local B2B outreach lead lists — pull every restaurant / dentist / lawyer / plumber in a city with email + phone for SDR campaigns.
  • Local SEO agency prospecting — find businesses with low ratings or no website (= ideal upgrade targets).
  • Review monitoring — pull recent Google reviews for your locations or competitors' on a schedule.
  • Market sizing — count the total number of places in a category across a metro area before launching.
  • Franchise expansion research — map every existing competitor location before picking new sites.
  • Real-estate analytics — geocode all retail businesses in a corridor for foot-traffic modeling.
  • Academic geography research — build datasets of public-facing local businesses for urbanism studies.

Requirements

  • Python 3.10+
  • A free Apify account
  • No Google Cloud Platform / Places API key required

Quick start

git clone https://github.com/pro100chok/google-maps-leads-scraper-python.git
cd google-maps-leads-scraper-python
pip install -r requirements.txt
cp .env.example .env
# paste your APIFY_API_TOKEN
python main.py

main.py pulls up to 100 dental clinics in Austin, TX with their website-derived emails. Edit the constants at the top of the file to point at any city + business category combination.

What you get per place

Field group Notes
Identity title, categories[], categoryName, address, city, state, countryCode, postalCode, lat/lng.
Contact phone, phoneUnformatted, website, emails[] (from website), socials (Facebook, Instagram, X, LinkedIn, YouTube, TikTok, Telegram).
Status permanentlyClosed, temporarilyClosed, claimThisBusiness (whether the listing is claimed).
Reviews totalScore, reviewsCount, reviewsDistribution (5-star buckets), reviews[] (when includeReviews=true).
Images images[] (when includeImages=true).
Operations openingHours[] per day, popularTimesHistogram if Google has the data.
Misc priceRange, menu, reservations, orderUrls, additionalInfo (category-specific attributes).

How it works

  1. The actor sends the search terms + location (free text, structured city/state/country, or both) to Google Maps' internal /maps/search endpoints.
  2. It paginates through the search result list (Google's UI caps at ~120 results — this is the wall everyone hits).
  3. If deepSearch=true, when a search area returns ≥20 results, the actor recursively splits the bounding box into 4 sub-areas and re-queries each — repeating until each sub-area returns under the 20-place threshold. This lets you reliably pull every place in a city in one run.
  4. For each place, it fetches the place-detail page to get the full record (hours, photos, attributes).
  5. If scrapeContactsFromWebsite=true and the place has a website, the actor visits the homepage plus contact / about pages and extracts emails + phones + social handles.
  6. If includeReviews=true, it paginates Google's internal review API in batches of 20 to attach up to maxReviewsPerPlace reviews per place.

Example: local SDR lead list

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_API_TOKEN"])

run = client.actor("pro100chok/google-maps-scraper").call(run_input={
    "searchStringsArray": ["chiropractor"],
    "locationQuery": "Denver, CO",
    "maxCrawledPlacesPerSearch": 80,
    "scrapeContactsFromWebsite": True,
    "skipPlacesWithoutEmail": True,
})

for p in client.dataset(run["defaultDatasetId"]).iterate_items():
    email = (p.get("emails") or [""])[0]
    print(f"{p['title']:<40} {p['phone']:<18} {email}")

Example output

{
  "title": "Smile Bright Dentistry",
  "address": "123 Example Blvd, Austin, TX 78701",
  "lat": 30.2672, "lng": -97.7431,
  "phone": "+1-512-555-0123",
  "website": "https://smilebright.example",
  "emails": ["info@smilebright.example", "hello@smilebright.example"],
  "socials": {
    "facebook": "https://facebook.com/smilebright",
    "instagram": "https://instagram.com/smilebright"
  },
  "totalScore": 4.8,
  "reviewsCount": 312,
  "claimThisBusiness": true,
  "categoryName": "Dentist",
  "openingHours": [
    { "day": "Monday", "hours": "8AM–6PM" }
  ]
}

Input parameters

Parameter Type Required Description
searchStringsArray string[] yes Search terms (e.g. coffee shop, dental clinic).
locationQuery string no* Free-text location (Berlin, Germany).
city / state / country string no* Structured location fields (combined into one query).
maxCrawledPlacesPerSearch integer no Cap per (term, location) pair. Default 100. 0 = unlimited with deepSearch.
deepSearch boolean no Recursive geo-drilling to bypass Google's 120-place cap. Default false.
categoryFilter string no Post-filter results by category substring (Barber shop).
language string no hl parameter — language for the UI. Default en.
countryCode string no gl parameter — 2-letter ISO country code. Default us.
scrapeContactsFromWebsite boolean no Visit each place's website to extract emails + socials. Default true.
skipPlacesWithoutEmail boolean no Drop places that have no extractable email. Default false.
includeReviews boolean no Pull individual reviews per place. Default false.
maxReviewsPerPlace integer no Cap on reviews per place. Default 20.
includeImages boolean no Attach photo URLs per place. Default false.
maxImagesPerPlace integer no Cap on images. Default 20.
concurrency integer no Parallel workers during deep search. Default 3.
maxRetries integer no Retries per request on transient failures. Default 5.

* Either locationQuery or the city/state/country trio is required.

More examples

File Demonstrates
examples/01_basic_usage.py Single-keyword + single-city search.
examples/02_deep_search_bulk.py Geo-drilling to bypass the 120-place cap.
examples/03_reviews_sentiment.py Star-rating distribution per place.
examples/04_export_to_csv.py Multi-city plumber lead list with pandas filtering.
examples/05_export_to_google_sheets.py Append leads into a shared Sheet for SDR teams.

FAQ

How much does it cost? The actor is metered per place returned. Apify's free $5 monthly credit covers a few thousand places. Optional add-ons (reviews, images) are billed separately at lower per-item rates.

How does this compare with the Google Places API? Google's Places API costs roughly $17 per 1,000 detailed lookups and does not return business emails, social-media handles, or claim-status flags. This actor includes contact extraction by default. It also has no per-day quota and no project-level approval requirements.

Will the data look like what I see on maps.google.com? Yes — the actor pulls from the same internal endpoints that power the Google Maps website. Ratings, review counts, photos, opening hours all match the live UI.

Why is the 120-place cap a thing? Google Maps' search UI is designed for end-users browsing for a place, not for bulk enumeration. The pagination tops out at ~120 results regardless of how dense the area is. deepSearch works around it by automatically splitting the search area; no manual bounding-box config needed.

Does it work outside the US? Yes — Google Maps coverage is global. Use countryCode to set the geolocation hint and language to pick the UI language. Most European countries, LATAM, and Asia work cleanly.

How accurate are the website-derived emails? The contact extractor runs the same homepage + contact-page crawl described in the Website Contact Scraper actor. About 60–85% of US local businesses have at least one extractable email; gyms, restaurants, retail are highest, attorneys and medical are lower (lots of contact forms instead of plain mailto).

Can I get emails for places without a website? No — if the place doesn't link a website in its Google profile, there's no source to extract from. Filter those out with skipPlacesWithoutEmail: true.

Is this legal for cold outreach? You're scraping publicly-available data from Google Maps and the businesses' own websites. B2B cold email to those addresses is legal under most jurisdictions (CAN-SPAM in the US, legitimate-interest under GDPR in the EU) provided you offer an opt-out and identify yourself. Consult your own legal team for production-scale outreach.

Related actors

See all my actors at apify.com/pro100chok.

License

MIT — see LICENSE.


Built on top of the Google Maps Scraper Apify actor.