🌐 ES
Chrome Añadir a Chrome (es gratis)
🎯 Lead Generation 📅 22 Enero 2025 ⏱️ 20 min

Google Maps Leads: Cómo Verificar y Validar Datos para 95% de Precisión 2025

En esta guía de verificación de leads aprenderás:

El Problema Oculto: Datos "Sucios" Matan Tu Conversion

Extraes 1,000 google maps leads con tu maps scraper favorito. Te emocionas. Empiezas outreach y...

Realidad brutal:

Total leads realmente utilizables: 325 de 1,000 (32.5%)

Desperdiciaste 67.5% de tu tiempo en leads basura.

Solución: Verificación y validación sistemática ANTES de contactar. Para entender cómo construir un sistema completo de generación de leads, consulta nuestra guía sobre cómo crear un pipeline completo de Google Maps leads finder.

Las 7 Capas de Validación de Google Maps Leads

🎯

Sistema de 7 Capas de Validación

Implementa nuestro sistema completo y alcanza 95% de precisión en tus leads de Google Maps.

🚀 Empezar Ahora

Un sistema completo de validación tiene 7 capas progresivas:

Capa 1: Deduplicación (Elimina 10-20% de waste)

Problema: Mismo negocio aparece múltiples veces con pequeñas variaciones:

Solución - Algoritmo de deduplicación:

import pandas as pd
from fuzzywuzzy import fuzz
import phonenumbers

def normalize_phone(phone, country='ES'):
    """Normaliza teléfono a formato internacional"""
    try:
        parsed = phonenumbers.parse(phone, country)
        return phonenumbers.format_number(
            parsed,
            phonenumbers.PhoneNumberFormat.E164
        )
    except:
        return None

def are_duplicates(lead1, lead2, threshold=85):
    """Detecta si dos leads son duplicados"""

    # Nivel 1: Teléfono exacto
    if lead1['phone'] and lead2['phone']:
        phone1 = normalize_phone(lead1['phone'])
        phone2 = normalize_phone(lead2['phone'])
        if phone1 == phone2 and phone1 is not None:
            return True

    # Nivel 2: Nombre similar + ciudad igual
    name_similarity = fuzz.ratio(
        lead1['name'].lower(),
        lead2['name'].lower()
    )
    same_city = lead1.get('city', '').lower() == lead2.get('city', '').lower()

    if name_similarity > threshold and same_city:
        return True

    # Nivel 3: Dirección muy similar
    if lead1.get('address') and lead2.get('address'):
        address_similarity = fuzz.ratio(
            lead1['address'].lower(),
            lead2['address'].lower()
        )
        if address_similarity > 90:
            return True

    return False

def deduplicate_leads(df):
    """Elimina duplicados de dataframe de leads"""
    keep_indices = []
    duplicates_found = 0

    for i, lead1 in df.iterrows():
        is_duplicate = False

        for j in keep_indices:
            lead2 = df.loc[j]
            if are_duplicates(lead1.to_dict(), lead2.to_dict()):
                is_duplicate = True
                duplicates_found += 1
                break

        if not is_duplicate:
            keep_indices.append(i)

    print(f"Duplicados eliminados: {duplicates_found}")
    return df.loc[keep_indices]

# Uso
leads_df = pd.read_csv('google_maps_leads.csv')
leads_clean = deduplicate_leads(leads_df)
leads_clean.to_csv('leads_deduplicated.csv', index=False)

Resultado esperado: De 1,000 leads → 800-900 únicos (eliminas 100-200 duplicados)

Si quieres profundizar en técnicas de extracción masiva de datos, lee nuestra guía sobre extracción masiva de emails y teléfonos de Google Maps.

Capa 2: Validación de Formato de Teléfono (Elimina 5-15%)

Problema: Teléfonos con formato incorrecto o incompleto:

Solución - Validación con phonenumbers:

import phonenumbers
from phonenumbers import NumberParseException

def validate_phone(phone, country='ES'):
    """Valida si teléfono es válido y posible"""
    if not phone or pd.isna(phone):
        return {'valid': False, 'reason': 'Empty'}

    try:
        parsed = phonenumbers.parse(phone, country)

        # Check si es posible (formato correcto)
        if not phonenumbers.is_possible_number(parsed):
            return {'valid': False, 'reason': 'Invalid format'}

        # Check si es válido (existe en plan de numeración)
        if not phonenumbers.is_valid_number(parsed):
            return {'valid': False, 'reason': 'Not valid in numbering plan'}

        # Normalized format
        normalized = phonenumbers.format_number(
            parsed,
            phonenumbers.PhoneNumberFormat.E164
        )

        return {
            'valid': True,
            'normalized': normalized,
            'type': phonenumbers.number_type(parsed)  # MOBILE, FIXED_LINE, etc.
        }

    except NumberParseException as e:
        return {'valid': False, 'reason': str(e)}

# Aplicar a dataframe
def validate_phones_bulk(df):
    """Valida todos los teléfonos en dataframe"""
    results = []

    for idx, row in df.iterrows():
        validation = validate_phone(row['phone'])
        results.append({
            'original_phone': row['phone'],
            'valid': validation['valid'],
            'normalized': validation.get('normalized', ''),
            'reason': validation.get('reason', '')
        })

    results_df = pd.DataFrame(results)

    # Merge back
    df = df.merge(results_df, left_on='phone', right_on='original_phone')

    # Filtra solo válidos
    df_valid = df[df['valid'] == True]

    invalid_count = len(df) - len(df_valid)
    print(f"Teléfonos inválidos eliminados: {invalid_count}")

    return df_valid

leads_validated = validate_phones_bulk(leads_clean)

Capa 3: Verificación de Email (Elimina 20-40%)

Problema: Emails inválidos, desactualizados o inexistentes.

Tipos de validación:

Nivel 1: Validación de Sintaxis (gratis, instant)

import re

def validate_email_syntax(email):
    """Valida sintaxis básica de email"""
    if not email or pd.isna(email):
        return False

    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

# Uso
df['email_valid_syntax'] = df['email'].apply(validate_email_syntax)
df_syntax_valid = df[df['email_valid_syntax'] == True]

Nivel 2: Verificación MX Record (gratis, requiere DNS lookup)

import dns.resolver

def verify_mx_record(email):
    """Verifica si dominio tiene MX record (puede recibir emails)"""
    if not email:
        return False

    domain = email.split('@')[1]

    try:
        dns.resolver.resolve(domain, 'MX')
        return True
    except:
        return False

df['email_mx_valid'] = df['email'].apply(verify_mx_record)

Nivel 3: Verificación SMTP (más preciso, pero lento)

Usa servicios como:

import requests

def verify_email_zerobounce(email, api_key):
    """Verifica email con ZeroBounce API"""
    url = f"https://api.zerobounce.net/v2/validate"
    params = {
        'api_key': api_key,
        'email': email
    }

    response = requests.get(url, params=params)
    data = response.json()

    return {
        'status': data.get('status'),  # valid, invalid, catch-all, unknown
        'sub_status': data.get('sub_status'),
        'free_email': data.get('free_email'),  # gmail, yahoo, etc.
        'score': data.get('zerobounce_qual_score')
    }

# Batch verification para ahorrar tiempo
def verify_emails_batch(emails, api_key):
    """Verifica múltiples emails (max 100 a la vez)"""
    results = []

    # Procesa en batches de 100
    for i in range(0, len(emails), 100):
        batch = emails[i:i+100]

        for email in batch:
            result = verify_email_zerobounce(email, api_key)
            results.append(result)

        # Rate limiting
        time.sleep(1)

    return results

Resultado: De 500 emails → 300-400 válidos y utilizables

Para enriquecer aún más tus leads con información de contacto verificada, te recomendamos nuestra guía sobre cómo enriquecer leads de Google Maps con email finder y phone validator.

Capa 4: Verificación de Negocio Activo (Elimina 5-10%)

Problema: Negocios cerrados permanentemente pero aún en Google Maps.

Señales de negocio cerrado:

Detección automática:

import requests
from datetime import datetime, timedelta

def check_business_active(lead):
    """Verifica si negocio sigue activo"""
    signals = {
        'website_active': check_website(lead['website']),
        'recent_reviews': has_recent_reviews(lead),
        'phone_connected': test_phone_connection(lead['phone']),
        'google_status': lead.get('status', '').lower() != 'cerrado permanentemente'
    }

    # Scoring
    active_score = sum([
        signals['website_active'] * 30,
        signals['recent_reviews'] * 35,
        signals['phone_connected'] * 20,
        signals['google_status'] * 15
    ])

    return {
        'likely_active': active_score > 50,
        'confidence': active_score,
        'signals': signals
    }

def check_website(url):
    """Check si website está activo"""
    if not url:
        return False  # No website = no signal

    try:
        response = requests.head(url, timeout=5, allow_redirects=True)
        return response.status_code == 200
    except:
        return False

def has_recent_reviews(lead):
    """Check si tiene reseñas recientes"""
    last_review_date = lead.get('last_review_date')

    if not last_review_date:
        return True  # No data = benefit of doubt

    # Parse date
    last_review = datetime.strptime(last_review_date, '%Y-%m-%d')
    cutoff = datetime.now() - timedelta(days=365)  # 1 año

    return last_review > cutoff

# Aplicar a todos los leads
df['business_active_check'] = df.apply(check_business_active, axis=1)
df_active = df[df['business_active_check'].apply(lambda x: x['likely_active'])]

Capa 5: Validación de Dirección (Mejora deliverability)

Si planeas envío postal o necesitas precisión geográfica:

from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut

def validate_address(address, city, country='España'):
    """Valida que dirección sea real y geocodifiable"""
    full_address = f"{address}, {city}, {country}"

    geolocator = Nominatim(user_agent="lead_validator")

    try:
        location = geolocator.geocode(full_address, timeout=10)

        if location:
            return {
                'valid': True,
                'latitude': location.latitude,
                'longitude': location.longitude,
                'formatted_address': location.address
            }
        else:
            return {'valid': False, 'reason': 'Address not found'}

    except GeocoderTimedOut:
        return {'valid': False, 'reason': 'Timeout'}
    except Exception as e:
        return {'valid': False, 'reason': str(e)}

Capa 6: Validación de ICP Fit (Elimina 40-60% para mejor targeting)

No es que datos sean inválidos, sino que lead no encaja en tu ICP.

Criterios de filtrado:

def fits_icp(lead, icp_criteria):
    """Verifica si lead encaja en Ideal Customer Profile"""
    score = 0

    # Rating range
    if icp_criteria['min_rating'] <= lead['rating'] <= icp_criteria['max_rating']:
        score += 25
    else:
        return False  # Hard filter

    # Review count range
    if icp_criteria['min_reviews'] <= lead['reviews_count'] <= icp_criteria['max_reviews']:
        score += 25

    # Tiene website?
    if icp_criteria['requires_website'] and lead['website']:
        score += 20
    elif not icp_criteria['requires_website']:
        score += 20

    # Categoría exacta?
    if lead['category'] in icp_criteria['target_categories']:
        score += 30

    return score >= icp_criteria['min_score']

# Define tu ICP
my_icp = {
    'min_rating': 3.8,
    'max_rating': 4.5,
    'min_reviews': 50,
    'max_reviews': 500,
    'requires_website': True,
    'target_categories': ['clínica dental', 'dentista', 'odontología'],
    'min_score': 70
}

# Filtra
df_icp_fit = df[df.apply(lambda x: fits_icp(x.to_dict(), my_icp), axis=1)]
print(f"Leads que encajan en ICP: {len(df_icp_fit)} de {len(df)}")

Capa 7: Enriquecimiento para Mayor Confianza

Añade datos adicionales para validar que lead es real:

import whois
from datetime import datetime

def enrich_with_domain_age(url):
    """Obtiene edad del dominio"""
    if not url:
        return None

    try:
        domain = url.replace('https://', '').replace('http://', '').split('/')[0]
        w = whois.whois(domain)

        creation_date = w.creation_date
        if isinstance(creation_date, list):
            creation_date = creation_date[0]

        age_days = (datetime.now() - creation_date).days
        age_years = age_days / 365

        return {
            'domain': domain,
            'created': creation_date.strftime('%Y-%m-%d'),
            'age_years': round(age_years, 1),
            'trustworthy': age_years > 1  # Dominios >1 año más confiables
        }
    except:
        return None

df['domain_info'] = df['website'].apply(enrich_with_domain_age)

Pipeline Completo de Validación (Script Automatizado)

Script Automatizado Completo

Pipeline de validación en Python listo para usar. 7 capas de limpieza automática.

🚀 Empezar Ahora

Juntamos las 7 capas en un script único:

import pandas as pd

class LeadValidator:
    def __init__(self, icp_criteria, api_keys=None):
        self.icp = icp_criteria
        self.api_keys = api_keys or {}
        self.stats = {
            'input_count': 0,
            'duplicates_removed': 0,
            'invalid_phones': 0,
            'invalid_emails': 0,
            'inactive_businesses': 0,
            'icp_filtered': 0,
            'output_count': 0
        }

    def run_full_validation(self, input_csv, output_csv):
        """Ejecuta pipeline completo de validación"""
        print("=== Lead Validation Pipeline ===\n")

        # Load
        df = pd.read_csv(input_csv)
        self.stats['input_count'] = len(df)
        print(f"Loaded {len(df)} leads\n")

        # Layer 1: Deduplication
        print("[1/7] Deduplicating...")
        df = self.deduplicate(df)
        print(f"  Removed: {self.stats['duplicates_removed']} duplicates")
        print(f"  Remaining: {len(df)}\n")

        # Layer 2: Phone validation
        print("[2/7] Validating phones...")
        df = self.validate_phones(df)
        print(f"  Removed: {self.stats['invalid_phones']} invalid phones")
        print(f"  Remaining: {len(df)}\n")

        # Layer 3: Email validation
        print("[3/7] Validating emails...")
        df = self.validate_emails(df)
        print(f"  Removed: {self.stats['invalid_emails']} invalid emails")
        print(f"  Remaining: {len(df)}\n")

        # Layer 4: Business active check
        print("[4/7] Checking if businesses are active...")
        df = self.check_active_businesses(df)
        print(f"  Removed: {self.stats['inactive_businesses']} inactive")
        print(f"  Remaining: {len(df)}\n")

        # Layer 5: Address validation (optional, skip si no necesario)
        # df = self.validate_addresses(df)

        # Layer 6: ICP filtering
        print("[5/7] Filtering by ICP criteria...")
        df = self.filter_by_icp(df)
        print(f"  Removed: {self.stats['icp_filtered']} outside ICP")
        print(f"  Remaining: {len(df)}\n")

        # Layer 7: Enrichment
        print("[6/7] Enriching data...")
        df = self.enrich(df)
        print(f"  Enrichment completed\n")

        # Save
        print("[7/7] Saving validated leads...")
        df.to_csv(output_csv, index=False)
        self.stats['output_count'] = len(df)

        print(f"\n=== Validation Complete ===")
        print(f"Input: {self.stats['input_count']} leads")
        print(f"Output: {self.stats['output_count']} leads")
        print(f"Quality rate: {self.stats['output_count']/self.stats['input_count']*100:.1f}%")

        return df

    def deduplicate(self, df):
        """Layer 1: Remove duplicates"""
        initial = len(df)
        df = deduplicate_leads(df)  # Función definida arriba
        self.stats['duplicates_removed'] = initial - len(df)
        return df

    def validate_phones(self, df):
        """Layer 2: Validate phone numbers"""
        initial = len(df)
        df = validate_phones_bulk(df)  # Función definida arriba
        self.stats['invalid_phones'] = initial - len(df)
        return df

    # ... (implementa resto de métodos)

# Uso
validator = LeadValidator(
    icp_criteria={
        'min_rating': 3.8,
        'max_rating': 4.5,
        'min_reviews': 50,
        'max_reviews': 500,
        'requires_website': True,
        'target_categories': ['clínica dental'],
        'min_score': 70
    },
    api_keys={
        'zerobounce': 'tu_api_key',
        'hunter': 'tu_api_key'
    }
)

validated_leads = validator.run_full_validation(
    input_csv='google_maps_leads_raw.csv',
    output_csv='google_maps_leads_validated.csv'
)

Output típico:

=== Lead Validation Pipeline ===

Loaded 1000 leads

[1/7] Deduplicating...
  Removed: 127 duplicates
  Remaining: 873

[2/7] Validating phones...
  Removed: 98 invalid phones
  Remaining: 775

[3/7] Validating emails...
  Removed: 215 invalid emails
  Remaining: 560

[4/7] Checking if businesses are active...
  Removed: 54 inactive
  Remaining: 506

[5/7] Filtering by ICP criteria...
  Removed: 189 outside ICP
  Remaining: 317

[6/7] Enriching data...
  Enrichment completed

[7/7] Saving validated leads...

=== Validation Complete ===
Input: 1000 leads
Output: 317 leads
Quality rate: 31.7%

Herramientas de Verificación Recomendadas

Para Verificación de Emails:

Herramienta Precio Precisión Velocidad
ZeroBounce $16/1k emails 98% Rápida (API)
NeverBounce $8/1k emails 97% Rápida (API)
Hunter.io Verifier Incluido en plan 95% Media
Verificación manual SMTP Gratis 90% Muy lenta

Para Verificación de Teléfonos:

Para Enriquecimiento de Datos:

🛠️

Herramientas Verificación €80/Mes

ZeroBounce + NeverBounce + Twilio. Stack completo para 95% precisión.

Empezar validación 🔍

Checklist Manual de 15 Puntos (Para Hot Leads)

Para tus top 20-50 hot leads, vale la pena verificación manual rápida (5 min por lead):

Datos de Contacto:

  1. ☐ Teléfono tiene formato correcto y completo
  2. ☐ Email tiene sintaxis válida (no typos obvios)
  3. ☐ Website carga correctamente (no 404)

Negocio Activo:

  1. ☐ Google Maps no dice "Cerrado permanentemente"
  2. ☐ Última reseña <12 meses atrás
  3. ☐ Website tiene contenido actualizado recientemente
  4. ☐ Redes sociales activas (<30 días última publicación)

ICP Fit:

  1. ☐ Categoría correcta (exacta, no aproximada)
  2. ☐ Rating en rango objetivo (ej: 3.8-4.5)
  3. ☐ Reviews en rango objetivo (ej: 50-500)
  4. ☐ Ubicación en zona target

Señales de Presupuesto:

  1. ☐ Website profesional (no template gratis obvio)
  2. ☐ Fotos profesionales en Google Maps (>20 fotos)
  3. ☐ Responde a reseñas regularmente (>50% de respuestas)

Pain Point Identificable:

  1. ☐ Puedes identificar 1-2 pain points específicos en <5 min (website antiguo, mala visibilidad local, quejas en reseñas, etc.)

Si 12+ de 15 checks pasan → Lead validado, contactar YA.

Caso Real: De 32% a 94% de Tasa de Contacto Exitoso

Agencia de Marketing - Madrid

ANTES de validación sistemática:

DESPUÉS de implementar pipeline de validación:

ROI de validación:

Quote del head of sales:

"Validar leads antes de contactar cambió todo. Nuestro equipo dejó de perder tiempo con números equivocados y emails rebotados. Ahora 94 de cada 100 llamadas son a negocios reales, y conversion se triplicó."

📊

32% → 94% Tasa Contacto Exitoso

ROI 16,650%. Validación cambia todo. Ahorra 35h/mes + €12k/mes revenue adicional.

Replicar estrategia 💪

Conclusión: Calidad > Cantidad en Google Maps Leads

Extraer 10,000 google maps leads con tu google maps leads generator favorito es fácil.

Lo difícil (y valioso) es tener 500 leads VERIFICADOS Y VÁLIDOS.

Regla de oro:

Mismo resultado final (300-470 leads útiles), pero con validación:

Invierte 2 horas validando. Ahorra 40 horas desperdiciadas.

Una vez que tengas tus leads validados, el siguiente paso es convertirlos en clientes. Aprende las mejores prácticas en nuestro artículo sobre estrategias de conversión y cierre de maps leads. También puedes explorar nuestro guía definitiva del Google Maps scraper para optimizar tu extracción desde el inicio.

💎

Extrae Leads con 95% de Precisión

MapiLeads + validación sistemática = datos limpios y verificados. Ahorra 40+ horas al mes.

🚀 Empezar Ahora