DEV Community

Cover image for How to Scrape Google Maps (With Emails) in Python (2026 Guide)
Azzam
Azzam

Posted on

How to Scrape Google Maps (With Emails) in Python (2026 Guide)

Google Maps is the biggest directory of local businesses there is, and if you sell to dentists, plumbers or restaurants it's where your lead list lives. The problem is that copying listings by hand is slow, and Maps never shows an email address. This guide shows how to scrape Google Maps from Python, get one clean row per business, and add the emails each business publishes on its own website.

Quick start

pip install apify-client
Enter fullscreen mode Exit fullscreen mode
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("automly/google-maps-scraper").call(run_input={
    "searchStringsArray": ["dentists in Chicago"],
    "maxCrawledPlacesPerSearch": 50,
    "includeContacts": True,
})

for place in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(place["title"], "|", place.get("phone"), "|", place.get("emails"))
Enter fullscreen mode Exit fullscreen mode

That's a working Google Maps scraper in Python. Your token is in the Apify Console under Settings > API & Integrations. Not using Python? The same actor works as a plain Google Maps scraper API: a POST to Apify's run-sync-get-dataset-items endpoint with the same JSON input returns the rows as JSON.

Why not the official API?

Google does have a Google Maps API for this: the Places API (New). It works, but two things make it a poor fit for lead lists. It has no email field at all, and one Text Search query stops at 60 results across all pages. The phone, website, rating and opening hours fields also sit in the Enterprise tier, so asking for them bills the more expensive SKU.

Since March 1, 2025, Google no longer gives a $200 monthly credit. Each SKU now has its own free monthly cap instead. Prices below are the first volume tier from Google's pricing page, checked October 2026:

Option Access needed Cost What you get
Places API (New), Text Search Pro Google Cloud project, billing account, API key $32 per 1,000 requests, 5,000 free per month Name, address, category, location. No phone, website or rating
Places API (New), Text Search Enterprise Same $35 per 1,000 requests, 1,000 free per month Adds phone, website, rating, review count, hours. Up to 20 places per request, 60 per query. No email
Places API (New), Place Details Enterprise Same $20 per 1,000 requests, 1,000 free per month The same Enterprise fields for one place per request. No email
Google Maps Scraper (this guide) Free Apify account $3.00 per 1,000 places, plus $0.01 per run and platform usage Name, category, phone, website, address, rating, reviews, hours and more. Optional emails and social profiles from the business's website

If you need a handful of lookups inside an app, the official API is fine. If you need every dentist in a city with an email column, you need something else.

Step 1: Pick what and where to search

There are two ways to say where. You can put the city in the query, like plumbers in Denver, or keep the query short and set locationQuery:

run_input = {
    "searchStringsArray": ["plumber", "electrician"],
    "locationQuery": "Austin, TX",
    "maxCrawledPlacesPerSearch": 100,
    "language": "en",
}
Enter fullscreen mode Exit fullscreen mode

locationQuery takes a city, a state, a postal code or a country, and only places inside that area are kept. Each query in searchStringsArray runs separately, and maxCrawledPlacesPerSearch caps each one (the default is 100).

You can also skip searching and pass Google Maps links in startUrls or place IDs in placeIds if you already know which businesses you want.

Step 2: Turn on emails and social profiles

This is the part people mean when they search for a "Google Maps email scraper". Set includeContacts to True and the scraper visits each place's own website and adds three fields:

  • emails: addresses found on the site
  • phones: phone numbers found on the site (often more than the one on Maps)
  • socialProfiles: Facebook, Instagram, LinkedIn, X, YouTube, TikTok and Pinterest links

To be clear about where these come from: Google Maps itself doesn't publish emails. They come from the business's website. So places without a website get no email, and some websites simply don't list one. In a recent run of 15 Austin dentists, 14 came back with an email or a social profile.

Step 3: Filter before you pay for rows

Filters run before a place is saved, so skipped places aren't charged:

run_input.update({
    "placeMinimumStars": "four",       # also: "threeAndHalf", "fourAndHalf", ...
    "placeMinimumReviews": 20,
    "website": "withWebsite",          # or "withoutWebsite", "allPlaces"
    "skipClosedPlaces": True,
})
Enter fullscreen mode Exit fullscreen mode

A tip if you sell web design: set website to "withoutWebsite". Those businesses are your leads, and you call them instead of emailing.

Step 4: Read the output

Here's a real row, trimmed and anonymized:

{
  "title": "Example Hills Dentistry",
  "categoryName": "Dentist",
  "address": "Example Hills Dentistry, 1200 W Example Blvd #207, Austin, TX 78704",
  "city": "Austin",
  "postalCode": "78704",
  "website": "https://www.example-dentist.com/",
  "phone": "(512) 555-0147",
  "phoneUnformatted": "+15125550147",
  "totalScore": 5,
  "reviewsCount": 354,
  "openingHours": [{"day": "Friday", "hours": "8 AM-4 PM"}],
  "placeId": "ChIJ...",
  "url": "https://www.google.com/maps/place/?q=place_id:ChIJ...",
  "emails": ["hello@example-dentist.com"],
  "phones": ["+15125550147", "+17375550192"],
  "socialProfiles": {
    "facebook": ["https://www.facebook.com/example"],
    "instagram": ["https://www.instagram.com/example"]
  }
}
Enter fullscreen mode Exit fullscreen mode

placeId stays the same between runs, which makes it a good key when you refresh a list and want to spot new businesses.

Step 5: Save to CSV

import csv

rows = client.dataset(run["defaultDatasetId"]).iterate_items()
fields = ["title", "categoryName", "phone", "website", "address",
          "totalScore", "reviewsCount", "emails", "placeId"]

with open("leads.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
    writer.writeheader()
    for place in rows:
        place["emails"] = "; ".join(place.get("emails") or [])
        writer.writerow(place)
Enter fullscreen mode Exit fullscreen mode

If you prefer pandas, pd.DataFrame(client.dataset(...).list_items().items) gets you there too.

What else can you scrape?

The same actor can add more to each place:

  • maxReviews: the newest (or most relevant) reviews per place, with text, rating, date and the owner's reply. reviewsStartDate limits them to recent ones.
  • maxImages: photo links with the date each was added.
  • scrapePlaceDetailPage: popular times, review counts per star, review topics and "people also search for".
  • categoryFilterWords: keep only places whose Google category matches, say, italian restaurant.

Scaling

One Google Maps search shows around 200 places, and a big city has far more. Set enableGridSearch to True and the area is split into smaller cells that are searched one by one and merged, so a single query can return thousands of places. gridCellKm sets the cell size: 5 for a quick pass, 2 for a thorough one, 1 for the most complete list. For a city that isn't recognized automatically, pass customBounds with south, west, north and east.

For a list you keep fresh, save the input as a task in Apify and put it on a weekly schedule.

No code? Use the Apify Console

Open the actor page, click Try for free, type your searches and location, flip Add emails and social profiles, and click Start. The results show in tables like Overview, Contacts and Website contacts, and download as CSV, Excel or JSON. Website contacts puts the name, website, emails, phones and social profiles side by side, and it's the one I'd hand to a sales team. Google Sheets, Zapier, Make and n8n can pick up each run.

What it costs

Right now places cost $3.00 per 1,000, plus $0.01 per run start and the run's Apify platform usage. Contact details have no separate charge today.

That changes on October 17, 2026, when contact details get their own price, charged only for places where the website gave at least one email, phone or social profile:

Apify plan Per 1,000 places with contacts found
Free $1.80
Starter $1.60
Scale $1.20
Business and up $0.85

Every Apify account gets $5 of free usage per month, which covers roughly 1,500 plain places. The Pricing tab on the actor page always has the current numbers.

Responsible use

Stick to public business data. Some of it, like an owner's name or a personal email, can still be personal data under GDPR or CCPA, and cold outreach has its own rules such as CAN-SPAM. Follow Google's terms and ask a lawyer if you're unsure.

Links

If you want a lead list by this afternoon, try Google Maps Scraper on Apify. The free $5 a month is enough to test it on your own city.

Top comments (0)