Google Maps listings contain rich business data: name, address, phone, website, hours, ratings, and reviews. Instead of scraping Google Maps directly (which violates ToS and breaks constantly), this tutorial extracts Maps data from SERP results. Search for a business category and location, and the SERP returns local pack results with all the data you need for cold email outreach. Each search costs $0.005.
Prerequisites
- Python 3.9+ installed
- requests library installed
- A Scavio API key from scavio.dev
Walkthrough
Step 1: Search for local businesses via SERP
Search for a business category and location. SERP results include both organic listings and local pack data with business details.
import requests, os, re
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def find_local_businesses(category: str, city: str, count: int = 10) -> list:
query = f'{category} near {city}'
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': count})
resp.raise_for_status()
data = resp.json()
businesses = []
# Extract from organic results
for r in data.get('organic_results', []):
domain = r['link'].split('/')[2] if '/' in r['link'] else ''
# Skip aggregator sites
if any(a in domain for a in ['yelp.com', 'yellowpages.com', 'angi.com', 'thumbtack.com']):
continue
phones = re.findall(r'\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}', r.get('snippet', ''))
businesses.append({
'name': r['title'].split(' - ')[0].split(' | ')[0].strip(),
'website': r['link'],
'domain': domain,
'phone': phones[0] if phones else '',
'snippet': r.get('snippet', ''),
'category': category,
'city': city
})
return businesses
leads = find_local_businesses('dentist', 'Austin TX')
print(f'Found {len(leads)} businesses')
for l in leads[:5]:
print(f' {l["name"]}: {l["domain"]} {l["phone"]}')Step 2: Enrich leads with website analysis
Search for each business to find additional signals: whether they have a blog, social media presence, or outdated website design.
import time
def enrich_lead(lead: dict) -> dict:
domain = lead['domain']
if not domain:
return lead
# Search for the business to find additional info
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': f'site:{domain}', 'country_code': 'us', 'num_results': 5})
pages = resp.json().get('organic_results', [])
lead['page_count'] = len(pages)
lead['has_blog'] = any('blog' in p.get('link', '').lower() for p in pages)
lead['has_booking'] = any(w in ' '.join(p.get('title', '') for p in pages).lower()
for w in ['book', 'schedule', 'appointment'])
# Estimate website quality from page count
if lead['page_count'] <= 2:
lead['website_quality'] = 'minimal'
elif lead['page_count'] <= 5:
lead['website_quality'] = 'basic'
else:
lead['website_quality'] = 'established'
return lead
# Enrich top leads
for i, lead in enumerate(leads[:3]):
leads[i] = enrich_lead(lead)
print(f' {lead["name"]}: {lead["page_count"]} pages, '
f'blog={lead.get("has_blog", False)}, '
f'quality={lead.get("website_quality", "unknown")}')
time.sleep(0.3)Step 3: Generate personalized cold email templates
Use the enrichment data to create personalized email templates for each lead. Reference their specific website situation.
def generate_email(lead: dict) -> str:
name = lead['name']
quality = lead.get('website_quality', 'unknown')
has_blog = lead.get('has_blog', False)
has_booking = lead.get('has_booking', False)
# Personalized opening based on website analysis
if quality == 'minimal':
opening = (f'I noticed {name} has a fairly simple website. '
'Many of your competitors in the area have sites with '
'online booking and patient reviews that help them stand out.')
elif not has_booking:
opening = (f'I visited the {name} website and noticed you do not have '
'an online booking system. Adding one typically increases '
'new patient inquiries by 20-30%.')
elif not has_blog:
opening = (f'{name} has a solid website, but I noticed there is no blog. '
'Local dental practices with blogs rank for 3-5x more '
'keywords than those without.')
else:
opening = (f'I have been following {name} online and your web presence '
'is strong. I think there is an opportunity to improve '
'your local search visibility even further.')
email = f"""Subject: Quick question about {name}'s website
Hi,
{opening}
I help {lead['category']}s in {lead['city']} get more patients through
their website. Would a 10-minute call this week make sense?
Best regards"""
return email
for lead in leads[:2]:
if lead.get('website_quality'):
email = generate_email(lead)
print(email)
print('---')Python Example
import requests, os, re, time
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def find_leads(category, city):
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': f'{category} near {city}', 'country_code': 'us', 'num_results': 10})
leads = []
for r in resp.json().get('organic_results', []):
domain = r['link'].split('/')[2] if '/' in r['link'] else ''
if not any(a in domain for a in ['yelp.com', 'yellowpages.com']):
phones = re.findall(r'\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}', r.get('snippet', ''))
leads.append({'name': r['title'].split(' - ')[0], 'domain': domain,
'phone': phones[0] if phones else ''})
return leads
for l in find_leads('dentist', 'Austin TX')[:5]:
print(f'{l["name"]}: {l["domain"]} {l["phone"]}')JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
async function findLeads(category, city) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: `${category} near ${city}`, country_code: 'us', num_results: 10 })
});
return (await resp.json()).organic_results?.filter(r => {
const domain = new URL(r.link).hostname;
return !['yelp.com', 'yellowpages.com'].some(a => domain.includes(a));
}).map(r => ({
name: r.title.split(' - ')[0],
domain: new URL(r.link).hostname,
phone: (r.snippet || '').match(/\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/)?.[0] || ''
})) || [];
}
findLeads('dentist', 'Austin TX').then(l => l.forEach(x => console.log(`${x.name}: ${x.phone}`)));Expected Output
Found 7 businesses
Austin Dental Care: austindentalcare.com (512) 555-0123
Smile Solutions: smilesolutionsatx.com (512) 555-0456
Capital City Dental: capitalcitydental.com
Austin Dental Care: 3 pages, blog=False, quality=basic
Smile Solutions: 8 pages, blog=True, quality=established
Subject: Quick question about Austin Dental Care's website
Hi,
I visited the Austin Dental Care website and noticed you do not have
an online booking system. Adding one typically increases
new patient inquiries by 20-30%.
I help dentists in Austin TX get more patients through their website.
Would a 10-minute call this week make sense?