Exploraciones

Scraping del Senado mexicano

Usando Julia para hacer scraping de información útil del sitio web del Senado mexicano.

Haciendo scraping de información útil del Senado mexicano

En agosto de 2017 el Senado mexicano recibió un premio por cumplir con sus obligaciones de transparencia. Esto tiene que ver sobre todo con que todos los días se publica una enorme cantidad de información en el sitio web del Senado.

Pues sí... la información es muchísima, pero es imposible de manejar para la mayoría de los usuarios, porque no muestra ninguna estadística importante y los datos útiles están muy dispersos por todo el sitio. ¡Aquí es donde nuestras habilidades de scraping nos salvan el día! Si juntamos la información en formatos manejables, entonces podemos reacomodarla para sacar algunas conclusiones.

Lo primero que tenemos que hacer para hacer scraping de un sitio web con éxito es visitarlo y estudiarlo para descubrir qué cosas queremos ver. En este caso vamos a separar la información en ciertos temas principales:

  • Senadores
    • Nombre
    • Estado (entidad)
    • Partido
    • Suplente
  • Asistencia
    • Presente
    • Ausente
    • Ausencia justificada
    • Ausente por comisión oficial
  • Comisiones
    • 96 comisiones
  • Votaciones
    • A favor
    • En contra
    • Abstención
    • Ausente
  • Edictos (iniciativas)

Para esta tarea vamos a usar JuliaLang, con ayuda de 3 paquetes maravillosos.

  • Requests
  • Gumbo
  • Cascadia

Requests: para descargar el código de un sitio web. Gumbo: un parser de HTML. Cascadia: un selector de CSS.

Entonces el pipeline que vamos a seguir es: descargar el texto crudo del archivo -> convertirlo a HTML -> usar la estructura de árbol del HTML para descargar la información que necesitamos.

Para aprovechar las capacidades de paralelización que Julia trae integradas, escribí todo como funciones, así podía usar la función map. Puedes ver todas estas funciones aquí mismo.

Ahora veamos como ejemplo cómo haríamos scraping de la información de los senadores (nombre, entidad, partido y suplente), que está en este archivo.

{% highlight julia %} #!/usr/bin/env julia

@everywhere alternates = 640:767

@everywhere using Requests @everywhere using Cascadia

@everywhere include("scraping_functions.jl")

pmap(senators_and_alternates,alternates) {% endhighlight %}

Lo primero que hay que notar es el rango 640:767. Estos son los id de los suplentes en el sitio web, y vamos a usar estos en lugar de los de los titulares porque desde la página del senador titular no hay un link hacia su suplente, pero al revés sí. Luego, como estamos usando varios workers (paralelización), tenemos que importar cada función y cada paquete en cada uno de ellos, y para eso sirve el macro @everywhere. Y pmap es un map en paralelo de la función senators_and_alternates sobre el arreglo alternates.

Pero, ¿qué hay dentro de la función senators_and_alternates?

{% highlight julia %} function senators_and_alternates(id::Int64) tit_id, name, party, state, comissions, alt_id, alternate = names_and_representations(id) println(tit_id, "|", name, "|", party, "|", state, "|", alt_id, "|", alternate) end {% endhighlight %}

Esta imprime en el STDOUT un CSV separado por pipes, pero está llamando a otra función que recibe el id como argumento y regresa varios campos como resultado para ese id en particular.

{% highlight julia %} function names_and_representations(id::Int64) tit_id, alt_id, alt_name, alt_com = alternate_info(id) h = get_info(tit_id) qs = matchall(Selector("div table strong"),h.root) pty = matchall(Selector("div table tr td table img"),h.root) alt = matchall(Selector("div span b"),h.root) com = matchall(Selector("div ul li a[href^='index.php?watch=9&sm=100&id']"),h.root) name = giveme_names(qs) state = giveme_states(qs) party = giveme_party(pty) alternate = giveme_alternate(alt) if giveme_comissions(com) != nothing comissions = giveme_comissions(com) else comissions = alt_com end tit_id, name, party, state, comissions, alt_id, alternate end {% endhighlight %}

¡Aquí es donde pasa el scraping de los senadores! Pero, como dije antes, usa datos de la página del suplente. Aquí podemos ver cómo se usan Selector y los matches del paquete Cascadia, aprovechando la estructura de árbol del documento HTML, como mencioné antes.

Por último, echemos un ojo a la función get_info.

{% highlight julia %} function get_info(id::Int64) r = get("https://www.senado.gob.mx/index.php?watch=8&id=$id") h = parsehtml(String(copy(r.data))) h end {% endhighlight %}

Esta función es muy importante porque es la que descarga los archivos del sitio web. El argumento que recibe es el id del senador o del suplente, descarga esa página específica con el paquete Requests y luego la convierte a HTML usando Gumbo.

Ahora que entendimos cómo funciona esto, tenemos que correr el scraper diciéndole a la computadora cuántos workers queremos, y para eso podemos escribir un script sencillo de bash.

{% highlight bash %} #! /usr/bin/env bash

procs=grep -c ^processor /proc/cpuinfo

julia -p $procs get_names_and_reps.jl | egrep -o "[0-9]{3}.*" {% endhighlight %}

Lo que hace es ver cuántos procesadores tiene tu computadora y usarlos todos para hacer scraping del Senado. Pero tenemos que limpiar un poco la salida, porque no nos interesa saber qué worker hizo qué, y para eso sirve el egrep.

¡Pues eso es todo lo que necesitas saber para hacer scraping del sitio web del Senado mexicano! Si quieres la información pero no quieres escribir código, siempre puedes usar el mío, que está justo en este repo.