Lo esencial
Imagina que puedes contratar a alguien que en una noche reúne 500 contactos de clientes potenciales (leads), entra al sitio web de cada uno, lee sobre su negocio, redacta un icebreaker personalizado (una frase para abrir la conversación) y en la mañana te deja un CSV listo. Eso es justo lo que hace un Lead Generation System con Claude Code. Es una automatización B2B completa (ventas a empresas): de una lista vacía a una base enriquecida con la que puedes pasar directo al outreach en frío (contactar a un cliente potencial que no te conoce).
Conceptos clave
- Lead Generation Pipeline (la cadena de procesamiento para generar leads): 4 etapas, recolección → filtrado → enriquecimiento → exportación
- Apify: un marketplace de scrapers (programas que recolectan datos); el scraper de Google Maps sirve para buscar negocios por nicho y ubicación
- Filtrado: nos quedamos solo con los leads que tienen sitio web y teléfono (los demás se descartan)
- Enrichment (enriquecimiento de datos): Claude analiza cada sitio web: value proposition (propuesta de valor), qué los hace únicos, un case study (caso real con resultados)
- Output (el resultado final): un CSV con los campos company, location, website, phone, value_prop, differentiator, case_study
- Skill (habilidad, un módulo reutilizable de Claude): todo el pipeline se empaqueta en un skill reutilizable con un comando slash
Teoría
La estructura del pipeline
Entrada:
niche = "roofing companies"
location = "North Carolina"
count = 30
Paso 1: Google Maps Scraping (Apify)
→ 34 negocios con los metadatos de Google Maps
Paso 2: Filtrado
→ Nos quedamos solo con los que tienen website Y phone
→ Por ejemplo: 22 de 34 pasaron el filtro
Paso 3: Website Scraping + Claude Enrichment
→ Para cada uno de los 22: scrape de la homepage + /about + /services
→ Claude extrae: value_prop, differentiator, case_study
Paso 4: CSV Export
→ leads.csv, listo para Google Sheets y el CRMPor qué Apify y no extraer los datos de Google directamente
Google Maps tiene una API oficial de pago (la interfaz para que los programas se comuniquen), pero no está pensada para descargar bases de negocios en grandes volúmenes. Extraer directamente las páginas de Google viola sus ToS (Terms of Service, los términos de uso) y te bloquean rápido. Los términos de los servicios cambian: antes de empezar, revisa las condiciones vigentes y las leyes de tu país.
Apify es un marketplace de scrapers listos con:
- Rotación de proxies (para evitar bloqueos)
- Control de límites
- Salida estructurada (JSON)
- Un plan gratis con un pequeño crédito mensual (condiciones vigentes: apify.com/pricing)
El scraper de Google Maps en Apify recibe una consulta como "roofing companies near North Carolina" y devuelve una lista de negocios con dirección, sitio web, teléfono, calificación y horario.
El prompt inicial (la instrucción para la IA): explicarle la tarea a Claude Code
Un patrón importante que sale de la práctica: primero describe la tarea completa, no pidas de entrada "crea un skill". Deja que Claude primero construya y pruebe el sistema, y luego lo empaque en un skill:
Hola, la tarea de hoy es crear un Lead Generation System. Paso 1: yo te doy el nicho, la ubicación y la cantidad de resultados Paso 2: vas a Apify y usas el Google Maps scraper Paso 3: filtras: te quedas solo con los leads que TIENEN sitio web y teléfono Paso 4: entras a cada sitio (homepage + about + services) Paso 5: Claude extrae: value proposition, qué los hace únicos, casos de clientes Paso 6: entregas un CSV: company_name, location, website, phone, value_prop, differentiator, case_study Hazme preguntas para aclarar antes de empezar.
Claude preguntará: si tienes una clave de API de Apify, qué formato de salida prefieres y qué lenguaje usar.
Script de Python: la arquitectura del sistema
# lead_generation.py
import os
import csv
import json
import anthropic
from apify_client import ApifyClient
APIFY_TOKEN = os.environ["APIFY_API_TOKEN"]
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
def scrape_google_maps(niche: str, location: str, count: int) -> list[dict]:
"""Paso 1: Recolectamos negocios con el Google Maps scraper de Apify"""
client = ApifyClient(APIFY_TOKEN)
run_input = {
"searchStringsArray": [f"{niche} near {location}"],
"maxCrawledPlacesPerSearch": count,
"language": "en",
"countryCode": "us",
}
# Google Maps Scraper en Apify (compass/crawler-google-places)
run = client.actor("nwua9Gu5YrADL7ZDj").call(run_input=run_input)
results = []
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
results.append({
"company_name": item.get("title", ""),
"location": item.get("address", ""),
"website": item.get("website", ""),
"phone": item.get("phone", ""),
"rating": item.get("totalScore", ""),
})
return results
def filter_leads(raw_leads: list[dict]) -> list[dict]:
"""Paso 2: Nos quedamos solo con los leads que tienen sitio web y teléfono"""
filtered = [
lead for lead in raw_leads
if lead.get("website") and lead.get("phone")
]
print(f"Filtrado: {len(raw_leads)} → {len(filtered)} leads")
return filtered
def scrape_website(url: str) -> str:
"""Paso 3a: Recolectamos el texto del sitio (homepage + about + services)"""
import requests
from bs4 import BeautifulSoup
pages_to_scrape = [url, f"{url}/about", f"{url}/services"]
all_text = []
for page_url in pages_to_scrape:
try:
response = requests.get(page_url, timeout=10, headers={
"User-Agent": "Mozilla/5.0 (compatible; LeadBot/1.0)"
})
if response.status_code == 200:
soup = BeautifulSoup(response.text, "html.parser")
# Quitamos scripts y estilos
for tag in soup(["script", "style", "nav", "footer"]):
tag.decompose()
text = soup.get_text(separator=" ", strip=True)
all_text.append(text[:2000]) # Tomamos los primeros 2000 caracteres de cada página
except Exception:
continue
return " | ".join(all_text)
def enrich_with_claude(company_name: str, website_text: str) -> dict:
"""Paso 3b: Claude extrae información estructurada del texto del sitio"""
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
message = client.messages.create(
model="claude-haiku-4-5", # Haiku alcanza para extraer datos estructurados; modelos vigentes: página Lo vigente
max_tokens=500,
messages=[{
"role": "user",
"content": f"""Analiza este texto del sitio web de la empresa {company_name}.
Texto del sitio:
{website_text[:3000]}
Extrae en JSON:
- value_prop: la propuesta de valor principal (1-2 oraciones)
- differentiator: qué los hace únicos frente a la competencia (1 oración)
- case_study: el nombre del cliente y de qué trata el caso, si aparece en el sitio; si no, null
Devuelve solo el JSON, sin markdown.
"""
}]
)
try:
return json.loads("".join(b.text for b in message.content if b.type == "text"))
except json.JSONDecodeError:
return {
"value_prop": "N/A",
"differentiator": "N/A",
"case_study": None
}
def export_to_csv(leads: list[dict], filename: str = "leads.csv") -> None:
"""Paso 4: Exportar a CSV"""
if not leads:
print("No hay leads para exportar")
return
fieldnames = ["company_name", "location", "website", "phone",
"value_prop", "differentiator", "case_study"]
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for lead in leads:
# Nos aseguramos de que estén todos los campos (protección contra columnas desplazadas)
row = {field: lead.get(field, "") for field in fieldnames}
writer.writerow(row)
print(f"Se exportaron {len(leads)} leads a {filename}")
def run_pipeline(niche: str, location: str, count: int, output_file: str) -> None:
"""Ejecución del pipeline completo"""
print(f"Inicio: {niche} en {location}, {count} resultados")
# Paso 1: Scraping
raw_leads = scrape_google_maps(niche, location, count)
# Paso 2: Filtrado
filtered_leads = filter_leads(raw_leads)
# Paso 3: Enrichment
enriched_leads = []
for i, lead in enumerate(filtered_leads):
print(f" Enriqueciendo {i+1}/{len(filtered_leads)}: {lead['company_name']}")
website_text = scrape_website(lead["website"])
enrichment = enrich_with_claude(lead["company_name"], website_text)
enriched_leads.append({**lead, **enrichment})
# Paso 4: Export
export_to_csv(enriched_leads, output_file)
print("¡Listo!")
if __name__ == "__main__":
run_pipeline(
niche="roofing companies",
location="North Carolina",
count=30,
output_file="leads.csv"
)Resultado real: qué sale en el CSV
Después de ejecutarlo, el sistema entrega un archivo con datos como estos:
| company_name | location | website | phone | value_prop | differentiator | case_study |
|---|---|---|---|---|---|---|
| Example Roofing Co | Charlotte, NC | example.com | +1-555-0100 | Servicio integral de techos con trato familiar y financiamiento | Empresa familiar; su experiencia aparece en el sitio | Cliente: renovación completa del techo terminada sin retrasos |
| ... | ... | ... | ... | ... | ... | ... |
La fila de arriba es inventada, solo como ejemplo. Este CSV se lleva a Google Sheets con File → Import (si todo quedó en una sola columna, usa Data → Split text to columns).
Empaquetarlo como skill + comando slash
Cuando el sistema ya funciona, le pides a Claude Code que lo convierta en un skill:
Ahora que recorrí todo este pipeline, crea un skill llamado "lead-generation-system" para poder llamarlo como el comando slash /lead-generation-system.
Claude crea la estructura:
.claude/skills/lead-generation-system/
├── SKILL.md ← descripción + instrucciones para Claude
├── scripts/
│ └── lead_generation.py
└── references/
├── requirements.txt
└── sample_output.csvEl skill se vuelve un comando slash por sí solo: ahora puedes escribir /lead-generation-system y el sistema te preguntará el nicho, la ubicación y la cantidad. Ya no hace falta un archivo aparte en .claude/commands/: los comandos y los skills están unificados, y los archivos de comandos anteriores siguen funcionando. Para que el skill esté disponible en cualquier proyecto, ponlo en tu carpeta personal ~/.claude/skills/.
Límites éticos
| Permitido | Prohibido |
|---|---|
| Datos públicos de Google Maps | Correos electrónicos de personas físicas |
| Texto público de sitios web corporativos | Datos personales de los dueños |
| Teléfonos de negocios de fuentes abiertas | Saltarse CAPTCHAs de forma agresiva |
| Análisis de casos de estudio públicos | Datos de secciones privadas de los sitios |
El sistema solo recolecta lo que el negocio publicó por su cuenta en internet; normalmente eso es aceptable para un outreach B2B. Las reglas sobre datos personales y envíos masivos dependen del país (por ejemplo, GDPR en Europa, CAN-SPAM en Estados Unidos, CASL en Canadá, y las leyes de protección de datos personales de cada país de América Latina): antes de enviar, revisa la ley de tu país. Esto no es asesoría legal.
Práctica
Tarea: Lead Generation para un nicho concreto
- Regístrate en apify.com y obtén un token de API (el plan gratis trae un pequeño crédito mensual; revisa el monto vigente en el sitio)
- Instala las dependencias:
pip install apify-client anthropic requests beautifulsoup4 - Pídele a Claude Code que construya una versión simplificada del pipeline para el nicho que elijas:
- "digital marketing agencies in Miami"
- "yoga studios in Austin"
- empresas de TI en tu ciudad (en un nicho que entiendas)
- Ejecútalo con count=10 como prueba y asegúrate de que el CSV se genera bien
- Abre el CSV en Google Sheets y revisa que todas las columnas estén en su lugar
- Pídele a Claude Code que empaque el sistema en un skill
- Bonus: agrega una columna de puntaje (0-100) donde Claude califique la calidad del lead según si tiene case_study y qué tan detallado es el value_prop
Objetivo: pasar de cero a un sistema de lead generation que funcione con datos reales.
Herramientas y recursos
- Apify: apify.com, marketplace de scrapers; ID del Google Maps scraper:
nwua9Gu5YrADL7ZDj - apify-client:
pip install apify-client, SDK de Python para Apify - beautifulsoup4:
pip install beautifulsoup4, para leer el HTML de los sitios - requests:
pip install requests, solicitudes HTTP a los sitios de los leads - Google Sheets: Data → Split text to columns, para abrir el CSV
- Hunter.io: hunter.io, búsqueda de correos por dominio (tiene un plan gratis con créditos mensuales; revisa el límite vigente en el sitio); completa el pipeline con datos de contacto
- Apollo.io: apollo.io, base completa de contactos B2B con verificación de correos, alternativa a Apify para conseguir leads
- LinkedIn Sales Navigator: business.linkedin.com/sales-solutions, búsqueda dirigida de quienes toman las decisiones (revisa en el sitio las condiciones del periodo de prueba)
- Instantly.ai: instantly.ai, automatización de envío de correos con calentamiento del dominio (para cuando escales el outreach)
Errores comunes
- No filtrar los leads antes del enrichment. Enriquecer cada lead cuesta dinero (una llamada a la API de Claude). Sin filtro gastas presupuesto en empresas sin sitio web ni teléfono, a las que no puedes contactar. Filtra ANTES del enrichment.
- No revisar el CSV a mano. Revisa siempre a ojo las primeras 10 filas en Google Sheets. Columnas desplazadas, campos vacíos, duplicados: todo eso se detecta en los primeros datos, pero arruina toda la base cuando escalas.
- Un count demasiado grande al principio. Empieza con
count=10como prueba. Si pones 500 de entrada, gastarás dinero en Apify y en la API de Claude, y mucho tiempo en el enrichment. Y descubrirás el error en el paso 3. Primero revisa el costo y la calidad de los datos con una ejecución pequeña. - No conectar los leads con el outreach. Un CSV sin acciones es solo un archivo. Este pipeline alimenta el outreach en frío (→ Outreach en frío con Claude) y el CRM (→ la tabla de Google Sheets de esa misma lección).
Relación con otras lecciones
- → Primeros clientes: empieza con contactos cercanos y agrega Lead Gen después
- → Outreach en frío con Claude: el CSV de esta lección = los datos de entrada para correos personalizados
- → Casos reales de monetización: el pipeline de Lead Gen (flujo de trabajo) como uno de los casos analizados
Conclusiones clave
Un pipeline de 4 pasos: Apify (scrape) → filtro (website+phone) → enrichment con Claude → CSV. Cada paso es independiente y se prueba por separado.
Primero construye y prueba el sistema; después pídele a Claude que lo empaque en un skill. Un skill creado a partir de un sistema que funciona es mejor que uno creado "de la nada".
Protección contra columnas desplazadas en el CSV: indica
fieldnamesde forma explícita y usalead.get(field, ""); así evitas que el teléfono termine en la columna de ubicación.
Siguiente lección
La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso