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

CapaRolEvidencia que produce
Fuente institucionalPáginas HTML y vista AJAX bajo /66/.HTML de votación, fragmentos AJAX, páginas de perfil.
Cliente ScrapyRecorre IDs de votación o perfiles y conserva contexto de request.Respuestas asociadas a vote_id o senador_id.
Mitigación anti-WAFUsa scrapy-impersonate / curl_cffi para TLS fingerprinting.Requests con impersonación de navegador.
ParsingExtrae metadatos temporales, votos nominales y perfiles disponibles.Items VotacionItem, VotoNominalItem, SenadorItem.
PersistenciaInserta/upserta en SQLite local.Tablas de votaciones, votos nominales y senadores.
ValidaciónLee 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.ImpersonateDownloadHandler para 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.