-
Notifications
You must be signed in to change notification settings - Fork 6
Como contribuir
Para poder tener tus diputados locales en la base de datos necesitamos tres cosas, como mínimo:
- La lista de distritos electorales locales y sus secciones federales correspondientes,
- La lista de comisiones del congreso local, y
- la lista de diputados locales
Puedes seguir esta guía, en la que asumimos que todo puede salir haciendo un crawler de la página de tu congreso. Si ya sabes lo que estás haciendo y tienes los datos en otro formato, igual lee esta página, y luego échate un clavado a los modelos.
Para poder correr algunos de los comandos, sería util dar de alta las carpetas de tu entidad. En bin/camaras
podemos crear la siguiente estructura:
- camaras
- diputados
- senado
- 09-Distrito-Federal
- actor.rb
- asistencias.rb
- comisiones.rb
- distritos.rb
- endpoints.rb
- lista.rb
dónde el nombre de la carpeta de nuestra entidad sea compuesto por: índice de la entidad
-nombre de la entidad
. El índice debe de estar espaciado por el caracter 0
para que se ordenen correctamente en la terminal. Estamos en 2014, un sort por lenguaje natural es demasiado pedir.
No, no es una dirección real. Pero si tu congreso tiene una página con los datos accesibles (no imágenes), de tus representantes, podemos usar las librerías que tenemos para ingestar los datos y mantenerlos actualizados sin necesidad de andarnos mandando .xls
por correo. UGH.
Existe la posibilidad de que nos entreguen HTML hecho con las patas, o peor, frontpage. Prepárate para el reto.
Para poder crear un crawler, es necesario que creemos el archivo endpoints.rb, que debe de declarar un module
con el nombre de tu entidad, algo así:
module Parser
# tu módulo se debe llamar igual a la carpeta, reemplazando
# guiones por espacios, sin incluir el índice de la entidad
module DistritoFederal
def self.endpoints
{
# Requerido: El dominio de tu congreso
base: 'http://tucongreso.entidad.gob.mx/',
# Opcional
lista: 'http://tucongreso.entidad.gob.mx/lista_de_diputados',
# El URL para ver un actor
# JAJAJA, un congreso con API
actor: 'http://api.tucongreso.entidad.gob.mx/diputado/{{id}}.json',
# El URL para ver la lista de comisiones
lista_comisiones: 'http://tucongreso.entidad.gob.mx/lista_comisiones/{{id}'
}
end
end
end
Cada distrito local está compuesto por una lista de secciones federales, las cuales ya se encuentran en la base de datos. Un distrito se ve así:
{
"_id": "dl-9-12", // el id único de este distrito
"tipo": "local", // El tipo de distrito,
"entidad": "entidad_numérica", // El índice numérico de la entidad, por ejemplo 9 para el DF
"secciones": ["<id_seccion_federal>..."], // Un array de las secciones
}
El _id
de cada distrito está compuesto por: tipo de distrito
-entidad._id
-distrito
dónde:
-
tipo de distrito
puede ser uno de [dl
|df
|sf
], por ejemplo, "dl" para _D_iputado _L_ocal -
entidad._id
es el índice numérico del estado -
distrito
es el id de dicho distrito local como entero, nada de números romanos, por favor. Te estoy viendo, ALDF.
Por otro lado, secciones
es una lista de ids de secciones electorales, correspondientes a aquellas del Marco Geográfico Nacional, que componen y delimitan la geografía del distrito.
Supongamos que nuestro instituto electoral local es bien chido y nos entrega un archivo JSON en el que vienen todos estos datos ya peinados. Anden, vayan a darle un abrazo a los trabajadores de la oficina de información pública de su IE, se lo merecen. Ya que hayan apapachado, así se podría ingestar dicha lista:
#!/usr/bin/env ruby
# encoding: utf-8
# Incluimos las librerías que usaremos
require_relative '../common.rb'
listaDistritos = JSON.parse(File.open('./data/distritos.json'), symbolize_keys: true)
#Asumiendo que éstos son un array del ejemplo anterior
listaDistritos.each do |distrito|
Distrito.create!(distrito)
end
Es posible que las comisiones de tu congreso local estén disponibles desde la página del mismo, y en este caso podemos hacer un crawler, que se llamará comisiones.rb
Debemos ingestar las comisiones antes de agregar actores!
Asumiendo que los listados de comisiones son algo como:
<!--
JAJAJA LA BUROCRACIA NO HACE HTML SEMÁNTICO
http://bukkit.rob.mx/laugh-cry.gif
-->
<ul id="comisiones">
<li class="comision">
<a href="/comision/1/comision-de-datos-abiertos">
<span class="nombre">Comisión de datos Abiertos</span>
</a>
<p>Oficina: <span class="oficina">42, piso 6, edificio 9</span></p>
<ul class="telefonos">
<li class="telefono">
<a class="numero" href="tel://55123456,101">55123456</a>
extensión <span class="extension">101</span>
</li>
</ul>
</li>
</ul>
Entonces sí, en este archivo podríamos hacer algo como:
#!/usr/bin/env ruby
# encoding: utf-8
module Parser
module DistritoFederal
class Comision
attr_accessor :requests
def initialize
## la lista de páginas para hacer requests de endpoints[:lista_comisiones]
@requests = [{id: 1}, {id: 1}]
end
def parse(data, request)
dom = Nokogiri::HTML(data)
dom.at_css('.comision').each do |comision|
link = comision.at_css('a')
href = link.attr('href')
fkey = Parser::DistritoFederal.endpoints[:base]+href
nombre = link.at_css('.nombre').text.squish
telefonos = comision.at_css('.telefono')
comision = {
camara: 'local',
entidad: 9,
meta: {
# el url que puede visitar un humano para ver esta comisión
fkey: fkey,
lastCrawl: Time.now
},
nombre: nombre,
oficina: comision.at_css('.oficina').text,
telefonos: telefonos.map {|t|
{
numero: t.at_css('.numero').text,
extension: t.at_css('.extension').text
}
}
}
#por cada comision parseada, `yield`eamos el valor de la misma
yield comision
end #/at_css
end # parse
end # Comision
end # DistritoFederal
end # Parser
Lo más probable es que su HTML sea un potente purgante de los intestinos. Jajaja.
En el lingo de representantes.pati.to, cada representante es un "actor", como "actor político".
Ya que tenemos las comisiones, ahora podemos ingestar nuestros diputados locales, empezando por la lista de los mismos:
<ul id="diputados">
<li class="diputado">
<a href="/ver_diputado/1">Doña Diputada Local (Dto XLII)</a>
</li>
</ul>
# encoding: utf-8
module Parser
module DistritoFederal
class Lista
def initialize
@ids = []
#mod es el módulo actual, en este caso Parser::DistritoFederal
request(mod.endpoints[:lista]) do |data|
doc = Nokogiri::HTML(data)
doc.encoding = 'utf-8'
@ids = doc.css('.diputado a').map { |link|
#el nombre de la variable corresponde a aquel que usaremos en endpoints[:actor]
{id_del_diputado: link.attr('href').gsub(/\D/, '')}
}
end
end
def to_a
@ids
end
def count
@ids.count
end
end # Lista
end # DistritoFederal
end # Parser
Con la lista, podemos recorrer la lista de diputados e irlos ingestando, a la vez que los relacionamos a sus comisiones. Asumamos que tienen un API y regresan JSON.
# encoding utf-8
module Parser
module DistritoFederal
# OPCIONAL
def self.test
# método si quieres hacer pruebas con ./crawl 09-Distrito-Federal:actores
end
class Actor
def initialize
# si debes hacer requests antes o inicializar madres, por ejemplo
request('http://tucongreso.entidad.gob.mx/permiso-de-ddos?pretty=please') do |data|
Log.info "Ya pedimos permiso de DDOSear su server :)"
@tokenDDoS = data
end
@entidad = 9
end
# Extraer los datos de un actor
#
# este método se ejecutará una vez por diputado
#
# @param data [String] el body del request
# @param request [Hash], los datos del request
def parse(data, request)
# porque luego las librerías de ruby deciden parsear los datos solitas
info = JSON.parse(data, symbolize_keys: true) rescue data
actor = {
meta: {
fkey: request[:url]+@tokenDDoS,
lastCrawl: Time.now
},
camara: "local",
distrito: "dl-#{@entidad}-#{info[:distrito_local]}",
entidad: @entidad,
tipo_distrito: "local",
nombre: info[:nombre],
partido: info[:partido], # pri,pan,prd,pvem,pt,mc,panal,nil
# 1 para batos, 2 para morras, hay más géneros, pero no se como ponerlos :(
genero: info[:genero]=='bato' ? 1 : 2,
eleccion: info[:eleccion] # mayoría relativa ó representación proporcional
suplente: info[:suplente][:nombre],
curul: info[:calienta_el_asiento_numero], # o un string en caso de que tenga
cabecera: info[:municipio], # la cabecera de este distrito
puestos: info[:comisiones].map {|comision|
{
puesto: comision[:puesto],
# Acá relacionamos un actor a su comisión
comision: Comision.findOne("meta.fkey" => info[:url])
}
},
correo: info[:correo],
telefonos: [
{
#ambos como string, para no perder ceros al principio...
numero: info[:telefonos][0][:numero].to_s,
extension: info[:telefonos][0][:numero][:extension].to_s
}
],
links: [
{servicio: "twitter", url: info[:twitter]}
],
# corriendo bin/imagenes podemos convertir este URL a una imagen en la DB
imagen: info[:imagen],
}
# Y regresamos el actor ya parseado
return actor
end
end # Actor
end # DistritoFederal
end # Parser