Google Maps Scraper – Multi-City Lead Lists with Emails
Pricing
from $1.20 / 1,000 results
Google Maps Scraper – Multi-City Lead Lists with Emails
Google Maps leads scraper for multi-city lead lists. Enter business categories and cities to get one deduplicated list with phone, website, rating, hours, role emails, social profiles and WhatsApp from each site. Whole-city grid coverage, free filters, and you pay only for the leads returned.
Pricing
from $1.20 / 1,000 results
Rating
0.0
(0)
Developer
Locomint
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
This Google Maps leads scraper takes a list of business categories and a list of cities and returns one deduplicated lead list: each business's listing phone, website, rating and opening hours, plus the role mailboxes, social profiles and WhatsApp number published on its own website. Website contacts are included, and you pay only for the leads returned.
{"categories": ["dentist", "orthodontist"],"cities": ["Manchester, United Kingdom", "Leeds, United Kingdom"],"maxPlacesPerCombination": 100}
What it does
Every city and category pair is one search, so two categories in three cities make six searches, run city by city. A run takes up to 25 categories and 50 cities and runs the first 100 pairs; pairs beyond 100 are skipped with a warning in the log, so split bigger jobs across runs.
Each search collects up to maxPlacesPerCombination places (500 at most). A plain map search runs dry at about 100 to 120 results per area. Past that the actor splits the city into a grid of up to 64 cells and searches each cell, ending early once three cells in a row add fewer than two new places. Results outside the resolved city are dropped, so a search in Lagos, Nigeria never hands you restaurants in Lagos, Portugal.
The list is deduplicated across the whole run by place_id. A clinic that matches both "dentist" and "orthodontist", or sits where two cities' areas overlap, is delivered and charged once, and its search_city and search_category columns name the pair that found it first.
Each lead then gets two more reads, both included in the price:
- The full listing (
includeDetails): description, opening hours for all seven days, timezone, plus code, photo count with up to five photo URLs, and attributes such as delivery or dine-in. - The business website (
includeEnrichment): the homepage, plus one hop to the contact page when the homepage is missing an email address or a WhatsApp number. It returns role mailboxes, social profile links (Facebook, Instagram, LinkedIn, X, YouTube, TikTok), a WhatsApp number from click-to-chat links, the contact page URL, the technologies the site runs on (about 35 signatures) and awebsite_status.
The four filters are free and run on the search result before either read, so a lead that fails a filter is never fetched and never charged.
What each row contains
| Group | Columns |
|---|---|
| Found by | search_city, search_category |
| Identity | place_id, cid, name, primary_category, categories, description |
| Location | address, street, city, region, postal_code, country_code, lat, lng, plus_code, timezone, service_area_business |
| Listing contact | phone (as displayed), phone_e164, website |
| Reputation | rating, review_count, price_level |
| Status and hours | status, open_now, hours, hours_status |
| Media and attributes | photos_count, photo_urls, attributes |
| From the website | emails, socials, whatsapp, contact_form_url, tech_stack, website_status, enriched_at |
| Provenance | source, fetched_at |
website_status is one of ok, unreachable (the site was down or refused the request), parked (a domain-for-sale page), redirect_social (the "website" is a Facebook or Instagram page, kept in socials) or no_website. hours is keyed by weekday, each day a list of {"open": "09:00", "close": "18:00"} periods in local 24-hour time, with an empty list for a closed day.
Who it is for
- Lead generation. Build a territory in one run: every physiotherapist in twelve cities, with phone, role mailbox and WhatsApp in the same row and no duplicates to clean up.
- Sales prospecting. Split the result by
search_cityand give each rep their patch.rating,review_countandopen_nowseparate busy businesses from quiet ones before anyone picks up the phone. - Agencies.
websiteFilter: "without"across a region lists every business in a category with no website, andwebsite_status: "parked"finds the lapsed domains. - Local SEO. Benchmark a multi-location client against the competition in each of its cities: rating, review count, categories and photo count side by side.
- CRM enrichment. Load a new territory into your CRM with
place_idas the dedupe key, so the next run updates records instead of duplicating them. - Market research. Compare how many businesses of a category each city has, and how they rate, from one dataset with
latandlngfor mapping.
How to use it
- Open the actor in Apify Console and click Try for free.
- Enter business categories, one per line:
dentist,physiotherapist. - Enter cities with the country, one per line:
Manchester, United Kingdom,Leeds, United Kingdom. "Warsaw" alone can resolve to Warsaw, Indiana; "Warsaw, Poland" cannot. - Set max leads per city x category and any filters.
- Click Start and export the dataset to CSV, Excel or JSON, or read it through the API.
The synchronous endpoint runs the actor and returns the rows in one request:
curl -X POST "https://api.apify.com/v2/acts/locomint~google-maps-multi-city-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"categories": ["dentist"], "cities": ["Manchester, United Kingdom", "Leeds, United Kingdom"], "maxPlacesPerCombination": 25}'
That endpoint waits at most 300 seconds, which is about 100 leads with details on. For bigger lists use the Python client (pip install apify-client), which starts the run and waits for it to finish:
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("locomint/google-maps-multi-city-scraper").call(run_input={"categories": ["dentist", "orthodontist"],"cities": ["London, United Kingdom", "Manchester, United Kingdom"],"maxPlacesPerCombination": 200,"websiteFilter": "with",})for lead in client.dataset(run.default_dataset_id).iterate_items():print(lead["search_city"], lead["name"], lead["phone_e164"], lead["emails"])
Input example
{"categories": ["dentist", "orthodontist"],"cities": ["London, United Kingdom","Manchester, United Kingdom","Birmingham, United Kingdom"],"maxPlacesPerCombination": 200,"placeMinimumStars": "4","websiteFilter": "with","skipClosedPlaces": true,"categoryFilterWords": ["dent", "orthodont"],"includeDetails": true,"includeEnrichment": true,"language": "en"}
That is six searches of up to 200 leads each, so at most 1,200 leads.
| Field | Default | What it does |
|---|---|---|
categories | required | Up to 25. Each category is searched in every city. |
cities | required | Up to 50, each with its country. At most 100 city x category pairs run per run. |
maxPlacesPerCombination | 20 | Cap per city x category pair, 1 to 500. |
placeMinimumStars | any | "2" to "4.5". Places with no rating are left out. |
websiteFilter | any | with or without a website. |
skipClosedPlaces | false | Leave out places marked temporarily or permanently closed. |
categoryFilterWords | – | Keep a place only if its categories contain one of these words (case-insensitive). |
includeDetails | true | Read the full listing. Same price either way. When off, hours is empty and hours_status reads not_published. |
includeEnrichment | true | Visit each business website. Same price either way. |
language | en | Language for names and categories (en, de, ar, ja, …). |
Output example
A lead from a restaurant search in Lisbon, Portugal, trimmed to the columns most people use first:
{"place_id": "ChIJ…","name": "Belcanto","primary_category": "Fine dining restaurant","categories": ["Fine dining restaurant", "Portuguese restaurant", "Restaurant"],"phone": "+351 21 342 0607","phone_e164": "+351213420607","website": "https://belcanto.pt/","rating": 4.6,"review_count": 6697,"status": "open","address": "Largo de São Carlos 10, 1200-410 Lisboa, Portugal","city": "Lisbon","country_code": "PT","lat": 38.7099,"lng": -9.1418,"emails": ["press@belcanto.pt", "work@belcanto.pt"],"socials": {"facebook": "https://www.facebook.com/belcantolisboa","instagram": "https://www.instagram.com/belcantolisboa","linkedin": null,"x": null,"youtube": null,"tiktok": null},"whatsapp": null,"contact_form_url": "https://belcanto.pt/contact/","tech_stack": ["wordpress", "react", "google-analytics"],"website_status": "ok","search_category": "restaurant","search_city": "Lisbon, Portugal"}
Missing values are null or an empty list, never guessed.
Pricing
This actor is pay-per-event. You pay for two events and nothing else; Apify platform usage and proxy traffic are not billed to you separately.
| Event | Price | Charged |
|---|---|---|
| Lead delivered | $0.0015 ($1.50 per 1,000) until 26 September 2026, then $0.0030 ($3.00 per 1,000) | once per row in the dataset |
| Actor start | $0.00005 per GB of run memory | once per run ($0.00005 at 1 GB) |
Worked example. 1,000 dentists across London, Manchester and Birmingham: 1,000 x $0.0015 = $1.50, plus $0.00005 for the start. From 26 September 2026 the same run is $3.00.
The most a run can cost is cities x categories x maxPlacesPerCombination x the price per lead, and most runs cost less, because you are not charged for:
- Filtered-out places. The filters run on the search result, before the listing and the website are read, and a place that fails is never written to the dataset.
- Duplicates. A business found by two categories or in two overlapping cities is delivered and charged once.
- Empty searches. A pair that finds nothing costs nothing beyond the start event.
Set a maximum cost per run in the run options and the actor stops before the next row would pass it. Apify's free plan includes $5 of usage a month with no card.
FAQ
How many leads will one run return?
Up to maxPlacesPerCombination for each city and category pair, less filtered places and duplicates. Common categories in big cities reach the cap, helped by the grid sweep past the first 100 to 120 results; niche categories return what exists.
What happens when a business matches two categories?
It is delivered once, and charged once. search_category and search_city record the pair that found it first, in the order the searches ran: city by city, and within a city in the order you listed the categories.
Why must every city include the country? A bare city name is resolved to whichever place the map picks first. In our tests "Warsaw" came back as Warsaw, Indiana, while "Warsaw, Poland" cannot be misread.
Why do some leads have no email?
The business has no website, the site did not answer, or it publishes no role mailbox on its homepage or contact page. A 403 or a challenge page counts as a refusal, and the actor does not retry it through another IP. Mailboxes that name a person are dropped on purpose, and contact_form_url still gives you the site's contact form.
Does it include reviews?
No. Every lead carries rating and review_count, but not review text. The reviews_summary column exists for compatibility and is empty in this actor.
Can I schedule it and get only new businesses?
The actor does not remember earlier runs: each run delivers, and charges for, everything it finds. Schedule it in Apify and compare place_id against your previous dataset to see which businesses are new and which have disappeared.
Is it legal to scrape Google Maps for leads? The actor reads publicly available listing pages and business websites while logged out, never signs in, never solves a CAPTCHA, and backs off when a site refuses it. How you use the list is your responsibility: cold outreach to businesses is regulated by laws such as GDPR, CAN-SPAM and PECR, and the rules differ by country.
Which countries does it cover? Any country with map listings, and cities from different countries can share one run. There is no country targeting; each city you type decides where its searches run.
Limits
- Run size. 25 categories, 50 cities and 100 city x category pairs per run; 500 leads per pair.
- Speed. Listing reads are paced at about 20 a minute, so a run with details on delivers roughly 20 leads a minute. The 1,200-lead example above takes about an hour.
- No name matching. This form has no option to match business names against the search term. For brand searches use the Google Maps Scraper & Email Extractor below.
- Reviews. No review text.
reviews_summary,snapshot_idandfirst_seen_atare always empty in actor runs. - Websites. Pages are read as served, without running JavaScript, so a contact page that only appears after scripts load is missed. The actor reads the homepage and at most one contact page. A site that takes longer than 20 seconds is abandoned: that row keeps its
websitebut showswebsite_status: "no_website"withenriched_at: null, meaning the site was not read. - Emails. Role mailboxes only, such as info@, sales@ and bookings@. An address on another domain, a Gmail account for example, is kept only when its name is a business function.
- Phones.
phoneandphone_e164come from the listing, not from the website.phone_e164is empty when only a national number was offered and the calling code cannot be settled safely (+1 and +7 are each shared by several countries). - Fields not yet seen filled.
price_levelhas not been populated in any record we have captured, and closed markers have not appeared in our captures either, soskipClosedPlaceshas not yet been tested against a real closed listing. - Addresses. Service-area businesses with no storefront have an empty
addressandservice_area_business: true;latandlngare still set. Australian localities come through with the state attached ("Melbourne VIC").
Compliance
Business contact points only: role mailboxes published on the business's own website. Mailboxes that name a person are dropped and owner names are never collected. You are responsible for using the data lawfully (for example GDPR, CAN-SPAM and PECR for outreach).
Questions, bug reports and feature requests go on this actor's Issues tab. Business owners can send data-removal requests to info@locomint.io; that address is for removal requests only. This actor keeps no copy of what it reads between runs, so each run returns what the public pages show at that moment.
Not affiliated with or endorsed by Google.
Other Locomint actors
- Google Maps Scraper & Email Extractor – Business Leads: One city at a time, with name matching and place-ID input.
- Google Maps Place Details Scraper – Bulk Place ID Lookup: Place IDs or place-page links in, full records out.
- Website Email Scraper – Contact Details, Socials & WhatsApp: Contact points from website URLs you supply.
- Website Content Crawler – Markdown for AI, Emails & Contacts: A whole site as Markdown, with its contact points.
- Bulk Email Verifier & Validator: Checks whether addresses can receive mail.
- Company Enrichment API – Domain to Emails, Socials & Tech: A domain in, its contacts and technologies out.
- AI Crawler Checker – robots.txt Rules for GPTBot & ClaudeBot: Which AI crawlers a site's robots.txt allows.
- Schema Markup Validator & Generator – JSON-LD Checker: Checks and generates schema.org markup.