Pipeline de extracción
El pipeline de camara-senadores-mex ejecuta dos recorridos principales: votaciones nominales y perfiles de senadores. Ambos usan Scrapy, pero tienen fuentes y criterios de aceptación distintos.
IDs de votación
│
▼
/66/votacion/{id}
│
├── parsing temporal
│
└── POST viewTableVot.php
│
▼
filas nominales
│
▼
SQLite: votaciones + votos_nominales
│
▼
IDs de senador observados
│
▼
/66/senador/{id}
│
▼
SQLite: senadores disponibles
1. Selección de IDs
El spider de votaciones acepta dos modos de entrada:
| Modo | Comportamiento |
|---|---|
max_id | Itera range(1, max_id + 1). |
ids | Usa una lista explícita separada por comas. |
Esto permite tanto crawls masivos como recrawls selectivos de casos concretos.
Ejemplos operativos documentados por el propio spider:
scrapy crawl votaciones
scrapy crawl votaciones -a max_id=50
scrapy crawl votaciones -a ids=347,891,2103,2789,3671,4256,4890
El recorrido autorizado del dataset final usó el rango [1, 5000]; la documentación del pipeline no inventa filtros adicionales sobre legislatura, partido o tipo de votación.
2. Request HTML de votación
Para cada ID, Scrapy solicita:
https://www.senado.gob.mx/66/votacion/{id}
La request incluye impersonación de navegador en meta para mitigar el WAF:
meta={"vote_id": vote_id, "impersonate": "chrome131"}
Sobre esa respuesta se intenta extraer metadata temporal:
- legislatura;
- año de ejercicio;
- periodo;
- fecha.
El parser revisa textos directos de <strong> y también fragmentos separados por <br>. Si la legislatura no aparece explícitamente, puede inferirse desde la fecha usando rangos conocidos de LX a LXVI.
3. Request AJAX vigente
Después del HTML inicial, el spider siempre consulta la vista AJAX nominal:
POST https://www.senado.gob.mx/66/app/votaciones/functions/viewTableVot.php
La request usa cuerpo form-urlencoded:
action=ajax&cell=1&order=DESC&votacion={id}&q=
y headers:
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Referer: <url de la página de votación>
La decisión de si una votación tiene datos se toma en parse_votes(), con la respuesta AJAX como evidencia principal. Si no hay votos y tampoco se pudo resolver legislatura, el caso se descarta silenciosamente; si hay votos o metadata legislativa, se emite la votación.
4. Parsing de filas nominales
El parser recorre filas <tr> y exige al menos cuatro celdas. Para cada fila útil:
- toma el nombre del enlace en la segunda celda;
- limpia prefijos como
Sen./Senador/Senadora; - reordena nombres con coma (
Apellido, Nombre→Nombre Apellido); - extrae partido desde la tercera celda;
- une todos los nodos de texto de la cuarta celda para conservar votos multi-nodo;
- extrae
senador_iddesde elhrefde nombre o partido; - emite
VotoNominalItemsolo si haysenador_idy nombre.
La normalización de espacios internos es parte del pipeline para evitar que detalles de HTML fragmentado contaminen la base. No se inventan valores cuando el portal no los entrega.
5. Persistencia conceptual
La persistencia usa un pipeline SQLite:
| Item | Operación conceptual |
|---|---|
VotacionItem | INSERT OR REPLACE en votaciones. |
VotoNominalItem | INSERT OR IGNORE en votos_nominales, apoyado en restricción de unicidad. |
SenadorItem | INSERT OR REPLACE en senadores. |
La base se escribe en lotes con commits periódicos. Esto permite recrawls selectivos sin duplicar votos nominales cuando la restricción de unicidad aplica.
6. Pipeline de perfiles
El spider de senadores parte de la base ya poblada:
SELECT DISTINCT vn.senador_id
FROM votos_nominales vn
LEFT JOIN senadores s ON vn.senador_id = s.id
WHERE s.id IS NULL
ORDER BY vn.senador_id
Con esa lista visita:
https://www.senado.gob.mx/66/senador/{id}
Si la página no contiene la sección de información esperada, el perfil se omite. Si existe, se guardan nombre, sexo inferido desde prefijo, tipo de elección, estado y URL.
7. Validación y límites
El pipeline no termina con una base “perfecta”; termina con una base auditable.
Los límites conocidos forman parte del resultado:
- hay IDs de votación vacíos dentro del rango recorrido;
- no todos los IDs presentes en votos tienen perfil disponible;
- pueden existir partidos o votos vacíos;
- el WAF puede introducir variabilidad de acceso;
- el extractor no debe rellenar faltantes sin evidencia del portal.
La validación posterior lee, cuenta y señala. No debe borrar la historia de extracción ni esconder anomalías que son relevantes para entender la fuente.