Most freelancers spend 3–5 hours every week doing the same soul-crushing thing: searching Google Maps for potential clients, copying business names and phone numbers into a spreadsheet, and sending the same generic cold email to everyone. I got tired of it. So I spent a Saturday afternoon building a Python bot that does all of it automatically.
Here's what it does, how I built it, and the exact code — under 100 lines.
The Problem: Manual Lead Gen Is a Time Sink
If you're a freelance web developer, copywriter, or marketing consultant, your typical lead gen workflow looks something like this:
- Open Google Maps
- Search "plumber in Austin TX"
- Click each result, copy the name, phone, website
- Paste into a spreadsheet
- Write a cold email that says "Hi, I noticed your website could use some improvements..."
- Send to 50 people. Hear back from maybe 2.
The email template is the real killer. Generic outreach gets ignored. But writing a personalized email for each lead takes 5–10 minutes per person. At 50 leads, that's nearly a full workday just on prospecting.
The fix is obvious once you see it: automate the scraping and the personalization. That's exactly what Claude API is built for.
The Architecture
The bot has three stages:
Stage 1: Scrape leads from Google Maps
I use the SerpAPI Google Maps endpoint (free tier: 100 searches/month) to pull business listings for any search query. Each result includes name, address, phone, website, rating, and category. No Playwright required for this part — just a simple HTTP request.
Stage 2: Personalize cold emails with Claude API
For each lead, I pass the business name, category, and any website info to Claude with a prompt that says: "Write a 3-sentence cold email from a freelance web developer to this business." Claude returns a personalized email in under a second.
Stage 3: Export to CSV
All leads + generated emails get written to a CSV file you can import into any email sender (Mailchimp, Lemlist, or just Gmail).
Total runtime for 20 leads: about 45 seconds.
The Code
import anthropic
import requests
import csv
import os
from datetime import datetime
# ── Config ──────────────────────────────────────────────
SERP_API_KEY = os.getenv("SERP_API_KEY") # serpapi.com free tier
CLAUDE_API_KEY = os.getenv("ANTHROPIC_API_KEY")
QUERY = "web design agency in Austin TX" # change this to your niche
NUM_RESULTS = 20
OUTPUT_FILE = f"leads_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
# ── Init Claude ──────────────────────────────────────────
client = anthropic.Anthropic(api_key=CLAUDE_API_KEY)
def fetch_leads(query: str, num: int) -> list[dict]:
"""Pull business listings from Google Maps via SerpAPI."""
params = {
"engine": "google_maps",
"q": query,
"num": num,
"api_key": SERP_API_KEY,
}
resp = requests.get("https://serpapi.com/search", params=params, timeout=10)
resp.raise_for_status()
results = resp.json().get("local_results", [])
leads = []
for r in results:
leads.append({
"name": r.get("title", ""),
"phone": r.get("phone", "N/A"),
"website": r.get("website", "N/A"),
"category": r.get("type", "business"),
"rating": r.get("rating", "N/A"),
"address": r.get("address", ""),
})
return leads
def generate_email(lead: dict) -> str:
"""Ask Claude to write a personalized cold email for this lead."""
prompt = (
f"Write a 3-sentence cold email from a freelance web developer "
f"to {lead['name']}, a {lead['category']} business. "
f"Be specific, friendly, and end with a clear call to action. "
f"Do not use placeholders like [Your Name]. Sign off as Alex."
)
message = client.messages.create(
model="claude-haiku-4-5-20251001", # fast + cheap for email gen
max_tokens=200,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text.strip()
def main():
print(f"Fetching leads for: {QUERY}")
leads = fetch_leads(QUERY, NUM_RESULTS)
print(f"Found {len(leads)} leads. Generating emails...")
rows = []
for i, lead in enumerate(leads, 1):
email_text = generate_email(lead)
lead["cold_email"] = email_text
rows.append(lead)
print(f" [{i}/{len(leads)}] {lead['name']} ✓")
# Write CSV
fieldnames = ["name", "phone", "website", "category", "rating", "address", "cold_email"]
with open(OUTPUT_FILE, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"\nDone! {len(rows)} leads saved to {OUTPUT_FILE}")
if __name__ == "__main__":
main()
That's 98 lines including blanks and comments. The core logic — fetch, personalize, export — is about 60 lines of actual Python.
Results and Lessons
I ran this on three niches over two weeks:
- Plumbers in Denver → 20 leads, 4 replies, 1 paid project ($850)
- Real estate agents in Phoenix → 20 leads, 6 replies, 2 discovery calls
- Restaurants in Nashville → 20 leads, 1 reply (restaurants are tough)
What worked:
- Specificity beats volume. 20 personalized emails outperformed 200 generic ones.
- Claude's output is surprisingly good at matching tone — it picked up "local plumber" vs "real estate professional" without any examples.
- Using
claude-haiku-4-5-20251001keeps costs negligible: 20 emails costs about $0.003 total.
What didn't work:
- SerpAPI's free tier caps out fast. If you want scale, you need a paid plan or a scraping alternative (Playwright + rotating proxies).
- Restaurants and retail have low response rates — target service businesses that actually need web presence.
- Rate limiting. If you hammer the API with 100 leads at once, add
time.sleep(0.5)between requests.
The real lesson: The bottleneck isn't finding leads or writing emails — it's follow-up. I wired this into a simple Notion database so I can track who replied and when to follow up. That's a post for another day.
Take It Further
If you want to extend this, here are three immediate upgrades:
- Add Playwright scraping for sites that block SerpAPI — scrape the business's actual website and pass the homepage copy to Claude for even more personalized emails.
-
Auto-send via Gmail API — hook into
smtplibor the Gmail API to send directly from the script. - Niche targeting — swap the query to target specific verticals: "HVAC contractor in [city]", "personal injury lawyer in [state]", etc.
Get the Full Starter Kit
I packaged this script alongside 9 other Python automation tools — including a Fiverr proposal bot, a content repurposer, and a Reddit lead scanner — into a single starter kit.
Grab it at https://payhip.com/b/GuGDX — use it this week and start closing leads before the weekend.
Questions? Drop them in the comments. I check daily.
Top comments (0)