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
SearchEngineclass handles pagination and parses raw SerpAPI JSON into cleanBusinessInfodataclasses.
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
LeadScorerare pure standard library (dataclasses,typing). Ingestion and analysis lean onrequestsand Playwright. - Broad coverage. Every component (from
SocialAnalyzertoLeadScorer) has its own test module, most written alongside the code they cover. - Mocking strategy. Heavy use of
unittest.mockto 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:
- Maps Ranking (40%): higher rank = more established business = higher budget
- Website Quality Gap (30%): lower quality = bigger pain = easier sale
- Social Presence (20%): missing social channels = low-hanging fruit for upsells
- 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.