Scraping y Recolección de Datos
Vista General
Sección titulada «Vista General»El Observatorio extrae datos legislativos de dos cámaras, cada una con un enfoque fundamentalmente distinto. La Cámara de Diputados expone un portal de datos abiertos sin protección anti-bot, por lo que un cliente HTTP estándar es suficiente. El Senado está protegido por el WAF Incapsula de Imperva, que detecta y bloquea solicitudes automatizadas mediante TLS fingerprinting. Extraer datos del Senado requirió construir un cliente anti-WAF especializado.
Dos cámaras, dos stacks:
| Cámara | Cliente | Dificultad | Razón |
|---|---|---|---|
| Diputados | httpx | Baja | Portal de datos abiertos, sin protección |
| Senado | curl_cffi | Alta | WAF Incapsula con TLS fingerprinting |
Fuentes de Datos
Sección titulada «Fuentes de Datos»| Fuente | Cámara | URL Base | Método | Volumen |
|---|---|---|---|---|
| Datos Abiertos | Diputados | datos.abiertos.diputados.gob.mx | httpx + delay | ~4,600 eventos de votación |
| Portal LXVI | Senado | senado.gob.mx/66/ | curl_cffi + TLS impersonation | 5,047 eventos de votación |
| Directorio XLS | Senado | Archivos XLS oficiales | pandas read_excel | Legislaturas LVIII-LXV |
Scraper de Diputados
Sección titulada «Scraper de Diputados»El scraper de Diputados apunta al portal de datos abiertos en datos.abiertos.diputados.gob.mx. No existe protección anti-bot, por lo que el stack es mínimo:
- Cliente HTTP: httpx con un delay configurable entre solicitudes (por defecto 2.0 segundos)
- Parser: BeautifulSoup para parseo HTML donde no hay endpoints JSON disponibles
- Alcance de datos: registros de votación identificados por SITL IDs, perfiles de legisladores y afiliaciones partidistas
El scraper obtiene aproximadamente 4,600 eventos de votación. Todos los datos se cargan en una base de datos SQLite con deduplicación mediante el campo source_id en cada registro vote_event.
Scraper del Senado — Caso de Estudio Anti-WAF
Sección titulada «Scraper del Senado — Caso de Estudio Anti-WAF»El Problema
Sección titulada «El Problema»El portal del Senado en senado.gob.mx está protegido por Incapsula (WAF de Imperva). Los clientes HTTP estándar — requests de Python, httpx, incluso curl — son bloqueados inmediatamente. El WAF detecta tráfico automatizado a través de tres mecanismos:
- TLS fingerprinting: El hash JA3 del handshake TLS identifica clientes que no son navegadores reales
- Desafíos JavaScript: Incapsula sirve JS que los navegadores reales ejecutan automáticamente
- Análisis conductual: Se monitorean los patrones de solicitudes, timing y comportamiento de cookies
Las primeras dos iteraciones del scraper del Senado fueron bloqueadas en cuestión de minutos.
La Solución
Sección titulada «La Solución»El SenadoLXVIClient en senado/scrapers/shared/client.py usa curl_cffi con TLS fingerprint impersonation. Esta biblioteca envuelve libcurl-impersonate, que puede reproducir el handshake TLS exacto de navegadores reales — coincidiendo con los hashes JA3, suites de cifrado y extensiones.
class SenadoLXVIClient: _IMPERSONATE_TARGETS: tuple[BrowserTypeLiteral, ...] = ( "chrome", "safari", "chrome116", "chrome131", "edge", "chrome_android", )
MAX_REQUESTS_PER_SESSION: int = 10 WAF_CONSECUTIVE_THRESHOLD = 2Pool de Fingerprints
Sección titulada «Pool de Fingerprints»Seis objetivos de impersonación rotan entre sesiones. Cada objetivo presenta un hash JA3 distinto al WAF:
| Objetivo | Perfil |
|---|---|
chrome | Chrome desktop (última versión) |
safari | Safari desktop |
chrome116 | Chrome 116 desktop |
chrome131 | Chrome 131 desktop |
edge | Edge desktop |
chrome_android | Chrome mobile |
Estrategia de Gestión de Sesiones
Sección titulada «Estrategia de Gestión de Sesiones»El cliente usa una estrategia de sesiones por capas diseñada para minimizar la detección del WAF mientras se recupera de forma graceful ante bloqueos:
- Sesión activa: fingerprint fijo del pool, cookies persistentes compartidas entre solicitudes dentro de la sesión
- Bloqueo del WAF detectado: cerrar la sesión inmediatamente, descartar todas las cookies (las cookies quemadas cargan flags del WAF)
- Nueva sesión: rotar al siguiente fingerprint del pool, realizar una solicitud GET de warm-up para poblar cookies frescas antes de scrapear
- Rotación proactiva: rotar la sesión cada 10 solicitudes (
MAX_REQUESTS_PER_SESSION) antes de que el WAF pueda detectar el patrón
Circuit Breaker
Sección titulada «Circuit Breaker»Un circuit breaker rastrea los bloqueos consecutivos del WAF. Tras WAF_CONSECUTIVE_THRESHOLD (2) bloqueos consecutivos, la sesión se declara quemada. El cliente lanza SessionBurnedError, fuerza una pausa obligatoria y debe reiniciarse con una sesión nueva.
Esto evita que el scraper golpee el WAF con solicitudes que nunca van a succeed.
Procedimiento de Warm-up
Sección titulada «Procedimiento de Warm-up»Tras crear una nueva sesión, el cliente emite una solicitud GET dummy al portal antes de hacer cualquier solicitud de datos real. Esta solicitud de warm-up permite que Incapsula establezca sus cookies de desafío. Una sesión fría sin cookies es bloqueada mucho más agresivamente que una que ya pasó el desafío JS inicial.
Resultados
Sección titulada «Resultados»| Métrica | Cantidad |
|---|---|
| Eventos de votación scrapeados | 5,047 |
| Perfiles de senadores scrapeados | 1,754 |
| Iteraciones hasta funcionar | 3 |
Diagrama de Estrategia Anti-WAF
Sección titulada «Diagrama de Estrategia Anti-WAF»Request → Check Cache ├─ Cache Hit → Return cached data └─ Cache Miss → Send via curl_cffi ├─ Response OK → Cache + Return └─ WAF Detected (Incapsula markers) ├─ Consecutive < 2 → New session, rotate fingerprint, warm-up, retry └─ Consecutive ≥ 2 → SessionBurnedError → Pause + restartLa capa de cache no es opcional. Cada página cacheada es una solicitud menos al portal del Senado, lo que significa una oportunidad menos para que el WAF detecte y bloquee el scraper. En ejecuciones repetidas, el cache reduce drásticamente la exposición.
Calidad y Procesamiento de Datos
Sección titulada «Calidad y Procesamiento de Datos»Deduplicación
Sección titulada «Deduplicación»Cada registro vote_event tiene un campo source_id que mapea al identificador original del portal fuente. Esto permite scraping idempotente: ejecutar el scraper múltiples veces no crea registros duplicados. El patrón INSERT OR IGNORE de SQLite sobre source_id maneja esto a nivel de base de datos.
Enriquecimiento de Perfiles
Sección titulada «Enriquecimiento de Perfiles»Los perfiles de legisladores se enriquecen con datos demográficos y electorales:
- Género: 480 mujeres / 598 hombres (en todos los registros cargados)
- Tipo de escaño: MR (mayoría relativa) o PL (representación proporcional)
- Circunscripción: Asignación de distrito electoral para escaños PL
Normalización de Partidos
Sección titulada «Normalización de Partidos»La función normalize_party() mapea los valores mixtos de vote.group que devuelven los portales a IDs canónicos de organización. Los nombres crudos de partidos en los datos fuente son inconsistentes — las abreviaturas varían, las coaliciones crean nombres compuestos y los partidos históricos tienen múltiples etiquetas. La normalización colapsa todas las variantes a un único ID canónico.
Resolución de Membresías
Sección titulada «Resolución de Membresías»Algunos legisladores tienen membresías multipartidistas a lo largo de su carrera. El scraper resuelve esto por frecuencia de votación: el legislador se asigna al partido donde emitió más votos. Esta es una heurística pragmática — maneja correctamente cambios de partido y expulsiones sin requerir desambiguación manual.
Lecciones Aprendidas
Sección titulada «Lecciones Aprendidas»-
TLS fingerprinting es el mecanismo principal de detección de bots para WAFs como Incapsula. Los headers y user-agent strings son fáciles de spoofear; el hash JA3 del handshake TLS no lo es. Bibliotecas como
curl_cffique pueden impersonar stacks TLS de navegadores reales son esenciales. -
La rotación proactiva supera a la rotación reactiva. Rotar sesiones antes de que el WAF detecte un patrón es mucho más efectivo que rotar después de un bloqueo. El límite de 10 solicitudes por sesión es conservador pero confiable.
-
La gestión de cookies importa. Las cookies quemadas cargan flags del WAF. Descartarlas completamente y empezar de cero es mejor que intentar “reparar” una sesión flaggeada.
-
Las solicitudes de warm-up son esenciales. Una sesión fría sin las cookies de desafío de Incapsula es bloqueada en la primera solicitud real. El GET de warm-up puebla las cookies necesarias.
-
El cache reduce la exposición. Cada página cacheada es una solicitud menos al portal. Para un scraper operando detrás de un WAF, minimizar el total de solicitudes es una estrategia de supervivencia, no solo una optimización de rendimiento.
-
El scraper del Senado requirió tres iteraciones para funcionar correctamente. Las primeras dos fueron bloqueadas en minutos. La tercera iteración introdujo curl_cffi, rotación de fingerprints y gestión proactiva de sesiones — y ha funcionado de forma confiable desde entonces.