Building an Automated Lead Generator with a QA Mindset

Finding clients for a software consulting side hustle is usually harder than the actual work. You end up scrolling Google Maps, clicking broken links, and trying to guess whether a business has the budget for digital work.

So I built the Local Business Lead Generator to automate the hunt for high-value service businesses (landscapers, plumbers, HVAC) that are successful enough to pay for services, but outdated enough to need them. Here’s how it works: tests from day one, tiered analysis, and deep platform fingerprinting.


The Core Architecture

The system is a modular pipeline that takes a location and industry and outputs a prioritized list of leads. Concerns are cleanly separated into API (data ingestion), Analysis (business logic), and Models (data structures).

1. Data Ingestion (The Search Engine)

I use SerpAPI to scrape Google Maps results. Unlike generic web search, Maps data gives you the context needed for scoring, specifically, the Maps ranking.

  • Hypothesis: a business ranking #1-3 on Maps is getting real traffic. If their website is broken, that’s a high-value emergency they’ll pay to fix.
  • Implementation: the SearchEngine class handles pagination and parses raw SerpAPI JSON into clean BusinessInfo dataclasses.

2. The Tiered Analysis Pattern

This is the most technically interesting part. I needed to analyze websites for SSL, mobile responsiveness, and tech stack, and the obvious approaches both had problems. requests is fast but fails on JavaScript-heavy sites (Wix, Squarespace). Playwright is accurate but slow and resource-heavy. So I went with a hybrid tiered approach instead.

  • Tier 1 (Fast): lightweight HTTP request first. If it returns good HTML, parse it.
  • Tier 2 (Heavy): if the response looks JavaScript-dependent (“enable JavaScript” text in the HTML, a quality score under 30, zero pages parsed, or a domain on my known JS-heavy allowlist), spin up a headless browser.

You get the speed of requests for most sites (ballpark 80% in my runs) and the accuracy of Playwright for the messy remainder.

3. Deep Platform Fingerprinting

To sell anything effectively, you need to know what the client is already using. The analyzer doesn’t just check if a site exists, it fingerprints the stack from HTML signatures.

I look for specific artifacts in the DOM and headers to identify:

  • CRMs: Salesforce (slds-scope), HubSpot (hs-script), GoHighLevel (leadconnector)
  • Builders: Wix, Squarespace, WordPress
  • Funnels: ClickFunnels, Leadpages

If a client is running Salesforce but their website is broken, they’re a high-value target, they invest in tech but implemented it poorly. If they’re on ClickFunnels, they already understand conversion funnels and might be ready for automation. The signals change what you’re selling, not just whether to pitch.


The QA-First Approach

Given my background in QA, I wasn’t going to build this as spaghetti code. Tests were part of the project from day one using Python’s standard unittest library, and the discipline grew as the project did: the API layer got its implementation first with real tests landing right behind it, while the analysis and scoring modules shipped tests with their implementation.

  • Stdlib scoring core. The models and LeadScorer are pure standard library (dataclasses, typing). Ingestion and analysis lean on requests and Playwright.
  • Broad coverage. Every component (from SocialAnalyzer to LeadScorer) has its own test module, most written alongside the code they cover.
  • Mocking strategy. Heavy use of unittest.mock to simulate API responses. I tested the 404 logic and rate-limit handling without spending a dime on API credits during development.

Current status: 89 tests, all passing. The suite hit 87 within the first two days of development.


The Scoring Algorithm

Data is useless without prioritization. The LeadScorer module calculates an Opportunity Score (0-100) based on weighted factors:

  1. Maps Ranking (40%): higher rank = more established business = higher budget
  2. Website Quality Gap (30%): lower quality = bigger pain = easier sale
  3. Social Presence (20%): missing social channels = low-hanging fruit for upsells
  4. Platform Opportunity (10%): specific stacks (outdated WordPress, etc.) suggest clear upgrade paths

The output isn’t just a number either, it generates human-readable reasoning:

Top ranking business; Poor website quality; High-value target with strong local presence


What’s Next: The Intelligence Layer

The current system runs on deterministic rules. The next phase is an LLM layer that replaces hard-coded logic with semantic understanding:

  • Smart normalization: mapping “guys who cut trees” to “Tree Service” automatically
  • Content intelligence: reading the website text to determine if they offer emergency services (which implies they need an AI voice receptionist)
  • Competitive gap analysis: “3 of your top 5 competitors offer online booking, but you don’t.”

What started as a lead-finding tool turned into a decent case study for how TDD and solid error handling can turn a simple scraper into something you actually trust to run unattended.