Google Maps Scraper - Business Data, Reviews & Leads
Pricing
from $4.00 / 1,000 place results
Google Maps Scraper - Business Data, Reviews & Leads
Scrape Google Maps business listings without an API key — name, address, phone, website, rating, reviews, photos, hours, amenities & GPS. Precision targeting (batch, map URLs, custom area) + optional add-ons: emails, socials, contacts & ad intelligence. Export to CSV, Excel & JSON.
Pricing
from $4.00 / 1,000 place results
Rating
0.0
(0)
Developer
yossef Nagy
Maintained by CommunityActor stats
4
Bookmarked
408
Total users
52
Monthly active users
9 days ago
Last modified
Categories
Share
Google Maps Scraper
Extract business data from Google Maps at scale — names, addresses, phone numbers, websites, ratings, reviews, photos, opening hours, amenities, coordinates and more — for any type of business in any location. Extraction uses Google Maps' and Google Search's internal HTTP endpoints; no browser is launched.
It also ships powerful, optional layers: precise targeting (batch queries, map URLs, or a drawn area), deep reviews, complete place data, plus experimental lead enrichment (emails, socials, people/contacts) and ad intelligence (is the business advertising on Meta / Google right now?).
What it costs
This Actor uses pay per event pricing. The same flat prices apply to every customer; Apify Console shows the effective prices and your estimated run cost before you start. The charge units are:
| Event | Price | Charged unit |
|---|---|---|
| Result | $0.004 | Each saved business row in the default dataset. |
| Actor start | $0.00005 | Each GB of run memory, rounded up (minimum one). |
| Reviews | $0.0005 | Each real delivered review (author, 1-5 rating, and text or date), up to reviewsLimit per business. Highlights without an author are never billed. |
| Emails | $0.005 | Each distinct delivered email address on the business's own website domain or on a free email provider (such as Gmail, Outlook or Yahoo), only when you turn on includeEmails or emailOnly. Other addresses found on its pages (an agency's, a vendor's, a booking platform's or another location's mailbox) are returned but not billed. A business whose Maps website is a social profile, directory, site-builder, booking, listing or ordering page, or its store page on a shopping centre's, landlord's, food hall's or shared directory's site (for example highstreetplace.com/merchant/gracenote-coffee/), has no own domain, so only its free-mail addresses are billed; the landlord's or platform's mailboxes found there are returned free. |
| Social profiles | $0.006 | Each distinct delivered social profile URL, only when you turn on includeSocials or socialOnly. |
| People / contacts | $0.008 | Each saved business with at least one proven person (includePersonnel / onlyWithPersonnel): someone with their own email, LinkedIn profile or direct phone, or someone with a role title (or a professional prefix such as Dr.) in a real staff list on the site, such as a team page or a staff grid. A name taken from a page heading, a paragraph, a footer, a menu, a client testimonial or a button does not count, and neither does the business's own structured-data record; see People / contacts below. The business's own phone, email and social profiles stay in business_lead and are never shown as a person's. Emails and socials it returns are not billed unless you enabled them. |
| Meta ads | $0.004 | Each saved business whose Meta Ad Library check identified the business's own advertiser, including one with no currently active ads. A search that finds nothing, or only ads that cannot be attributed to the business, is returned as null and is not billed. |
| Google ads | $0.0008 | Each saved business with a website whose Google Ads Transparency lookup completed and could be attributed to it, including one where no ads were found. The advertiser is used when its name points at your business, or when it is the only advertiser on your website domain and is not an agency, retail-media brand or ordering/booking platform (an agency, retail-media brand or ordering platform that links to your website is never used). A business without its own website domain, an unreachable lookup, or ads that cannot be attributed to the business is returned as null and is not billed. |
Place details, photos, amenities, place extras and popular times have no separate fee; they are included in the Result price. Add-ons are charged per delivered unit, after the saved business has been written to the dataset. They are never charged once per run, and an attempted but inconclusive check is not billed. Rows removed by a filter and skipped rows are not saved and not charged. When your spending limit is reached, the last business is saved with all its data and only the add-on units that fit are charged (see below). Start with maxResults: 10, review the resulting charge breakdown, then scale the same input. For example, 10 saved businesses with five real reviews, two email addresses and one completed Meta check cost $0.0566 in event charges at 2 GB memory ($0.04 + $0.0001 + $0.0025 + $0.01 + $0.004), before any other enabled add-ons.
Which options switch on a paid add-on
Some filters imply an add-on check. The table shows exactly what each option runs and whether it is billed.
| You turn on | What also runs | Billed? |
|---|---|---|
includeEmails | Website crawl for email addresses | Yes, per distinct delivered email on the business's own website domain or a free email provider |
emailOnly | Same as includeEmails, plus the filter | Yes, per distinct delivered email on the business's own website domain or a free email provider (a business whose only email is on another domain is saved and billed the Result price only) |
includeSocials | Website crawl for social profiles | Yes, per distinct delivered social profile URL |
socialOnly | Same as includeSocials, plus the filter | Yes, per distinct delivered social profile URL (a profile that is itself the business's Google Maps website is free) |
includePersonnel | People search on the business website; it also finds emails and social profiles | People/contacts only. The emails and socials it returns are included in the row but are not billed unless you also enabled them above |
onlyWithPersonnel | Same as includePersonnel, plus the filter | People/contacts only, as above |
includeMetaAds / includeGoogleAds | The matching ad check | Yes, per completed check as described in the table above |
onlyRunningAds on its own | Both the Meta and the Google ad check | Yes. Every saved business is, by definition, running ads on at least one source, and each completed Meta or Google check on a saved business is billed at the ad prices above, even though you did not tick the ad options |
onlyRunningAds with includeMetaAds or includeGoogleAds | Only the source(s) you selected | Yes, for the selected source(s) only |
onlyWithWebsite, onlyWithoutWebsite | Nothing extra | No |
Filters never charge for businesses they remove. With a filter on, the Actor looks at a bounded amount of extra places (roughly up to three times maxResults) to fill the shortfall, and you pay only for the saved rows.
What is never billed as an add-on unit: reviews that are only a photo or Street View link, a Google interface label or a continuation token; review highlight snippets without an author; email addresses that are neither on the business's own website domain nor on a free email provider (they are returned free); email addresses from placeholder or template domains, and malformed addresses glued to a real one in a page's visible text (a label or phone number stuck in front of a mailbox, a city or state name stuck in front of another mailbox of the same business such as austinhello@ next to hello@, or a plain word stuck in front of a bare label mailbox; an address the site itself links or declares, such as skininfo@ in its structured contact data next to info@, is a real mailbox and is kept; two genuinely different mailboxes such as events@ and privateevents@ are both kept); official accounts of site-builder and hosting platforms found in templates and footers (Wix, Squarespace, Shopify, GoDaddy, Weebly, Webflow, Duda, Luxury Presence and similar, unless the business is itself that platform); official accounts of booking, listing and ordering platforms (Booksy, Vagaro, Zocdoc, Calendly, OpenTable and similar) found on its pages; truncated or malformed social links; a social profile that is the business's own Maps website; contact names that are page headings, platform names or legal-page words, that are taken from a page footer, a legal line, a menu, a list of links, a client testimonial or a one-off line of text, or that are not shaped like a person's name (an email or web address, a widget or form label, button text, a service or class heading, a lone first name with no role, email, phone or LinkedIn); a person who is not proven (no email, LinkedIn or direct phone of their own and no role title inside a real staff list, see People / contacts below); a name that is really a heading or a business (an institution or education line such as "Baylor University", a practice-area or service heading such as "Atlanta Teeth" / "Whitening Specialist" or "Dayton Family Dentist", the business's own name or structured-data record, a blog or page author named only in the page's metadata); a WhatsApp share button (api.whatsapp.com/send?text=..., or a send link that names no phone number); and a People unit for a name that was only guessed from its position next to an email or phone number.
If you set a maximum total charge for the run (maxTotalChargeUsd), it is never exceeded, and every business the run saves is saved with all its data. When a business's add-on units no longer all fit the remaining limit, that business is still saved complete and is the last one: only the add-on units that still fit are charged, in this order: reviews, emails, social profiles, people, Meta ad check, Google ad check; the rest are included free. The run then stops and its status message says "Saved N result(s); the run's charge limit (maxTotalChargeUsd) was reached, so the run stopped there. The last result was saved with all its data; M of its add-on unit(s) did not fit the limit and were included free of charge." When not even the next business's Result price fits, the run stops before it and says so. If the run timeout is also reached in the same run, the message keeps that text and adds how the timeout affected what was saved (for example that 3 of the 12 saved results were saved before their enrichment could finish, so their add-on fields may be empty); raise the run timeout as well as the limit for complete results. On a tight limit the Actor enriches only about as many businesses as the limit can pay for.
What's new
- Add-ons are charged per delivered unit (reviews, emails, social profiles, people, ad checks) instead of a flat fee per run. The four small per-run fees for place details, photos, place extras and popular times are gone.
google_maps_urlnow carries the business name and coordinates, so a saved result can be pasted back intostartUrlsand resolves to the same place.found_viais always filled, including for a plainqueryrun (the dataset schema description still says it is absent there; that text is out of date).- Businesses with accented or non-Latin names (for example Café or 渋谷カフェ) now get their full place details.
- Breaking change:
hourskeys are always the English weekday names (monday...sunday). The previous version used the result language for the keys (lundi...dimanche,montag...sonntag); update any code that reads localized keys. The opening-hours text itself stays in your language. - Ad intelligence is stricter about whose ads it reports. A Google advertiser must resemble your business or its website domain, or be the only advertiser on that domain and not a known agency, retail-media brand or platform, and a Meta advertiser must be tied to the business (its Facebook page, or an exact page name backed up by its website); otherwise the ad fields are
nulland not billed.google_ads_active_countcounts only ads shown in the past 30 days when Google returns a single page (see Ad intelligence below). - Reviews work again: full review lists up to
reviewsLimitfrom Google's own review list, a real newest-first sort, and rating / keyword filters that count toward the limit (see Reviews). No browser is launched and nothing is stored for reviews. - Place links are fetched directly and completely. Every supported form returns the place it points to with its full details: full URLs with or without
@lat,lng(also when a spreadsheet export or a redirect link percent-encoded the colons and slashes in them), links with Google's short/g/place id (including ids that contain an underscore, such as/g/1v76_180),?cid=,?ftid=,?q=place_id:andquery_place_idlinks, and maps.app.goo.gl share links. The run's status message names any requested place that could not be fetched or came back incomplete (when amenities are requested, also one whose amenities are still only accessibility items or empty after three lookups); a link that only marks a map position, with no place name or id, cannot be looked up and is named there too. A link that the run timeout stopped before it could be looked up is not named as unfetchable: the status message reports the run timeout instead. A link that really could not be fetched is still named in a run the timeout cut short: when such a run saved no row, it ends as failed with the run-timeout message, and the same message names the lost link. - Impossible filter combinations stop immediately with a clear message instead of running and returning nothing.
- Spending limits keep every saved business complete. When the limit is reached, the last business is saved with all its data (its add-on units that do not fit are free), the run stops cleanly and the status message says so (and names the run timeout too when it was also reached in the same run).
- Emails are billed only on the business's own website domain or a free email provider; agency, vendor, booking-platform and other-location mailboxes are returned free, and so are a landlord's mailboxes when the Maps website is the business's store page on a shopping centre's or landlord's site. An address the site declares itself (an email link, its structured contact data or a protected email address) is always returned.
- People / contacts are billed only for proven people. A person counts when they have their own email, LinkedIn profile or a direct phone printed in their own staff card, or a role title (or a professional prefix such as Dr.) inside a real staff list: a team or staff grid, a staff page, their own profile page in a staff directory, or the site's structured-data record for that person. Names are not taken from page footers, menus, lists of links, buttons, client testimonials, one-off text or headings: a page's title or main heading such as "Dayton Family Dentist" is never a person, also when it starts with a first name. An institution or education line ("Baylor University"), a practice-area or service heading ("Atlanta Teeth" / "Whitening Specialist"), the business's own structured-data record (also when the business is named after a person, the record is typed both Person and Organization, or the Maps name adds a city) and a blog author named only in a page's metadata are not staff either, and the business's own phone, email and social profiles are never put on a person. Whether a first name is common plays no part: "Chloe Martin" in a team grid is billed like anyone else. Cards that only look like a grid of people are not staff: products, classes, colours, software, awards, brands and packages ("Agreeable Gray" / "Designer Favorite"), office and "Areas we serve" cards ("Morgan Hill" / "Family Dentist near Morgan Hill") and department cards ("Guest Experience", "Patient Care"). Real staff on a page without a proper staff list can be returned but not billed (see People / contacts). More team-page layouts are read (Squarespace and WordPress team pages, staff directories that put a name heading above its role line).
- Social profiles are attributed more carefully. A Google Maps website that is a link inside a social account (
instagram.com/handle/tagged,facebook.com/page/posts) is read as that account's profile, so the place passessocialOnlyand the profile is free; a booking, listing or ordering platform's own accounts (facebook.com/booksypolska,instagram.com/booksybiz) are no longer returned or billed as the business's. - A profile that a site links through one of its tabs is returned as the profile itself.
youtube.com/channel/<id>/videos,youtube.com/@handle/featured,instagram.com/<handle>/reels,x.com/<handle>/media,facebook.com/<page>/aboutandlinkedin.com/company/<name>/aboutgiveyoutube.com/channel/<id>,youtube.com/@handle,instagram.com/<handle>and so on, billed once with the profile; 1.11.31 returned the tab address as written. A link to a single Instagram post or reel, X post, TikTok video or YouTube video is not a profile. A Facebook link to a post, photo, video or event of a page, Facebook's old/pg/<page>/...form and a page tab such asmenu,shoporjobsgive the page's profile (facebook.com/<page>), billed once with it, and a Facebook link that names no page (facebook.com/events/<id>,photo/?fbid=) is not returned. A tab on a YouTube vanity address (youtube.com/<name>/videos) givesyoutube.com/<name>. - A WhatsApp share button is not the business's WhatsApp.
api.whatsapp.com/send?text=...(a "share this page" widget) and anysendlink that names no phone number are no longer returned or billed; 1.11.31 returned them as the business's WhatsApp. A business number (wa.me/<digits>,send?phone=<digits>) or short link (api.whatsapp.com/message/<code>) is kept and billed as before. - Opening hours of businesses that hide their address (plumbers, locksmiths and other service-area businesses) are returned, and so is their phone when the place details are its only source. Before,
hourswas empty for them (also in 1.11.31) although Google shows it; theiraddressstays empty because Google does not publish one. - Rows saved with basic data only are named in the status message, with their number, instead of ending without a message.
- Meta sample titles that were raw catalog templates such as
{{product.name}}now read "No title available for this ad". - A custom
proxyUrlis tested with up to 3 attempts before the run starts, so one dropped connection no longer fails the run. - Run logs no longer contain proxy passwords or API tokens.
What does it do?
- Searches Google Maps for your query and covers the whole area with a grid search, finding businesses that a single search page misses.
- Extracts a clean record for every business — the full place profile.
- Optionally enriches each business with deeper place data, full reviews, website contact data, the people who work there, and live ad-spend signals.
Features
- 🗺️ Comprehensive place data — name, address, phone, website, rating, review count, category list, coordinates, opening hours, price level, photos, amenities, Plus Code, permanently-closed status.
- 🎯 Precise targeting — search by a plain query, a batch of queries, Google Maps URLs, or a custom area (lat/lng + radius, or a GeoJSON polygon).
- ⭐ Reviews — up to
reviewsLimitreviews per place from Google's own review list, newest first or in Google's relevant order: text, rating, date and author plus owner responses, reviewer stats, language and review photos, with rating / keyword filters. - 📩 Lead enrichment (experimental) — emails (MX-validated) and social profiles harvested from each business's own website.
- 👤 People / contacts (experimental) — the actual people at a business with their own name, title, email, phone and LinkedIn.
- 📣 Ad intelligence (experimental) — detect which businesses are actively running ads on Meta (Facebook/Instagram) and Google (Search/YouTube/Display), with sample creatives.
- ⚡ HTTP-only — no browser is launched, reviews included. Residential proxy remains the reliable default for Maps traffic.
Quick start
Just enter what you'd type in the Google Maps search box — include the location:
restaurants in New York
dentists in Miami, FL
coffee shops 90210
That's it. Everything else has a sensible default. Turn on the optional layers below only when you need them.
Targeting (how to choose what to scrape)
You can target in four ways — use whichever fits:
| Input | Use it for |
|---|---|
query | A single search, e.g. plumbers in Chicago. The location is auto-detected. |
searchQueries | A batch of searches in one run, e.g. ["dentists in Miami", "dentists in Orlando"]. Results are de-duplicated across queries. |
startUrls | Direct Google Maps URLs (place or search URLs). |
customGeolocation | A custom area — either a point + radius { "lat": 25.77, "lng": -80.19, "radiusMeters": 1500 } or a GeoJSON Polygon / MultiPolygon (coordinates in [lng, lat] order). |
If you provide searchQueries or startUrls you can leave query empty. A customGeolocation only says where to search, so it needs query as well (the category to look for): when the area is the only target and query is empty the run fails at once with a message, and when searchQueries or startUrls are given the area search is skipped with a warning and those run as usual. A Google Maps place link can be pasted in any common form: the full URL from your browser's address bar (with or without its @<lat>,<lng> part, and also when a spreadsheet export or a redirect link percent-encoded its colons and slashes), a maps.app.goo.gl share link, a ?cid= or ?ftid= link, a ?q=place_id: or query_place_id link, or the google_maps_url from an earlier result; a link that holds only a /g/ id needs its name and coordinates (the full URL) to be found. A requested place that could not be fetched, or that came back with incomplete details, is named in the run's status message, also when a spending-limit or timeout status is shown (a link that the run timeout stopped before it could be looked up is not named: the status message reports the timeout). A link that only marks a map position (for example .../maps/place/40.6892494,-74.0445004, no place name or id) cannot be looked up and is named there as well.
Output
Each business is one dataset item. Base fields (always present when Include Place Details is on):
{"name": "Joe's Pizza","address": "7 Carmine St, New York, NY 10014","phone": "+1 212-366-1182","website": "https://www.joespizzanyc.com","rating": 4.5,"reviews_count": 12847,"category": "Pizza restaurant","categories": ["Pizza restaurant", "Italian restaurant"],"latitude": 40.7304,"longitude": -74.0022,"place_id": "ChIJr3k0v6VZwokRPCxBJnIcdTA","google_maps_url": "https://www.google.com/maps/place/Joe%27s%20Pizza/@40.7304000,-74.0022000,15z/?q=place_id:ChIJr3k0v6VZwokRPCxBJnIcdTA","hours": { "monday": "10 AM-2 AM", "tuesday": "10 AM-2 AM" },"price_level": "$10-20","photos": ["https://lh3.googleusercontent.com/..."],"amenities": ["Outdoor seating", "Takeout", "Wheelchair accessible entrance"],"plus_code": "76QXQR66+RC"}
google_maps_url includes the business name and its coordinates, so you can paste it back into startUrls; it falls back to the plain ?q=place_id: form when a result has no usable coordinates. hours keys are always the English weekday names (monday … sunday) whatever the result language; the hour text itself stays in the result language. This is a breaking change for non-English integrations: the previous version keyed hours by the local weekday name (lundi … dimanche in French, montag … sonntag in German). Only when Google sends no usable day number for a day does the lower-cased local day name remain the key. found_via says which target produced the row (query for a single plain query, query: <text> for each searchQueries entry, startUrl place, startUrl search or customGeolocation). It is filled on every row, although the field's description in the dataset schema still says it is absent for a plain single-query run.
Additional fields appear when the matching option is enabled: reviews, permanently_closed, popular_times, the lead-enrichment columns (email, emails, facebook … whatsapp, website_reachable), the people columns (business_lead, contacts), and the ad-intelligence columns (meta_ads_*, google_ads_*). Anything not found is null — the base scrape is never affected.
Input reference
Results & detail
| Field | Type | Default | Description |
|---|---|---|---|
maxResults | integer | 100 | Max businesses to extract (0 = unlimited). |
language | string | en | Two-letter result language (Google hl). |
zoom | integer | 13 | Search granularity (1–21). Values 1–10 share the widest, coarsest bounded grid; 11–15 progressively refine it; 16–21 share the finest bounded grid. |
includeDetails | boolean | true | Fetch full place details (hours, phone, website, price level, photos…). |
Complete place data
| Field | Type | Default | Description |
|---|---|---|---|
includePhotos | boolean | true | Include up to 10 photo URLs per place. |
includePlaceExtras | boolean | true | Include amenities, Plus Code and permanently-closed status. |
includePopularTimes | boolean | false | Experimental: Google's place data almost never includes the popular-times histogram, so popular_times is null for virtually all places. No separate fee. |
Reviews
| Field | Type | Default | Description |
|---|---|---|---|
includeReviews | boolean | false | Extract reviews for each business from Google's review list. Billed per delivered review that has an author. |
reviewsLimit | integer | 5 | Max reviews per business (up to 1,000). Counts reviews that pass the rating and keyword filters. |
minReviewRating | integer | 0 | Keep only reviews rated ≥ this (1–5; 0 = off). Applied while paging; with a filter on, at most the first 2,000 reviews of a place are scanned (if that limit is reached before reviewsLimit matching reviews are found, the status message says so). |
reviewKeyword | string | — | Keep only reviews whose text contains this keyword. Applied while paging; with a filter on, at most the first 2,000 reviews of a place are scanned (if that limit is reached before reviewsLimit matching reviews are found, the status message says so). |
reviewsSort | select | newest | newest (most recent first) or relevant (Google's default order). |
Each review includes review_id, author, author_photo, rating, date, text, and (when present) owner_response_text, owner_response_date, reviewer_review_count, reviewer_is_local_guide, review_language and review_images. review_translated is always null. A review is billed only when it has an author, a 1–5 rating and text or a date. If a place's review list cannot be fetched, stops part-way with nothing usable, or the run timeout comes first, the few reviews embedded in the place data are returned instead when they are available (filtered and capped at reviewsLimit; highlight snippets without an author are returned but not billed) and the status message says so. A place that Google itself shows without any review (no rating and no review count) is not asked for its review list, so it costs no review request; billing is unchanged, only delivered reviews are billed.
Lead enrichment (experimental add-on)
Visits each business's own website (plus contact/about/imprint pages) to extract contact data. Off by default; never changes the base scrape.
| Field | Type | Default | Description |
|---|---|---|---|
includeEmails | boolean | false | Find email addresses (deduplicated and MX-validated). Billed per delivered address on the business's own website domain or a free email provider; other addresses are returned free. A business whose Maps website is a social profile, directory, site-builder, booking, listing or ordering page, or a store page on a shopping centre's or landlord's site, has no own domain, so only its free-mail addresses are billed. |
includeSocials | boolean | false | Find social profiles (Facebook, Instagram, LinkedIn, X/Twitter, YouTube, TikTok, WhatsApp). The official accounts of site builders and of booking, listing and ordering platforms are not returned as the business's. A link to one of a profile's tabs (for example youtube.com/channel/<id>/videos) is returned as the profile itself, and so is a Facebook link to a post, photo, video or event of a page (its page profile); a link to a single Instagram post or reel, X post, TikTok or YouTube video is not a profile, a Facebook link that names no page (facebook.com/events/<id>, photo/?fbid=) is not returned, and neither is a WhatsApp share button (api.whatsapp.com/send?text=...). |
emailOnly | boolean | false | Keep only businesses with an email (implies includeEmails, so emails on the business's own website domain or a free email provider are billed per delivered address; other addresses are returned free). |
socialOnly | boolean | false | Keep only businesses with a social profile (implies includeSocials, so social profiles are billed per delivered URL). Can be combined with onlyWithoutWebsite to find businesses whose only listed website is a social profile or a link inside one (such as instagram.com/handle/tagged, read as that account's profile). |
onlyWithWebsite | boolean | false | Keep only businesses that have a real website (including a page on a site builder such as wixsite.com or square.site, but not a social profile or directory listing). |
onlyWithoutWebsite | boolean | false | Keep only businesses without a website (prospects for web/design agencies). A page on a site builder (for example wixsite.com, square.site, weebly.com, business.site) counts as a website; a social profile or directory listing does not. It cannot be combined with onlyWithWebsite, emailOnly or onlyWithPersonnel; those inputs stop the run immediately with a message. |
maxPagesPerSite | integer | 4 | Advanced: max pages crawled per website (1–10). |
People / contacts (experimental add-on)
| Field | Type | Default | Description |
|---|---|---|---|
includePersonnel | boolean | false | Extract the people at each business (name, title, their own email/phone/LinkedIn). The business's own phone, email and social profiles stay in business_lead. |
maxContactsPerBusiness | integer | 10 | Max contacts per business (highest-confidence first). Within a confidence tier, a contact that earns the People charge comes first, then a contact with personal-name evidence of its own (Dr. or credentials, its own email or LinkedIn, its own profile page, a job title in structured data) before one that only its page vouches for; when the limit would leave out every person who earns the People charge although one passes personnelMinConfidence, that person is kept first. So a small limit keeps the clearest people. |
personnelMinConfidence | select | low | Drop contacts below this tier (low/medium/high). |
onlyWithPersonnel | boolean | false | Keep only businesses with at least one named contact at or above personnelMinConfidence (implies includePersonnel; billed as People / contacts only). A contact with only an email and no name does not count. A card with only a name (no role, no contact) is at most low confidence, so with high (or medium) it can leave a business without a contact, and with low it keeps the business but creates no People charge. A named person that nothing proves (see below) also creates no People charge. |
People / contacts are billed once per saved business, and only when at least one person delivered for it is proven to be a real person. A person is proven in one of two ways. Their own contact details: their own email address (it spells their name: their first name, surname or both, a first initial with the surname, three or more initials such as mls@, or a nickname such as bob@ for Robert), their own LinkedIn profile (the address spells their name), or a direct phone number printed in their own staff card. The business's Maps phone, an office, branch or toll-free line and a number shown on several people's cards are the business's, never a person's. Or a role title inside a real staff list: a job title (or a professional prefix such as Dr., Dra., Prof., or credentials such as DDS) for a person who is listed in a team or staff grid (two or more person cards side by side under a team or staff heading such as "Our Team", "Our Doctors" or "Our Leadership", in a section the site marks as its team, or each card with Dr., credentials, the person's own email or a link to their own page; one practitioner card with Dr. or credentials under such a heading counts too), on a staff or team page that lists two or more people (one card alone there counts only with the person's own contact details), on their own profile page in a staff directory (its address names the person and nothing else, apart from credentials such as -dds), or in the website's own structured-data record for that person (a schema.org Person that is only a person, is not the business itself, is not a blog or page author entry, is not an event performer, course instructor, article subject, product brand or page reviewer, and states the role). Inside a staff list, clinical and trade roles such as Orthodontist, Chiropractor, Veterinarian, Pharmacist, Groomer or Master Plumber count as role titles, and so does Dr. on its own. Whether a first name is common or unusual plays no part: Chloe Martin, Siobhan Murphy or Aoife Byrne in a team grid are billed like anyone else. Nothing else creates the charge: names taken from page headings (a page title or main heading such as "Dayton Family Dentist", also when it starts with a first name), one-off text in a paragraph, a footer or a menu, client testimonials and reviews ("What our clients say", or any card that quotes a client before their name), cards of products, classes, colours, software, awards, brands or packages ("Vinyasa Flow" / "Lead Instructor", "Agreeable Gray" / "Designer Favorite"), office and service-area cards ("Warner Robins" over its street address, "Morgan Hill" / "Family Dentist near Morgan Hill", a card whose role line names somebody else such as "Lead Dentist: Dr. Amy Park"), department cards ("Guest Experience", "Patient Care", "Human Resources"), blog or page author entries, the business's own structured-data record (also when the business is named after a person, such as "Tony Lopez Photography", or the record is typed both Person and Organization), an institution or education line ("Baylor University"), a practice-area or service heading split into a name and a title ("Atlanta Teeth" / "Whitening Specialist", "Teeth Whitening" / "Dentist"), a business name, and a name that was only guessed from sitting next to an email or phone number. Emails and social profiles found while looking for people are still returned in the row, but they are billed as Emails or Social profiles only when you turn on includeEmails / includeSocials (or emailOnly / socialOnly) yourself.
Each business gains business_lead (org-level email/phone/socials) and contacts[] (people with name, title, email, phone, linkedin, confidence, tier, attributed, source_urls). A person's fields carry only their own details: the business's phone, email and social profiles (its Maps phone, the site's header, footer and structured-data contacts, office mailboxes such as info@, toll-free numbers, and anything shown on several people's cards) stay in business_lead and are never put on a person, and a person found without own contact details has empty personal contact fields. Names come from team cards and staff or provider grids, staff lists whose items link to each person's own page, captions such as Name | Role, a name heading with its role on the next line on a staff-directory page (two or more needed), and structured data, and never from a page footer, a legal or licence line, a menu, a list of links, a button, a call to action, a client testimonial or a one-off line of text; a name in a footer counts only next to that person's own mailbox (a solo agent's name, role and mailbox). A person keeps a mailbox only when it is theirs (it spells their name, or it is a personal mailbox printed on their own card and on no other person's), and a branch or office phone on a location card goes to business_lead, and so does a billing, insurance, fax, front-desk, main, office or toll-free line printed in a person's card or block (a line labelled direct, mobile or cell stays theirs); when a person's card holds two unlabelled numbers, neither is put on the person. A role line is read without its label ("Title: Lead Dentist" gives "Lead Dentist"), and a line under another label ("Phone: ...", "Insurance: ...") is not a role. attributed is true only for a proven person: someone with their own email or LinkedIn, or someone vouched for by a team grid, a staff page, their own profile page or the site's structured-data record for that person; a professional prefix on the name (Dr., Dra., Prof.) counts as the role, a courtesy title (Mr., Ms.) does not. attributed is false, and the contact is low confidence and not billed, for a card with only a name (no role, no contact), a name guessed from an email address, a name that merely sat next to an email or phone number that is not theirs, a name from a page heading or a single paragraph, a page or article author named only in the page's metadata, and a person known only from a structured-data record typed as a business (a lawyer typed Person and Attorney with no email or LinkedIn of their own and no place in a team list). Headings, labels, navigation items, street names and email or web addresses are not returned as names (a widget or page label such as "Close Estimated Monthly Payment" or "FAX Attention" is dropped, or kept without a name when it carries an email). Institutions and education lines ("Baylor University") and practice-area and service headings split into a name and a title ("Atlanta Teeth" / "Whitening Specialist", "Teeth Whitening" / "Dentist"), also ones whose name opens with a city, are not returned as people either: a page's title or main heading is never a source of names, and neither are words cut from a longer heading ("Dayton Family" of "Dayton Family Dentist"), whatever the first word is. The business's own structured-data record is the business, not a person: a record named like the business or the site (also when the business is named after a person, when the Maps name adds a city such as "Paul Davis Restoration of Austin", or when the name ends with a trade or company word such as Restoration, Photography, Plumbing or Group), or typed both Person and Organization, gives its phone, email and profiles to business_lead unless it carries the person's own email or LinkedIn; a one-person site's record with a job title and that email or LinkedIn is that person. A lone first name with no role, email, phone or LinkedIn is never billed (it is left out, or returned as a low-confidence, unattributed hint). Precision comes before recall for billing: some real staff on pages without a proper staff list are returned but not billed; the business is still billed when another person on the site is proven. With maxContactsPerBusiness, contacts are ordered by confidence tier; within a tier, people who earn the People charge come first, then the person the business is named after, then those with personal-name evidence of their own (Dr. or credentials, own email or LinkedIn, own profile page, a structured-data Person with a job title), then those with a common given name (a hint for the order only, never for billing), then those vouched for only by their page; when the limit would leave out every person who earns the People charge although one passes personnelMinConfidence, one of them is kept first. So a small limit keeps the clearest people.
Ad intelligence (experimental add-on)
| Field | Type | Default | Description |
|---|---|---|---|
includeMetaAds | boolean | false | Check the Meta Ad Library — is the business running Facebook/Instagram ads? With sample creatives. |
includeGoogleAds | boolean | false | Check the Google Ads Transparency Center — is it running Search/YouTube/Display ads? |
onlyRunningAds | boolean | false | Keep only businesses currently running ads. With neither ad option ticked it checks both Meta and Google and bills each completed check on the businesses it saves; with includeMetaAds or includeGoogleAds it checks and bills only the selected source(s). |
adCountry | string | US | Two-letter country code for where the ads are shown (applies to both sources). UK is treated as GB. |
Whose ads are reported. A null ad result means "could not be tied to this business" and is never billed.
- Meta: the advertiser must be the business's Facebook page (its id or page address, found on the business's website or Maps website) or an exact page-name match that the business's own website or Facebook link backs up. A search that returns no ads is
meta_ads_running: null, notfalse;falsemeans the advertiser was identified and has no active ads. A sample whose headline is an unrendered catalog template (such as{{product.name}}) reads "No title available for this ad". - Google: the Transparency Center lists every advertiser whose ads link to a website domain, agencies and retail-media brands included. The advertiser is used when its name equals the business name, or when a distinctive word of its name (legal and generic words such as Inc, LLC, Holdings, Brands, Media, Marketing and Dental do not count) appears in the place name or resembles the website's domain name; legal names such as "Target Brands, Inc", "Best Buy Purchasing LLC" and "Toothbar, PLLC" qualify. An advertiser whose name does not match is still used when it is the only advertiser on the domain (two accounts with the same name count as one), Google's first page shows at least half of the domain's ads, and it is not a known agency, retail-media brand or ordering/booking platform: this attributes owner-run personal accounts and parent companies. A lone agency, retail-media brand or ordering-platform tenant stays
nulland is not billed, and so does a parent company or personal name that shares the domain with another advertiser or sits on a large retailer or marketplace domain, even when it is the real advertiser. Businesses whose website is a site-builder or online-ordering page (wixsite.com,square.site,menufy.com,chownow.comand similar) get no Google lookup. - Google fields: once an advertiser is accepted,
google_ads_advertiser,google_ads_advertiser_idandgoogle_ads_samplescome from that advertiser (which can differ from the previous version, because that named the advertiser with the most ads on the page). As in the previous version,google_ads_running,google_ads_last_shown,google_ads_formatsandgoogle_ads_active_countcover every ad on the first page Google returns for the business's website domain, including ads the business shows through a second account or an agency.google_ads_runningis true when one was shown in the past 30 days.google_ads_active_countis a minimum: on a single-page answer it counts the ads shown in the past 30 days (0when none was; the previous version counted every ad on the page, including old ones, so the number can be lower), and when Google reports more pages and ads are running it is the number of ads on the first page, as before.
Proxy
| Field | Type | Default | Description |
|---|---|---|---|
proxyConfiguration | object | Apify Residential | Search and blocked full-detail recovery use the production-compatible Residential proxy group by default. |
proxyUrl | string | — | Advanced: a single custom HTTP proxy used instead of Apify Proxy. It is tested before the run starts with up to 3 attempts, each on a fresh connection, so one dropped connection does not stop the run. The run stops with a message and collects nothing when the proxy rejects its credentials (HTTP 407) or every attempt fails the same way. |
Recipes
Web-design leads (businesses with no website):
{ "query": "restaurants in Austin, TX", "onlyWithoutWebsite": true, "includePersonnel": true }
Warm leads — businesses advertising right now, with their contacts:
{ "query": "dentists in Miami, FL", "includeMetaAds": true, "includeGoogleAds": true,"onlyRunningAds": true, "includeEmails": true, "includePersonnel": true }
Reputation / review mining (5★ reviews mentioning a topic):
{ "query": "hotels in Paris", "includeReviews": true, "reviewsLimit": 50,"minReviewRating": 5, "reviewKeyword": "breakfast" }
Whole-area sweep with a custom radius:
{ "query": "gyms", "customGeolocation": { "lat": 34.0522, "lng": -118.2437, "radiusMeters": 5000 } }
Tips
- Include a location in your query (
plumbers in Chicago); plain phrases likeplumbers Chicagoorcoffee 90210also work. - Keep Apify Proxy enabled — the Actor uses the Residential proxy group by default for reliable search and detail retrieval.
- Start with a small
maxResultsto test, then scale up. - The optional enrichment / ad-intelligence layers add run time and are off by default — they never change the base scrape.
Limitations
- Results depend on what Google Maps returns for your query and location; some businesses have incomplete data.
- Reviews come from Google's review list. Reviews are read from Google's own review list over HTTP. With a rating or keyword filter, at most the first 2,000 reviews of each place are scanned; if that scan limit is reached before
reviewsLimitmatching reviews are found, the status message says so. A place whose whole list holds fewer matching reviews thanreviewsLimitsimply returns them, with no message. A place that Google shows without any review is not asked for its list. If a place's list cannot be fetched after retries, its embedded reviews are returned and the status message says how many saved places were affected (a more important status, such as a spending-limit or timeout stop, takes its place). A list that stops part-way keeps the reviews fetched before that, and a run that reaches its timeout keeps the reviews it has. When the list cannot be fetched for several places in a row, the Actor pauses it for a minute at a time and returns the embedded reviews meanwhile, so a Google-side outage cannot stall the run. popular_timesis experimental: Google's place data almost never carries it, so expectnullfor virtually all places.- If Google does not return a business's full place details, even after retries, the saved row contains only the basic data from the search result (for example name, address, coordinates, rating and category) and the detail-only fields such as opening hours, amenities and photos are empty. When
maxResultsis set, such a row is replaced by another business whenever one is available; otherwise it is saved and billed like any other result. A place you request by URL is never dropped. The run's status message then says how many saved rows have basic data only. - The Meta check returns
meta_ads_running: null, notfalse, for a business whose advertiser page cannot be identified in the Ad Library (including a business that has never advertised, and a business that advertises but whose Facebook page cannot be found);falsemeans the advertiser was identified and has no active ads. The Google check needs the business's own website domain and an advertiser whose name resembles the business or its domain (or that is the only, non-listed advertiser on that domain); businesses with no website, a social profile, a directory listing, a site-builder or online-ordering page as their website getnull, and so does a business whose parent company or owner account shares its domain with another advertiser, or whose only advertiser is a known agency, retail-media brand or platform. Anullad result is never billed. - A row made from a place link (
startUrls) can carry a few review highlights inreviewseven whenincludeReviewsis off. They have no author and are never billed. - The agency, retail-media and ordering-platform names that the Google check refuses are a finite list: an agency the Actor does not know that is the only advertiser on a small business's domain can still be attributed to that business.
- People are read best from team pages that show each person as a card (name, role, optional contact) and from structured data. A page that gives each person a long biography section instead of a repeated card layout can come back with no named person; the addresses found there are then returned without a name or at business level. A doctor listed only as
Name, MD(noDr.and no link to a profile page) is skipped, because a bare MD, MA, MS or DC can also be a US state or city label, and a name that appears only in a photo's alt text is not matched with the card's phone and social profiles. A business that lists its branches or clubs as cards with a mailbox each that spells the place (saintjohn@, a gym chain's location page) can still return place names as people and one People unit when a card shows neither an address with a state or ZIP code nor a "near / in / serving - A store page on a landlord's, mall's or directory's site is recognised by its address (a folder such as
/merchant/,/tenant/,/stores/,/shop/or/directory/before the business's page) on a site whose own name does not name the business. A landlord page without such a folder is treated as the business's own site and its mailboxes are billed; a business's own mailbox on a landlord's domain is returned but not billed. - The social accounts of a booking, listing or ordering platform are never attributed to a business, also when the business is itself such a platform's own listing; the fields stay empty. A link to one post, photo, video or event of a Facebook page, Facebook's old
/pg/<page>/...form and a page tab such asmenu,shop,jobsoroffersgive that page's profile (facebook.com/<page>), billed once with it as one social profile; a profile link found elsewhere on the site is preferred, but a site that links only to someone else's post can return that other page as its Facebook. A Facebook link that names no page (facebook.com/events/<id>,photo/?fbid=,permalink.php,fb.me/e/<code>) is not returned. A tab on a YouTube vanity address (youtube.com/<name>/videos) givesyoutube.com/<name>(1.11.31 returned it as written). customGeolocationis searched as a bounding box: aMultiPolygonbecomes one box covering all its parts, a point and radius becomes a box around the point, and a geometry the Actor does not understand (for exampleCircle) is ignored with a warning, so rows can come from outside the shape you drew.- The Apify run option
maxItemsis not enforced by the Actor; usemaxResultsand a maximum total charge to cap a run. - A place link that holds only a
/g/id needs its name and coordinates (the full URL from your browser's address bar) to be found. A requested place that cannot be fetched is skipped and named in the run's status message; one that Google answers only partly is returned with what was found and named as incomplete. A place that the run timeout stopped before it could be looked up is not named as unfetchable: the status message reports the run timeout (run again with a longer timeout), and it does not list the links that were not reached. A link that really could not be fetched is still named when the timeout cut other links short; if the run saved no row for that reason it ends as failed with the run-timeout message, which names the lost link too. - The Google Ads Transparency Center does not expose the text of search ads (only creative previews/metadata).
- For ad lookups outside the US, set
adCountryto the business's country (e.g.GB,DE) for accurate results.
Frequently asked questions
Do I need a Google Maps API key?
No. This is a scraper, not the Google Places API — there is no API key, no OAuth and no Google Cloud project. It talks to Google Maps' public endpoints over HTTP, so you just enter a search query and run it.
Can it scrape emails and contact details from Google Maps?
Yes, with the optional experimental add-ons. Turn on includeEmails, includeSocials or includePersonnel and the Actor visits each business's own website to find emails (MX-validated), social profiles and the people who work there. Off by default and billed separately (emails only on the business's own website domain or a free email provider); includePersonnel bills people/contacts only, not the emails and socials it returns unless you also turn those on.
Does it extract Google Maps reviews?
Yes. Enable includeReviews to receive up to reviewsLimit reviews per place (all of them when a place has fewer), newest first or in Google's relevant order, with author, rating, date, text, owner replies, reviewer statistics, language and photos, plus minReviewRating and reviewKeyword filters. Reviews are billed per delivered review that has an author, a rating and text or a date. They are an optional layer — the base place scrape runs without them.
Is it legal to scrape Google Maps?
The Actor reads only Google's public, unauthenticated listings — the same data any visitor sees. Scraping public data is broadly treated as lawful (e.g. hiQ v. LinkedIn), and Google's Terms are civil, not criminal. Use the output within applicable privacy laws.
How many businesses can I get from one search?
The Actor runs a grid search over the whole area, so it finds far more places than a single Google Maps page (which caps out quickly). Set maxResults to your target (0 = unlimited); when filters or unfetchable rows leave you short, the Actor looks at a bounded amount of extra places to fill the gap. Split very dense areas with a custom area or batch queries.
How is this different from the Google Places API?
No API key, no OAuth and no per-call quota. Each saved place has a base result price; optional reviews, emails, socials, personnel and completed ad checks have the separate unit prices shown above. The grid search returns more places than the API's per-search cap. You also get optional reviews, emails, contacts and ad intelligence the API does not provide.
Can I scrape a custom area, radius or Google Maps URL?
Yes. Target with a single query, a batch of searchQueries, direct Google Maps startUrls, or a customGeolocation (point + radius, or a GeoJSON polygon). Use whichever fits — you can leave query empty with searchQueries or startUrls, but a customGeolocation needs query for the category to search.
Can I find businesses without a website, or ones running ads?
Yes — both are built for agencies. Use onlyWithoutWebsite to find web-design prospects, and the ad-intelligence add-ons (includeMetaAds, includeGoogleAds, onlyRunningAds) to keep only businesses currently advertising on Meta or Google. onlyRunningAds on its own runs, and bills, both ad checks for the businesses it saves.
What formats can I export to?
Every run saves to an Apify dataset you can download as CSV, Excel (XLSX), JSON, JSONL or HTML, or pull via the Apify API and the official Python / JavaScript clients — ready for spreadsheets, CRMs and data pipelines.
Other Actors by buff_pineapple
- Google Reviews Scraper — every Google Maps review for a place, with reviewer details and owner replies.
- Meta Ad Library Scraper — spy on Facebook & Instagram ads (creatives, spend, landing pages), no login.
- Yelp Scraper — Yelp business profiles and reviews.
- Trustpilot Reviews Scraper — every Trustpilot review plus the company TrustScore.
- Shopify Store Leads Scraper — detect Shopify stores and pull product + contact leads.
- Zillow Listings Scraper — homes for sale, rent and sold with full details.
- TikTok Top Ads Scraper — TikTok Creative Center top-ads intelligence.
Privacy & anonymous usage information
This Actor records a small amount of anonymous, aggregate usage information so the maintainer can understand which features are used and keep the Actor reliable. At the end of a run it may record, in the run's own storage on the Apify platform: the run ID, build number, run status/outcome, how the run was started, the number of results collected / pushed / filtered / skipped, and which optional features (reviews, emails, socials, personnel, ad-intelligence, etc.) were enabled, plus non-personal numeric settings (such as the result limit, reviews limit, language, and ad country).
Usage metadata recorded: when Apify provides an account ID, the Actor stores a truncated salted pseudonymous hash so distinct runs can be counted. It also stores a paying-status flag and per-feature counters. The salt is present in the source, so this hash should not be treated as cryptographic anonymization. No search query, geolocation/targeting input, scraped business data, contact details, raw account ID, IP address, or user-agent is stored in this record. Metadata stays in Apify key-value storage and is not sent to an external service. Recording is best-effort and fail-open; a storage failure does not stop or slow the run.
Credentials. No Google login is used and nothing is stored for reviews. Run logs hide proxy passwords and API tokens.