DEV Community

Alex Rowan
Alex Rowan

Posted on Originally published at agent-run-agency-site.pages.dev

How to turn Google Maps Scraper output into a scored local SEO lead list

By Alex Rowan

Disclosure: Manta Media, the local SEO agency I work at, built the two Apify Actors linked near the end of this post (Google Maps Leads Scorer and Local SEO Bulk Site Auditor). Every step here also works in your own script, and the code is included.

A Google Maps scrape gives you a long list of businesses: names, phones, websites, ratings. What it doesn't tell you is which of them actually need help with their website or local SEO. If you do outreach for an agency or as a freelancer, emailing all 200 is a waste of everyone's time.

This walkthrough goes from a raw Maps scrape to a short, ranked list with one concrete talking point per business. You can do every step yourself in Python, or use an Actor for the scoring part.

Step 1: Scrape one niche in one city

Run Google Maps Scraper (compass/crawler-google-places) for a single category and location. Keep it to one location per run, as the Actor's own input form recommends.

{
  "searchStringsArray": ["plumber"],
  "locationQuery": "Beaverton, OR, USA",
  "maxCrawledPlacesPerSearch": 50,
  "language": "en"
}
Enter fullscreen mode Exit fullscreen mode

Each place in the output dataset includes, among many other fields, title, website, phone, totalScore (rating), reviewsCount, categoryName and city. Those seven fields are all we need. Note the run's dataset ID; you'll pass it to the next step.

Step 2: Decide what "needs help" means

These are the checks we use per business. Each one is cheap: one fetch of the homepage, plus robots.txt and an http:// redirect check.

  • No website at all. The clearest lead for a web designer.
  • No LocalBusiness structured data. No JSON-LD telling Google the business name, address and phone.
  • Title and meta description. A missing meta description, or a title over ~60 characters that gets cut off in results.
  • Phone and address on the homepage. Local visitors expect to see them, and Google reads them too.
  • HTTPS. A valid certificate, and http:// redirecting to https://.
  • Reviews vs the local median. 3 reviews means little on its own. Against a group median of 31 (from the sample run below), it's a concrete talking point.

Checking for LocalBusiness JSON-LD takes only a few lines of Python:

import json, requests
from bs4 import BeautifulSoup

LOCAL = {"LocalBusiness", "Plumber", "Dentist", "HVACBusiness", "RoofingContractor"}  # extend as needed

def local_schema(url):
    html = requests.get(url, timeout=(10, 20), headers={"User-Agent": "my-audit-bot/1.0"}).text
    soup = BeautifulSoup(html, "lxml")
    for tag in soup.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "")
        except ValueError:
            continue
        for d in (data if isinstance(data, list) else data.get("@graph", [data])):
            t = d.get("@type", [])
            if set(t if isinstance(t, list) else [t]) & LOCAL:
                return {"type": t, "name": d.get("name"), "telephone": d.get("telephone")}
    return None
Enter fullscreen mode Exit fullscreen mode

Compare the schema telephone with the phone shown on the page. A mismatch is a real NAP (name, address, phone) problem worth pointing out.

Step 3: Score and tier

Add the signals into one 0-100 opportunity score, where higher means a better prospect. These are the weights the Google Maps Leads Scorer documents in its README:

Signal Points
SEO gap: 100 minus the site's local SEO score, scaled up to 35
No LocalBusiness schema +10
No SSL (or no https redirect) +8 (+4)
No meta description / no title +5 / +4
No mobile viewport +5
No phone on the homepage / no street address (on the page or in schema) +4 / +3
Slow site (over 3 s to respond, or PageSpeed mobile under 50 when enabled) +5
Reviews below the batch median (scaled by how far below) up to +15
Rating under 4.0 (under 4.5) +7 (+3)
Public contact email found (only when findEmails is on) +10
No website 85 base
Website failed to load 45 base

Review and rating points are added on top of the base for no-website and site-down leads too. Scores are capped at 100. Tiers: hot is ≥ 50 or no website, warm is ≥ 30, cold is < 30. Tune the weights to whatever you sell. A web designer might weight "no website" and "no viewport" higher; an SEO agency might weight schema and reviews.

Be polite

You're visiting small-business websites, so keep the load trivial:

  • Respect robots.txt, and skip pages it disallows.
  • Use low concurrency (we use 3) and conservative timeouts (10 s connect / 20 s read).
  • Cap the download size per page, and use an identifiable User-Agent.
  • Run each site with a hard time limit. One pathological page shouldn't stall the whole batch.
  • Only collect emails a business shows publicly on its own site, and follow CAN-SPAM / GDPR / PDPA when you contact anyone.

Results from a real run

Here are real rows from a published sample run of the Scorer on Beaverton, OR plumbers (scored 12 Oct 2026, email search off). You can open the full output yourself: JSON · CSV.

Business Reviews Local SEO score Opportunity Tier
Precision Plumbing Beaverton 3 51 48 warm
Anctil Plumbing Inc 31 68 27 cold
Leak Locaters 185 82 10 cold

Two things stand out. The business with the fewest reviews and the weakest site comes out on top, which is the point of scoring. And a high review count doesn't mean a well-optimised site: in a larger run, Apollo Plumbing, Heating, AC & Electric had 2,014 reviews but a local SEO score of 20 and no LocalBusiness schema, which put it in the hot tier. That's why it's worth scoring the website rather than filtering on reviews alone.

The practical outcome: instead of emailing every place on the scrape, you start with the hot and warm rows and one specific, checkable observation for each.

Doing it without writing the code

Disclosure again: Manta Media built both of these Actors. We packaged this workflow as Google Maps Leads Scorer ($0.008 per scored lead; leads that time out are not charged). Paste the Google Maps Scraper dataset ID into datasetId:

{ "datasetId": "<your Google Maps Scraper dataset ID>", "maxLeads": 200, "findEmails": true }
Enter fullscreen mode Exit fullscreen mode

You get one row per business with opportunityScore, tier, pitchPoints, email, localSeoScore, reviewMedian and seoIssues. The Google Maps fields title, website, phone, totalScore, reviewsCount, categoryName and city are mapped automatically. Sort by opportunityScore and start with the hot tier. If you only need the website checks for a list of URLs, the sister Actor Local SEO Bulk Site Auditor does that for $0.004 per URL.

Either way, the steps above work just as well in your own script. What matters is the order: scrape, check, score, then contact only the top of the list.


If you want the whole setup I use around this (the agent instructions, site templates and pre-send checks), it is in the Lead Site Crew Kit, $39.

Top comments (0)