Echando código: ¿Como hacer web-scrapping con Perl?

Web-scrapping no es un término nuevo, y no es más que procesar una página web utilizando un lenguaje de programación; La idea es eliminar el HTML (presentación) que no nos interesa para luego procesar los datos que están en la pagína web.

Típicamente esta era una de las alternativas usadas por los desarrolladores antes de la venida de los «Web services» or fees en XML como Atom o RSS. Su uso aún es muy difundido, ya que no todos los sitios web proveen la información de manera amigable para programas, por diversas razones.

Perl es una maravilla en ese sentido, ya que es casi trivial sacar la información usando expresiones regulares; Sin embargo si la página es compleja ni siquiera la mejor de las expresiones regulares va a ayudar.

Vamos a hacer un programita que haga lo siguiente:

  • Vaya a la página de los últimos 100 actualizados de VeneBlogs y se traiga el contenido
  • Nos imprima el titulo de la página y el URL de verdad. Veneblogs tiene un enlace el cual oculta el URL de verdad (y con el cual seguro miden el tráfico) pero nosotros queremos el URL real de el sitio web.

¿Suena interesante? Es una aplicación sencilla, pero eso le dará una idea de las cosas que se pueden hacer con Web Scraping y Perl; La estrategía aqui es identificar el pedazo de código HTML en donde podemos localizar nuestra información sin equivocarnos y luego sacarla de allí.

Revizando el URL de los «top100» de VeneBlogs, podemos ver que el HTML es muy limpio y que es bastante sencillo identificar en donde está la información de los usuarios:

   1:<div id="innercontent">
2: <h2>&Uacute;ltimos 100 Blogs Actualizados</h2>
3: <p><strong>NOTA:</strong> Todas las horas están en el huso
horario venezolano (UTC - 4.00 h).</p>
4: <dl>
5: <dd><a title="Veneblogs - Directorio de Blogs de Venezuela - Enlace
Externo hacia Diario de un poeta gris" href="/?redidblog=1580" onmouseover=" window.status='
http://poetagris.blogspot.com'; return true" onmouseout="window.status=''; return true">
Diario de un poeta gris</a></dd>
6:<dt>Fecha de Actualización : 08/13/05 -- 11:16:45 am</dt>
7:
8:<dd><a title="Veneblogs - Directorio de Blogs de Venezuela - Enlace Externo
hacia El Tecnorrante" href="/?redidblog=584" onmouseover=" window.status='
http://atorrante.blogspot.com'; return true" onmouseout="window.status=''; return true"
>El Tecnorrante</a></dd>
9:<dt>Fecha de Actualización : 08/13/05 -- 11:06:52 am</dt>
10:<dd><a title="Veneblogs - Directorio de Blogs de Venezuela - Enlace Externo
hacia MagicVenezuela.com" href="/?redidblog=2660" onmouseover=" window.status='
http://www.magicvenezuela.com'; return true" onmouseout="window.status=''; return true"
>MagicVenezuela.com</a></dd>
11:<dt>Fecha de Actualización : 08/13/05 -- 10:55:55 am</dt>

¿Que herramientas necesitamos en Perl? Vamos a utilizar un parser de HTML, llamado HTML::Parser. E mi caso este no estaba instalado en Fedora Core 4, así que lo mandamos a instalar (you voy a utilizar CPAN):

perl -MCPAN -e’install HTML::Parse’

El otro módulo a usar es LWP::UserAgent y es quien nos va a permitir bajarnos el URL. En mi caso ya está instalado en Fedora Core 4, pero si no lo tiene entonces repita los pasos anteriores para instalar un módulo de CPAN.

La técnica tiene sus problemas; Si el HTML es complicado, o está mal escrito o cambia con frecuencia, tratar de sacar los datos de esta manera puede ser una pesadilla (no hay almuerzo grátis). En fin, el código:

   1:#!/usr/bin/perl
2:
3:use strict;
4:use LWP::UserAgent;
5:
6:# Be carefull with this one as nested elements can be ignored and
7:# HTML normally is not well formed!
8:my @ignore_tags = (
9: "head",
10: "h1",
11: "strong",
12: "form"
13: );
14:my ($title, $url);
15:
16:# Define the "format" header we will use for printing:
17:format STDOUT_TOP =
18:@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
19:"Blog title:", "URL:"
20:---------------------------------------------------------------- ----------------------------------
21:.
22:format STDOUT =
23:@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
24:$title, $url
25:.
26:
27:use constant URL_TOP100_VENEBLOGS
28: => "http://www.veneblogs.com/feeds/ultimos100.php";
29:use constant DEFAULT_TIMEOUT
30: => 180;
31:
32:my $agent = LWP::UserAgent->new({
33: agent => 'get_veneblogs_top100.plx/kodegeek 0.1',
34: timeout => DEFAULT_TIMEOUT
35: });
36:my $response = $agent->get(URL_TOP100_VENEBLOGS);
37:if (! $response->is_success) {
38: die "[ERROR]: Unable to retrieve the HTML from '" . URL_TOP100_VENEBLOGS .
39: "', Status: '" . $response->status_line . "'";
40:}
41:my $parser = HTML::Parser->new(
42: api_version => 3,
43: start_h => [ \&start_a, "tagname, attr" ]
44: );
45:$parser->ignore_tags(@ignore_tags);
46:$parser->parse($response->decoded_content());
47:
48:# ****** Functions used on the script *******
49:# Callback function used to print the contents of the HTML file
50:sub start_a {
51: my $tagname = $_[0];
52: my %attr = %{$_[1]};
53: if ( ($tagname eq "a") && ($attr{title} =~ m#veneblogs#i) ) {
54: $attr{title} =~ m#enlace externo hacia (.*)#i;
55: $title = $1;
56: $attr{onmouseover} =~ m#http://(.*)';#;
57: $url = $1;
58: if ( (length($title) == 0) || (length($url) == 0) ) {
59: return;
60: }
61: #printf "%s, %s\n", $title, $url;
62: write;
63: }
64:}
65:__END__
66:=head1 NAME
67:
68:get_veneblogs_top100.plx - Get the VeneBlog Top 100 list as text.
69:
70:=head1 DESCRIPTION
71:
72:This script will download show the Blog name and the URL for the "100 TOP" Blogs of Veneblogs; Each
73:blog title will be show along with their real URL.
74:
75:=head1 AUTHOR
76:
77:Jose Vicente Nunez Zuleta (josevnz@yahoo.com)
78:
79:=head1 BLOG
80:
81:KodeGeek - http://kodegeek.com
82:
83:=head1 LICENSE
84:
85:GPL
86:
87:cut

La salida de ejemplo:


Blog title: URL:
---------------------------------------------------------------- ----------------------------------
>> Rozanel www.rozanel.blogspot.com
Brea... www.carlosbrea.com
El Blog De Juancho juan-casanas.blogspot.com
Hedonista reprimida en busca del nirvana canelita.blogspot.com
rubenologia.net www.rubenologia.net
BlogaCine | Blog Venezolano sobre Cine www.blogacine.com
quieto quieto.motocine.com/
La Taguarita taguarita.blogspot.com
unocontodo light unocontodo.blogspot.com/
Da Vinci's Element. Artes & Tecnologia dvinci.outnloud.net

Hay varias referencias obscuras en el código; Entre ellas:

  • El uso de Expresiones regulares para terminar de obtener los valores deseados de los atributos ‘title‘ y ‘onmouseover‘ de la etiqueta ‘a‘ en el HTML.
  • El uso de reportes en Perl, para obtener una salida formateada

En pocas palabras, le sale estudiar ;). Le recomiendo que se lea las siguientes páginas man: LPW::UserAngent, HTML::Parser, perlre, perlform.

En un próximo articulo les voy a mostrar como hacer lo mismo pero con Java y utilizando un enfoque un poco menos tradicional (el cual también puede ser aplicado a Perl, pero me provocó jugar con Java esta vez :D). Puede bajarse el código de aqui.

Buscar en Technorati: , cpan