DEV Community

Henry Knight
Henry Knight

Posted on

I Built a Lead Generation Bot in 100 Lines of Python (Claude API + Google Maps)

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:

  1. Open Google Maps
  2. Search "plumber in Austin TX"
  3. Click each result, copy the name, phone, website
  4. Paste into a spreadsheet
  5. Write a cold email that says "Hi, I noticed your website could use some improvements..."
  6. 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()
Enter fullscreen mode Exit fullscreen mode

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-20251001 keeps 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:

  1. 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.
  2. Auto-send via Gmail API — hook into smtplib or the Gmail API to send directly from the script.
  3. 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)