Arquitectura del extractor senatorial
camara-senadores-mex es un extractor Scrapy standalone para convertir páginas del Senado mexicano en una base SQLite local. Su responsabilidad termina en la descarga, parseo y persistencia de votaciones nominales y perfiles disponibles; no intenta resolver representación Popolo ni completar artificialmente huecos históricos.
La arquitectura está organizada alrededor de una fuente institucional hostil: el portal publica contenido útil, pero no lo expone como API estable de datos abiertos.
Portal Senado
senado.gob.mx/66/
│
├── /66/votacion/{id}
│ │
│ └── POST AJAX viewTableVot.php
│
└── /66/senador/{id}
│
▼
Scrapy + scrapy-impersonate
│
▼
parsing temporal / votos / perfiles
│
▼
SQLite local: senado.db
Capas
| Capa | Rol | Evidencia que produce |
|---|---|---|
| Fuente institucional | Páginas HTML y vista AJAX bajo /66/. | HTML de votación, fragmentos AJAX, páginas de perfil. |
| Cliente Scrapy | Recorre IDs de votación o perfiles y conserva contexto de request. | Respuestas asociadas a vote_id o senador_id. |
| Mitigación anti-WAF | Usa scrapy-impersonate / curl_cffi para TLS fingerprinting. | Requests con impersonación de navegador. |
| Parsing | Extrae metadatos temporales, votos nominales y perfiles disponibles. | Items VotacionItem, VotoNominalItem, SenadorItem. |
| Persistencia | Inserta/upserta en SQLite local. | Tablas de votaciones, votos nominales y senadores. |
| Validación | Lee la base y contabiliza anomalías sin reescribirla. | Métricas y advertencias auditables. |
Flujo de votación
Para cada votación, el spider parte de la página HTML:
https://www.senado.gob.mx/66/votacion/{id}
Ese primer HTML sirve para recuperar contexto de navegación, cookies y metadatos temporales cuando están presentes. La tabla nominal no se toma como contrato completo desde el HTML inicial: el código vigente siempre ejecuta una segunda solicitud al endpoint AJAX de votos nominales.
El endpoint operativo actual es:
POST https://www.senado.gob.mx/66/app/votaciones/functions/viewTableVot.php
con cuerpo application/x-www-form-urlencoded equivalente a:
action=ajax&cell=1&order=DESC&votacion={id}&q=
y headers relevantes:
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Referer: <url de /66/votacion/{id}>
Esto fija una frontera importante: en la versión documentada aquí, viewTableVot.php no debe describirse como contrato GET. Si existen referencias históricas a GET, solo describen exploración previa o implementaciones antiguas, no el contrato operativo actual.
Flujo de perfiles
Los perfiles se obtienen desde IDs detectados en votos nominales:
https://www.senado.gob.mx/66/senador/{id}
El spider de perfiles no recorre un catálogo universal inventado. Lee los senador_id presentes en votos_nominales que aún no existen en la tabla senadores, intenta abrir la página correspondiente y guarda solo perfiles con sección válida.
Fricción anti-WAF
El portal opera detrás de Incapsula. Por eso el proyecto usa Scrapy con scrapy-impersonate, que integra curl_cffi y permite requests con fingerprint TLS de navegador.
La configuración vigente incluye:
- download handlers de
scrapy_impersonate.ImpersonateDownloadHandlerpara HTTP y HTTPS; - middleware
scrapy_impersonate.RandomBrowserMiddleware; meta={"impersonate": "chrome131"}en requests de votación y AJAX;- cookies habilitadas, retries y throttle manual.
La mitigación anti-WAF no convierte la fuente en estable. Solo permite acceder de forma suficientemente consistente para extraer, persistir y auditar.
Contrato de salida
La salida del extractor es operacional: senado.db. Su lectura correcta depende de conservar los límites de la fuente:
- IDs sin contenido no se rellenan artificialmente.
- Perfiles faltantes no se inventan.
- Valores vacíos se preservan como evidencia de la extracción.
- La capa Popolo queda fuera de este repositorio y consume la base como insumo posterior.