Script en Ruby para sorteos

Publicado el Domingo, 31 de julio de 2011
Ruby

Ruby

Una de las excusas para realizar el sorteo de Picando Código fue programar algo que me diera los nombres de los ganadores al azar.

El lenguaje elegido no podía ser otro que Ruby, mi lenguaje "hobby" favorito. Digo "hobby" ya que lo vengo estudiando y usando para cosas personales desde hace un tiempo, pero no he tenido la oportunidad de trabajar con Ruby. Bah, esto era cierto hasta que toqué mi primer código Ruby por trabajo. Fue simplemente modificar un poco un script, pero se sintió bien 😀

Volviendo al tema del script en cuestión, les comento un poco cómo se fue desarrollando. En principio no tenía pensado un diseño, así que lo primero que hice fue ponerme a programar y probar cosas con Ruby. Obviamente al no usarlo todo el tiempo, es un lenguaje donde siempre estoy descubriendo cosas, y esto en parte es lo que lo hace tan divertido (además de haber sido diseñado para eso).

Una de las primeras ideas con el script era que los participantes a través de los comentarios en el post sobre el concurso, debían ser ingresados automáticamente. No tenía sentido copiar y pegar los nombres de a uno, así que empecé por ahí. Lo mismo con los tweets y dents. Mantuve una página actualizada con esa info.

Ejecutar comandos del sistema con Ruby

Lo primero que busqué fue cómo ejecutar un comando del sistema desde un script en Ruby. Para descargar las páginas, pensé en usar wget, y posteriormente procesarlas. Me encontré que ejecutar un comando del sistema es bastante sencillo, simplemente hay que encerrarlo entre comillas invertidas: ``

    puts '*     Descargando comentarios de la entrada    *'
    `wget https://picandocodigo.net/2011/concurso-en-picando-codigo/ -O comentarios.html`
    puts '*          Descargando tweets y dents          *'
    `wget https://picandocodigo.net/concurso-picando-codigo-tweets-y-dents/ -O tyd.html`

Fue así que se guardaron dos archivos html cuyo nombre definí con el parámetro -O de wget, para trabajar con su información más adelante.

Procesar HTML con Ruby

Ahora que tenía el HTML con la info, necesitaba procesarlo. En un trabajo anterior había pasado por la experiencia de procesar HTML. Creo que en su momento probé algo con Perl, aunque al final grilix se convirtió en el PerlMaster, y más adelante usé algo en PHP. En algún momento incluso hice la prueba de procesar HTML con expresiones regulares. La experiencia y lectura me han llevado a entender que pedirle a las expresiones regulares que parseen HTML arbitrario es como pedirle a Paris Hilton que escriba un sistema operativo. Así que me puse a buscar un parser.

Una búsqueda rápida sobre el tema me llevó a la siguiente conclusión (basada únicamente en la información obtenida en una mirada rápida a los resultados de búsqueda):
Hay dos parseadores de HTML en Ruby que son bastante populares: Hpricot y Nokogiri. El primero se ha convertido en "el estándar" en el mundillo Ruby. El segundo vino después y se promociona como más rápido y mejor que el primero.

¿Cuál elegir? En mi caso, elegí Nokogiri porque uno de sus responsables es Aaron Patterson (@tenderlove) y me cae bien. Como ven soy muy objetivo con las características técnicas de una biblioteca a la hora de incluir una en mi código...

Nokogiri (?) es un parseador HTML, XML, SAX, y Reader. Entre sus muchas características está la habilidad de buscar documentos a través de XPath o selectores CSS3.

XML es como la violencia - si no soluciona tus problemas, no lo estás usando lo suficiente.

La elección resultó bastante buena,  ya que a los pocos minutos de instalar Nokogiri ya tenía el código listo para parsear el HTML y obtener los nombres de los usuarios que comentaron en la entrada. El código es muy similar al de algunos ejemplos que pueden ver en el sitio web de Nokogiri, o el post en el blog de tenderlove:

    f = File.open("comentarios.html")
    comentarios_web = Nokogiri::HTML(f)
    f.close
 
    @participantes = []
    comentarios_web.css("p.author").each do |n|
      @participantes.push n.text
    end

Para sacar los usuarios por tweet, la solución fue mucho más sucia, agarré todos los enlaces de la página, y si su texto comenzaba con "@", lo guardaba como nombre de usuario twitter/identi.ca:

    comentarios_td.css("a").each do |com|
      if com.text =~ /^@/
	n = com.text
	@participantes.push n
	i+=1
      end
    end

Esto fue lo "nuevo" que aprendí, además de varias cosas -que se harían bastante obvias para un programador Ruby- que uno va encontrando en el lenguaje. Voy a ir mejorando el script de a poco, ya que el sorteo estuvo muy bueno como para realizar nuevas ediciones y tiene varios bugs para arreglarle:

  • Los nombres de los participantes se pueden repetir en los comentarios - Tanto dos personas distintas con el mismo nombre, como varios comentarios de un mismo participante.
  • Cómo obtuve los usuarios de Twitter e Identi.ca, debería darme vergüenza... Mucho para mejorar por ahí 😛
  • Tiene mucho para encapsular.
  • Agregar el tema de que alguien pueda "elegir" por qué premio concursar y que sus probabilidades de ganar sean más o menos las mismas de aquellos que se juegan a ganar cualquier premio.

En fin, si a alguien le interesa ver el código completo de sorteo.rb, pueden verlo en github, descargarlo, reírse de él, o usarlo para leer en el baño.

2 comentarios en este post

Feed de comentarios
  1. Avatar

    Gonzalo Arreche 1 agosto. 2011 - 11:27

    Me gusta tu solución, y me parece excelente que hayas dejado este sorteo en “manos” de una máquina (o más, si usaste cloud, jaja). Yo te cambiaría..

    @participantes = []
    comentarios_web.css("p.author").each do |n|
      @participantes.push n.text
    end

    por:

    @participantes = comentarios_web.css("p.author").map(&text)

    y..

    comentarios_td.css("a").each do |com|
      if com.text =~ /^@/
        n = com.text
        @participantes.push n
        i+=1
      end
    end

    por…

    comentarios_td.css("a").each do |com|
      @participantes.push com.text if com.text =~ /^@/
    end

    luego, para saber el total de cada uno, podés guardar una variable antes y después te fijás en la diferencia..

    total_1 = @participantes.count
    # magia..
    total_2 = @participantes.count - total_1

    Creo que esto funcionaría jeje, acepto comentarios :P.. Otra cosa, podrías haber usado gists (https://gist.github.com/) en lugar de un proyecto. Y bueno, arriba ruby! ^^

    • Avatar

      Fernando 1 agosto. 2011 - 22:48

      ¡Gracias Gonza! Me enorgullece que a un verdadero rubista le haya gustado la solución 😛

      Vienen muy bien tus sugerencias, supongo que las agregaré al proyecto (a menos que quieras agregarlas vos mismo en github).

      ¡Arriba Ruby! 😀

Dejar un comentario

Toasty!