← Back to blog
GuideSeptember 28, 2026·9 min read

How to Clean a Google Maps Lead List (2026)

A Google Maps export doesn't fail the way a bought list does. The phone numbers mostly connect and the businesses mostly exist. What goes wrong is subtler: a plumbing search that returns supply stores, a clinic listed five times under five dentists' names, a Facebook page sitting in the website column. Here's what to check, in what order, with the formulas and a short script to do it.

What's already clean when the file lands

Before cleaning anything, it helps to know which problems the scrape already handled, so you don't spend an afternoon hunting for duplicates that can't be there. In a MapsHarvest export:

  • Same listing twice in one job: When a business matches several of your searches or sits near a city boundary, it can surface more than once. Rows are deduplicated on the Google Maps URL before they're written, so each listing appears once per export.
  • Listings from your earlier jobs: A listing whose Maps URL appeared in any of your previous jobs is skipped, so a second scrape of an overlapping area doesn't hand you rows you already have.
  • Rows that fail your filters: Minimum rating, minimum review count, has-website and has-phone run during the scrape. A listing that fails them never reaches the file.

So exact-duplicate removal is mostly done. What remains is judgment work the scraper can't do for you: deciding which categories you actually sell to, recognizing that two different listings are one business, and knowing which rows your CRM already owns. Exact duplicates come back mainly when you merge files from different tools or different accounts — that's the case the first row of the table below covers.

The cleaning order, step by step

Order matters because each step shrinks the file for the next one, and because some checks give wrong answers if you run them early. Counting shared phone numbers before you've removed closed listings, for example, makes a relocated business look like a duplicate of itself.

01

Drop the rows you'll never contact

Start with the cheapest cuts. Remove listings where temporarily_closed is TRUE (that column comes with the full field set on Growth and up). If you're building a call list and didn't use the has-phone filter, remove blank phones too. Do this first so every later check runs on fewer rows.

02

Fix category drift by category, not by row

A search for 'plumber' brings back what Google considers relevant: plumbers, but also plumbing supply stores, water heater suppliers, septic services and the odd general contractor. Build a pivot table on the category column, count rows per category, and decide keep or drop for each category once. Twenty decisions clean a 3,000-row file; row-by-row review never finishes.

03

Find businesses with more than one listing

Now group by phone number. A dental practice and each of its dentists, a car dealership's sales, service and parts desks, a hospital's departments — Google lets each have its own listing, often with the same main line. Different Maps URLs, same business. Decide whether you want one row per business or one per listing, and keep the listing whose name matches the business, not the practitioner.

04

Separate chains from independents

Group by website_domain. Twelve locations sharing one domain is a chain or franchise, and the purchase decision for all twelve is usually made by one person at head office, not by whoever answers the phone at each location. Tag these rows so your reps call head office once instead of twelve stores twelve times.

05

Reclassify websites that aren't websites

Plenty of small businesses list their Facebook page, Instagram profile, a Linktree, a Yelp page or a booking link in the website field. As a column it looks full; as a lead, the business has no site of its own. Flag these domains so they move into your no-website segment instead of getting a pitch that assumes they have one.

06

Format phones for the dialer and flag the odd ones

Strip everything but digits and convert to the format your dialer imports (most accept E.164, like +15125550100). While you're there, flag toll-free numbers (800, 833, 844, 855, 866, 877, 888): on a local-business list they usually mean a franchise hotline or a call-tracking line, not the owner.

07

Tidy names for mail merge, but keep the original

Some businesses pack keywords into their listing name — 'Rivera Plumbing | 24/7 Emergency Plumber Austin TX'. That reads badly in a {{company}} merge field. Add a short-name column you edit, and keep the original next to it; the original is what the business owner sees on Maps and what you match on later.

08

Match against your CRM before anything is sent

Match in order of reliability: Google Maps URL if your CRM stores it, then normalized phone, then website domain. Never match on name alone — 'Main Street Dental' exists in hundreds of towns. Rows that match an open deal or an existing customer come out of the campaign.

Steps 3 to 5 depend on the website and website_domain columns, which are included from the Starter plan up. On the free plan you can still run steps 1, 2, 6, 7 and 8 on the core fields.

Reading the duplicates: what each match means

"Duplicate" covers several different situations in Maps data, and only one of them should be deleted automatically. The column that matches tells you which situation you're in:

What matchesWhat it usually meansWhat to do
Same Maps URL on two rowsThe same listing, collected twice (usually from files merged outside MapsHarvest)Delete one
Same phone, different namesPractitioners or departments of one business, or a shared answering serviceReview; keep the business-level listing
Same website domain, different addressesChain, franchise, or multi-location groupTag; route to head office
Website domain is a social or directory siteNo website of its ownMove to the no-website segment
Toll-free phone numberCentral hotline or call trackingFlag; lower dial priority
Category outside your targetGoogle's idea of relevant, not yoursDecide once per category

Address is missing from that list on purpose. Shared addresses are normal on Maps — every tenant of a medical office building, a strip mall, or a coworking space has the same street address, often down to the suite. Two businesses at one address are two leads.

The checks as Google Sheets formulas

These assume the phone is in column D and website_domain in column F; adjust the letters to your file. Put the first formula in a helper column R and the rest reference it. For the social-site check, make a second tab named Social with one domain per row in column A — facebook.com, instagram.com, linktr.ee, yelp.com and whatever else shows up in your files.

Digits-only phone (for matching)
=REGEXREPLACE(TO_TEXT(D2),"[^0-9]","")
How many rows share this phone
=COUNTIF($R:$R,R2)
How many rows share this domain
=IF(F2="","",COUNTIF($F:$F,F2))
Social or directory site in the website field
=COUNTIF(Social!$A:$A,F2)>0
Toll-free number
=REGEXMATCH(R2,"^1?(800|833|844|855|866|877|888)")
E.164 for a US or Canadian number
=IF(LEN(R2)=10,"+1"&R2,IF(AND(LEN(R2)=11,LEFT(R2,1)="1"),"+"&R2,""))

In Excel, Data → Remove Duplicates on the Google Maps URL column handles exact duplicates, and the COUNTIF formulas work unchanged. For the regex ones, use Excel's REGEXREPLACE if your version has it, or strip spaces, dashes, dots and parentheses with nested SUBSTITUTE calls. If you're still at the export stage, the Excel export guide covers getting the file in cleanly in the first place.

Sort by the shared-phone count, highest first, and review the top of the list. The first few groups are almost always the interesting ones: a franchise hotline on 30 listings, a dental group on 8. Past a count of 2 or 3, the reason is usually obvious from the name column.

The same checks as a pandas script

If you clean a file like this every week, a script saves the formula-copying and gives the same result every time. This one runs steps 1 through 6 and step 8 and writes the flags as new columns rather than deleting rows, so you can still sort and review before anything is thrown away. It assumes US or Canadian numbers; for UK, Australian, German or Indian lists, swap the last-10-digits rule for a proper parser such as the phonenumbers library.

import pandas as pd

df = pd.read_csv("leads.csv", dtype=str).fillna("")

# 1. Rows you'll never contact
if "temporarily_closed" in df.columns:
    df = df[df["temporarily_closed"].str.lower() != "true"]
df = df[df["phone"] != ""]

# 2. Category decisions, made once per category (edit this list)
DROP_CATEGORIES = {"Plumbing supply store", "Water utility company"}
df = df[~df["category"].isin(DROP_CATEGORIES)]

# 3-4. Shared phones and shared domains (US/Canada: last 10 digits)
df["phone_digits"] = df["phone"].str.replace(r"\D", "", regex=True).str[-10:]
df["listings_on_phone"] = df.groupby("phone_digits")["google_maps_url"].transform("count")
df["listings_on_domain"] = df.groupby("website_domain")["google_maps_url"].transform("count")
df.loc[df["website_domain"] == "", "listings_on_domain"] = 0

# 5. Websites that aren't websites
SOCIAL = {"facebook.com", "instagram.com", "linktr.ee", "yelp.com",
          "tiktok.com", "x.com", "twitter.com", "nextdoor.com"}
df["own_website"] = (df["website_domain"] != "") & ~df["website_domain"].isin(SOCIAL)

# 6. Dialer format and toll-free flag
df["phone_e164"] = "+1" + df["phone_digits"]
df["toll_free"] = df["phone_digits"].str[:3].isin(
    ["800", "833", "844", "855", "866", "877", "888"])

# 8. Already in the CRM?
crm = pd.read_csv("crm_export.csv", dtype=str).fillna("")
crm_phones = set(crm["phone"].str.replace(r"\D", "", regex=True).str[-10:])
df["in_crm"] = df["phone_digits"].isin(crm_phones)

df.to_csv("leads_clean.csv", index=False)

On the Growth plan and up you can skip the CSV download step and feed the script straight from the REST API: GET /jobs/{id}/download?fmt=csv returns the same file; wrap the response text in io.StringIO and pass it to pd.read_csv. Pair it with a webhook on job completion and the cleaned file is ready without anyone opening the dashboard.

What not to clean out

The most expensive cleaning mistake with Maps data isn't leaving junk in. It's deleting good leads that look like junk:

Low review counts+

A new business, or one whose customers don't write reviews — accountants, B2B suppliers, estate lawyers — can have 3 reviews and be exactly who you want. If you filtered on review count during the scrape, that was a deliberate choice. Don't make it again by accident while cleaning.

No website+

For anyone selling websites, SEO, online ordering or booking software, the no-website rows are the target market. Move them to their own segment. We wrote a separate guide on finding businesses without a website if that's the list you're after.

Personal names as business names+

'Maria Lopez, CPA' or 'Dr. James Carter' is how many sole practitioners register. The name looks messy in a merge field, but the person answering is the decision maker. Shorten it in your short-name column; don't drop the row.

Out-of-town addresses on a city scrape+

Maps shows businesses that serve an area, not only ones located in it, so a city search picks up some neighbors. Check whether the business is within your delivery or sales territory before deleting it. A company 6 miles over the city line may still be in range.

For the no-website segment specifically, see how to find businesses without a website. For rows with a real website but no email, the email-finding guide picks up where this one ends.

FAQ

What's the best column to deduplicate Google Maps data on?+

The Google Maps URL. It identifies one listing, so two rows with the same URL are the same listing, full stop. Phone number and website domain are for a different question — whether two different listings belong to the same business — and those matches need a human decision, not an automatic delete.

Should I delete rows with no website?+

Only if your offer needs one. For web design, SEO, and online ordering pitches, the no-website rows are the best part of the file. Keep them and put them in their own segment instead of deleting them.

Why do some businesses appear with the same phone number?+

Usually one of three reasons: several listings for one business (a clinic and each of its practitioners, or a dealership's sales and service departments), a franchise or chain routing every location to one call center, or a shared answering service. The name and category columns tell you which one you're looking at.

If I re-run the same scrape, will I get the same rows back?+

No. MapsHarvest skips any listing whose Google Maps URL already appeared in one of your earlier jobs, so a repeat run returns only listings you haven't collected yet. That makes re-runs a good way to catch new businesses, but not a way to refresh the rows you already have.

Excel or Google Sheets for cleaning?+

Either handles a few thousand rows. Sheets has REGEXREPLACE and REGEXMATCH built in, which makes phone and domain checks one-line formulas. Past roughly 50,000 rows, or if you clean the same kind of file every week, a short script is faster and repeatable.

Start with a cleaner file

Filter by rating, reviews, website and phone during the scrape, get one row per listing, and export to CSV. 50 free credits, no credit card.