If you sell websites, local SEO or review management to small businesses, your best leads are easy to describe:
- a plumber with no website, so customers find them only through their Maps listing,
- a dentist whose Google Business Profile is unclaimed, so anyone can suggest edits,
- a restaurant rated below 4 stars, or a salon with fewer than 10 reviews.
Describing them is easy. Finding them is harder, because most Maps tools return every business in an area, and you pay for all of it before throwing most away. This post shows how to build these segments in Python, where the official API falls short, and how to keep the list clean of personal data.
Option 1: the Google Places API
The Places API (New) has a Text Search endpoint that returns businesses for a query like "plumber in Austin", and you choose the fields with a field mask:
import os, requests
def places(query, page_token=None):
r = requests.post(
"https://places.googleapis.com/v1/places:searchText",
headers={
"X-Goog-Api-Key": os.environ["GOOGLE_MAPS_API_KEY"],
"X-Goog-FieldMask": "places.displayName,places.websiteUri,places.rating,"
"places.userRatingCount,places.googleMapsUri,nextPageToken",
},
json={"textQuery": query, **({"pageToken": page_token} if page_token else {})},
timeout=30,
)
r.raise_for_status()
return r.json()
data = places("plumber in Austin, TX")
no_site = [p for p in data.get("places", []) if not p.get("websiteUri")]
print(f"{len(no_site)} of {len(data.get('places', []))} have no website")
It's official and live, and good for small jobs. Its limits for lead lists:
- A search returns up to 60 results across pages. Covering a whole metro means tiling the map into many small searches and deduping.
- You filter after you fetch. If 10% of plumbers have no website, you pay for the other 90% too. Website and rating fields are on the more expensive request tiers.
- No claim status. The API doesn't tell you whether a profile is claimed, which is the segment local SEO agencies care about most.
- "Website" is sometimes a Facebook page. You'll want to treat social-only sites as "no website".
SOCIAL = ("facebook.com", "instagram.com", "yelp.com", "linktr.ee")
def has_real_website(url):
return bool(url) and not any(s in url.lower() for s in SOCIAL)
Option 2: filter on the database side
Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.
Google Maps Leads: No Website, Unclaimed Profiles (steady-scrape/google-maps-business-segments) is an Apify Actor that runs the segment filters on the data provider's side. It queries a regularly refreshed, licensed database of Google Maps listings, so you only get (and only pay for) the businesses that match. You can filter by no website (or social-only), unclaimed, rating below X, fewer than Y reviews, or first seen in the last N days, and combine them.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/google-maps-business-segments").call(run_input={
"categories": ["plumber"],
"coordinates": "30.2672,-97.7431", # Austin, TX
"radiusKm": 25,
"website": "noWebsite",
"claimStatus": "unclaimed",
"maxResults": 200,
})
leads = list(client.dataset(run["defaultDatasetId"]).iterate_items())
for b in sorted(leads, key=lambda b: -(b.get("reviewsCount") or 0)):
print(b["segmentTags"], b.get("rating"), b.get("reviewsCount"), b["googleMapsUrl"])
Here's what that input (with maxResults: 50) returned in our test run on November 1, 2026. The database query took about 1.2 seconds and the whole run about 4:
- 20 businesses matched in the database, all with no website and an unclaimed profile (18 plumbers and 2 businesses in related categories).
- Median review count: 3.5. 17 of the 20 had fewer than 10 reviews, and 4 had none at all, which makes for a good "get more reviews" pitch.
- Of the 16 with a rating, 6 were below 4 stars.
- Median record age: 38 days since the database last refreshed the listing (49 days at most).
Each record has the business name and categories, the full address with coordinates, placeId and a Google Maps link, hasWebsite and websiteIsSocialProfile, isClaimed, rating with a 1–5★ distribution, review count, opening hours, status, and segmentTags such as no-website, unclaimed, no-reviews or rating-below-4.
Using coordinates, not city names
Use coordinates plus a radius rather than a city name. Addresses use the suburb's own name, so "Austin" misses Round Rock and Pflugerville. Postal codes and whole countries work too.
Doing it responsibly
Lead lists from Maps can drift into personal data, especially for sole traders. Some rules we built in, and recommend whatever tool you use:
- No email addresses. The Actor drops scraped contact lists and masks emails found in descriptions. Business emails scraped from other sites are what gets cold outreach flagged as spam, or worse.
- No owner names, reviewer data or photos.
-
Phone numbers are optional. It's the number the business published on its own Google profile. Set
dropPhones: trueif you target one-person businesses in the EU, UK or Quebec, where that number can count as personal data. - Check before you pitch. Coverage isn't 100% of Google Maps, and a business can have a website Google doesn't show. Open the Maps link first.
- "First seen" isn't an opening date. It's when the provider first found the listing. For likely new businesses, combine first seen in the last 60–90 days with fewer than 5 reviews.
Data is a refreshed database, not a live Maps load. In our tests most active listings had been refreshed within the previous one to two months.
What it costs
| Event | Price |
|---|---|
| Run start (only if a business is returned) | $0.015 |
| Business, no segment filter | $0.002 ($2 per 1,000) |
| Business matching a segment filter (no website, unclaimed, rating below, few reviews, first seen) | $0.005 ($5 per 1,000) |
Those are Apify Free plan prices. Paid Apify plans get 10-25% off the per-business prices automatically. Apify adds a $0.00005 start fee. Runs that return nothing, invalid input and provider errors are free.
Examples:
- The 20-lead test above: $0.015 + 20 × $0.005 = $0.115 (that's what the run was billed).
- 100 plumbers with no website: $0.515.
- 1,000 dentists in a metro with no segment filter: $2.015.
- A monthly refresh of no-website leads in 5 cities at ~100 each: about $2.58 a month.
Because the filter runs before delivery, you pay only for the matches, however rare they are in the area.
Which to use
For a live check of a few dozen businesses, the Places API plus the has_real_website() filter above is fine. For area-wide segment lists, and especially for claim status, filtering on the database side is cheaper and faster. Either way, keep the list to business data, and look at each listing before you send a pitch.
Try it on the Apify Store: the prefilled example input runs the same no-website, unclaimed plumber search shown above.
Top comments (0)