Biblioteca · Marketing, ventas y analítica con IA

Búsqueda de clientes potenciales: de recolectar datos de sitios web a una tabla CSV

Creador110 minActualizado: octubre de 2026
65 de 105 en la biblioteca

Tiempo: ~20 min de teoría + 90 min de práctica


Lo esencial

🎨 Imagínalo así: un Lead Generation System es como el turno de noche en una fábrica. Mientras duermes, la línea de producción recolecta, revisa y empaca el producto terminado. En la mañana tienes sobre la mesa cajas con el nombre, la dirección y una breve biografía de cada cliente.

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

🎨 Imagínalo así: el pipeline es como un oleoducto de cuatro tramos. Cada tramo refina la materia prima: primero se extrae (scrape), luego se filtran las impurezas (solo con sitio web y teléfono), después se enriquece (Claude lee cada sitio) y al final se embotella (CSV). Lo que sale no es petróleo crudo, es gasolina.

Código
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 CRM

Por qué Apify y no extraer los datos de Google directamente

🎨 Imagínalo así: Apify es como un barco pesquero profesional. Podrías salir tú solo con una caña (extraer datos de Google directamente), pero pronto te atrapan y te echan. El barco con licencia trabaja en mar abierto y te regresa toda la pesca ordenada.

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:

Escribe esto en el chat
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

python
# 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

🎨 Imagínalo así: empaquetar en un skill es como pasar la receta de un platillo al menú del restaurante. La receta la escribiste tú. El menú es cuando cualquier mesero puede tomar la orden y la cocina sabe qué hacer sin que tú estés en el salón.

Cuando el sistema ya funciona, le pides a Claude Code que lo convierta en un skill:

Escribe esto en el chat
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:

Código
.claude/skills/lead-generation-system/
├── SKILL.md          ← descripción + instrucciones para Claude
├── scripts/
│   └── lead_generation.py
└── references/
    ├── requirements.txt
    └── sample_output.csv

El 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

  1. 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)
  2. Instala las dependencias: pip install apify-client anthropic requests beautifulsoup4
  3. 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)
  4. Ejecútalo con count=10 como prueba y asegúrate de que el CSV se genera bien
  5. Abre el CSV en Google Sheets y revisa que todas las columnas estén en su lugar
  6. Pídele a Claude Code que empaque el sistema en un skill
  7. 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

🎨 Imagínalo así: no filtrar los leads antes del enrichment es como procesar el mineral junto con la grava. Más caro, más lento, y la mitad del esfuerzo se va a la basura. Primero filtra; enriquece solo lo que vale.

  • 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=10 como 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


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 fieldnames de forma explícita y usa lead.get(field, ""); así evitas que el teléfono termine en la columna de ubicación.


Siguiente lección

→ Precios: value-based pricing

La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso