Local Business Owner Enrichment Pipeline
End-to-end pipeline: Google Maps search -> Owner identification -> Email discovery -> Campaign-ready segmentation. 97% cheaper than Clay with 81% email coverage. Includes lead scoring, MX host classification, and cost tracking.
Docs: https://{YOUR_DOCS_URL}/
4-Stage Pipeline
| Stage |
Script |
What It Does |
| 1 |
1_scrape_maps.py |
Scrape Google Maps via Serper API for business listings |
| 2 |
2_find_owners.py |
5-layer waterfall to identify business owners (LinkedIn, BlitzAPI, web scraping, Facebook, state registries, LLM) |
| 3 |
3_find_emails.py |
Parallel email waterfalls for personal + generic emails (Prospeo, IcyPeas, BlitzAPI, LeadMagic verification) |
| 4 |
4_prepare_campaigns.py |
Score leads (A/B/C tiers), classify email hosts via MX, segment into campaign-ready CSVs |
Execution
# Full pipeline — query + location
python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX"
# Bulk mode — query file (one per line, or query|location)
python3 ./run_pipeline.py --query-file queries.txt
# Nationwide ZIP code scraping (one query per US ZIP code)
python3 ./run_pipeline.py --query "Juice bar" --zip-file ./data/us_zipcodes.txt --max-results 20
# Resume interrupted ZIP scrape
python3 ./run_pipeline.py --query "Juice bar" --zip-file ./data/us_zipcodes.txt --resume
# Start from existing CSV (skip Maps scraping)
python3 ./run_pipeline.py --input existing.csv
# Run a single step
python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX" --step 1
# Resume interrupted pipeline
python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX" --resume
Key Options
| Flag |
Default |
Purpose |
--zip-file |
- |
ZIP code mode: text file with one ZIP per line, generates one query per ZIP |
--max-results |
100 |
Max Maps results per query (use 20 for ZIP mode) |
--max-workers |
1 |
Thread pool size for steps 2-3 |
--step 1|2|3|4 |
all |
Run only a specific step |
--resume |
off |
Resume from checkpoints (includes ZIP scrape checkpoint) |
--skip-verify |
off |
Skip LeadMagic email verification |
--cost-file |
auto |
Path to cost tracking JSON (auto-created in output dir) |
-o, --output-dir |
auto |
Custom output directory |
Output
Creates a dated directory under ./output/ containing:
| File |
Contents |
businesses.csv |
Step 1 - scraped business listings |
businesses_with_owners.csv |
Step 2 - listings + identified owners |
businesses_enriched.csv |
Step 3 - full enrichment with emails (+ lead_score, lead_tier after Step 4) |
campaigns/ |
Step 4 - campaign-ready CSVs segmented by email host x lead tier |
campaigns/segment_summary.json |
Step 4 - segment counts, tier/host distributions, sendable rate |
cost_tracking.json |
API call counts and costs across all steps |
stats.json |
Pipeline statistics |
Step 4: Campaign Preparation
Step 4 transforms enriched leads into campaign-ready CSVs. Three operations:
- Lead Scoring (0-100, A/B/C tiers): Owner confidence (40pts), email quality (35pts), data completeness (15pts), business signals (10pts). Tiers: A >= 70, B >= 40, C < 40.
- MX Classification: Resolves email domains via DNS MX records to classify as google/microsoft/other. Threaded batch resolution with domain-level caching.
- Segmentation: Splits leads by email_host x lead_tier into campaign CSVs (e.g.
google_tier_a.csv, microsoft_tier_b.csv). Small segments merge into {host}_mixed or other_mixed. Generic-only leads go to generic_email.csv. Unsendable leads go to unsendable.csv.
Campaign CSV columns match CampaignPlatform format: email, first_name, last_name, company_name, job_title, company_domain, phone, address, category, lead_score, lead_tier.
API Keys
Resolved in order: CLI flag -> env var -> ~/.env.
| Key |
Used In |
SERPER_API_KEY |
Steps 1, 2 |
BLITZ_API_KEY |
Steps 2, 3 |
LLM_ENDPOINT, LLM_API_KEY, LLM_MODEL |
Step 2 (owner extraction via LLM) |
PROSPEO_API_KEY |
Step 3 |
ICYPEAS_API_KEY, ICYPEAS_API_SECRET |
Step 3 |
LEADMAGIC_API_KEY |
Step 3 (email verification) |
Important Notes
- ALWAYS verify emails before sending campaigns (don't use
--skip-verify for production lists)
- ZIP mode uses
data/us_zipcodes.txt (40,781 US ZIP codes) for nationwide coverage. Use --max-results 20 (most ZIPs have few results per vertical). Checkpoints every 100 queries, progress every 500.
- Step 2 uses LLM analysis — configure
LLM_ENDPOINT for owner extraction from web pages
- Checkpoints save progress per step — safe to interrupt and resume
- The
texas_queries.txt file contains example queries for trade service verticals
Script: ./run_pipeline.py
1---2name: local-enrich3description: Local business owner enrichment pipeline — Google Maps scraping, owner finding, email discovery. 97% cheaper than Clay. USE WHEN user says "local-enrich" OR "local enrich" OR "find business owners" OR "maps scraping" OR "scrape maps" OR "local business leads" OR "owner enrichment" OR "find owners for" OR wants to build a lead list from Google Maps searches.4---56# Local Business Owner Enrichment Pipeline78End-to-end pipeline: Google Maps search -> Owner identification -> Email discovery -> Campaign-ready segmentation. 97% cheaper than Clay with 81% email coverage. Includes lead scoring, MX host classification, and cost tracking.910Docs: https://{YOUR_DOCS_URL}/1112## 4-Stage Pipeline1314|Stage|Script|What It Does|15|---|---|---|16|1|`1_scrape_maps.py`|Scrape Google Maps via Serper API for business listings|17|2|`2_find_owners.py`|5-layer waterfall to identify business owners (LinkedIn, BlitzAPI, web scraping, Facebook, state registries, LLM)|18|3|`3_find_emails.py`|Parallel email waterfalls for personal + generic emails (Prospeo, IcyPeas, BlitzAPI, LeadMagic verification)|19|4|`4_prepare_campaigns.py`|Score leads (A/B/C tiers), classify email hosts via MX, segment into campaign-ready CSVs|2021## Execution2223```bash24# Full pipeline — query + location25python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX"2627# Bulk mode — query file (one per line, or query|location)28python3 ./run_pipeline.py --query-file queries.txt2930# Nationwide ZIP code scraping (one query per US ZIP code)31python3 ./run_pipeline.py --query "Juice bar" --zip-file ./data/us_zipcodes.txt --max-results 203233# Resume interrupted ZIP scrape34python3 ./run_pipeline.py --query "Juice bar" --zip-file ./data/us_zipcodes.txt --resume3536# Start from existing CSV (skip Maps scraping)37python3 ./run_pipeline.py --input existing.csv3839# Run a single step40python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX" --step 14142# Resume interrupted pipeline43python3 ./run_pipeline.py --query "plumbers" --location "Dallas, TX" --resume44```4546## Key Options4748|Flag|Default|Purpose|49|---|---|---|50|`--zip-file`|-|ZIP code mode: text file with one ZIP per line, generates one query per ZIP|51|`--max-results`|100|Max Maps results per query (use 20 for ZIP mode)|52|`--max-workers`|1|Thread pool size for steps 2-3|53|`--step 1\|2\|3\|4`|all|Run only a specific step|54|`--resume`|off|Resume from checkpoints (includes ZIP scrape checkpoint)|55|`--skip-verify`|off|Skip LeadMagic email verification|56|`--cost-file`|auto|Path to cost tracking JSON (auto-created in output dir)|57|`-o, --output-dir`|auto|Custom output directory|5859## Output6061Creates a dated directory under `./output/` containing:6263|File|Contents|64|---|---|65|`businesses.csv`|Step 1 - scraped business listings|66|`businesses_with_owners.csv`|Step 2 - listings + identified owners|67|`businesses_enriched.csv`|Step 3 - full enrichment with emails (+ `lead_score`, `lead_tier` after Step 4)|68|`campaigns/`|Step 4 - campaign-ready CSVs segmented by email host x lead tier|69|`campaigns/segment_summary.json`|Step 4 - segment counts, tier/host distributions, sendable rate|70|`cost_tracking.json`|API call counts and costs across all steps|71|`stats.json`|Pipeline statistics|7273## Step 4: Campaign Preparation7475Step 4 transforms enriched leads into campaign-ready CSVs. Three operations:76771. **Lead Scoring** (0-100, A/B/C tiers): Owner confidence (40pts), email quality (35pts), data completeness (15pts), business signals (10pts). Tiers: A >= 70, B >= 40, C < 40.782. **MX Classification**: Resolves email domains via DNS MX records to classify as google/microsoft/other. Threaded batch resolution with domain-level caching.793. **Segmentation**: Splits leads by email_host x lead_tier into campaign CSVs (e.g. `google_tier_a.csv`, `microsoft_tier_b.csv`). Small segments merge into `{host}_mixed` or `other_mixed`. Generic-only leads go to `generic_email.csv`. Unsendable leads go to `unsendable.csv`.8081Campaign CSV columns match CampaignPlatform format: `email`, `first_name`, `last_name`, `company_name`, `job_title`, `company_domain`, `phone`, `address`, `category`, `lead_score`, `lead_tier`.8283## API Keys8485Resolved in order: CLI flag -> env var -> `~/.env`.8687|Key|Used In|88|---|---|89|`SERPER_API_KEY`|Steps 1, 2|90|`BLITZ_API_KEY`|Steps 2, 3|91|`LLM_ENDPOINT`, `LLM_API_KEY`, `LLM_MODEL`|Step 2 (owner extraction via LLM)|92|`PROSPEO_API_KEY`|Step 3|93|`ICYPEAS_API_KEY`, `ICYPEAS_API_SECRET`|Step 3|94|`LEADMAGIC_API_KEY`|Step 3 (email verification)|9596## Important Notes9798- **ALWAYS verify emails** before sending campaigns (don't use `--skip-verify` for production lists)99- **ZIP mode** uses `data/us_zipcodes.txt` (40,781 US ZIP codes) for nationwide coverage. Use `--max-results 20` (most ZIPs have few results per vertical). Checkpoints every 100 queries, progress every 500.100- Step 2 uses LLM analysis — configure `LLM_ENDPOINT` for owner extraction from web pages101- Checkpoints save progress per step — safe to interrupt and resume102- The `texas_queries.txt` file contains example queries for trade service verticals103104Script: `./run_pipeline.py`