Nuestro problema es simple de enunciar y no tan difícil de resolver ad-hoc, pero extremadamente complejo de solucionar con eficiencia y de forma genérica . Nuestro reto es extraer periodicamente los titulares o contenido específico de una serie de páginas web no colaborativas (no disponen de sistemas de sindicación de contenidos como RSS o similares). Y más en concreto, no sólo queremos poder extraer contenido específico, sino que el objetivo final es que cualquier usuario medio de Internet [1] pueda seleccionar a golpe de ratón la zona a «monitorizar» periódicamente.
Para empezar la discusión, pongamos como ejemplo la siguiente página web: http://www.web-caching.com . Podemos empezar descomponiendo nuestro problema en dos subproblemas: cómo descargar periódicamente el contenido de la web que queremos monitorizar y cómo realizar el scraping (el rascado de la zona exacta a poner en nuestro punto de mira)
Para resolver la primera cuestión, ¿cómo descargar el contenido de esa página de forma periódica?, podríamos pensar en programar una tarea cron de tal forma que se ejecute el comando wget para hacer la descarga del [x]HTML. Por ejemplo, para descargar la página del ejemplo cada hora, introduciríamos la siguiente orden cron:
0 * * * * wget --output-document=/tmp/webcaching.html http://www.web-caching.com
¿Cómo resolver la segunda parte del problema? Es decir, el documento descargado no sólo contiene los titulares que nos interesan, sino también una cabecera, un pie de página y unas barras laterales que queremos desechar. Podríamos usar un método ad-hoc, ayudándonos de expresiones regulares. Veamos cómo. En nuestro ejemplo, tras descargar la página inicial (home page) de www.web-caching.com, vemos que la zona de titulares que nos interesa tiene un código como el siguiente:
<h3>The latest news headlines in the web caches, caching and content
distribution world:
</h3>
<ul>
<li>28 October 2006<br>[Die.net Blog]: <a href="http://www.die.net/musings/page_load_time/">Optimizing Page Load Time</a>
<li>29 June 2006<br>Network World: <a href="http://www.networkworld.com/news/2006/062906-foundry-security.html">Foundry launches barrage of security switches</a>
<br>Network World: <a href="http://www.networkworld.com/newsletters/accel/2006/0626netop2.html">How do you measure network performance from an end-user's perspective? Part 2</a>
...
</ul>
Hay tres puntos interesantes que resaltar. El primero: en general, no es HTML bien formado y por tanto, no pasa el test de validación del W3C ni para HTML 4.0 Transitional. Lo segundo, dentro del trozo de código que nos interesa, las etiquetas <li> aparecen sin cierre (</li>). Y finalmente, hay código generado dinámicamente a través de sentencias document.write de Javascript.
Sin embargo, a pesar de las pegas, la página es bastante sencilla y deberíamos de poder parsearla para extraer la zona de titulares que nos interesa con una expresión regular (no trivial en este caso) . Para ayudarnos a construir la expresión regular que nos extraiga el texto contenido entre las etiquetas <li>, podemos ayudarnos de herramientas visuales como Visual Regexp . Importamos nuestro fichero webcaching.html en esta herramienta y tecleamos la siguiente expresión regular:
<li>(.(?!<li>|</ul>))*
El resultado es el que podemos ver en la siguiente figura (pulsar sobre el thumbnail para agrandar):
Visual Regexp tiene un pequeño bug, pues en la barra de estado se nos indica que sólo ha habido un match de 3 elementos, pero en la parte central se nos colorean claramente los 7 elementos que casan con nuestra expresión. Obtener la regexp de este ejemplo ha costado más de 5 minutos de ensayo y error con esta herramienta, y además, los conocimientos necesarios para llevarla a cabo están fuera del alcance del usuario medio (por lo que se aleja de cumplir nuestros objetivos marcados al comienzo)
En cualquier caso, si quisiéramos manipular por programación nuestro documento webcaching.html con la regexp recién encontrada, podríamos hacerlo usando por ejemplo PHP con el siguiente fragmento:
<?
$fichero = file_get_contents("webcaching.html");
preg_match_all('#<li>(.(?!<li>|</ul>))*#is',$fichero,$trozos );
print_r($trozos);
?>
El procedimiento descrito, para este caso concreto, funcionaría, pero tiene serios problemas que lo hacen inviable si se quisiera automatizar y acercar su uso al usuario medio:
1) tal y como se ha indicado, la extracción de expresiones regulares es un proceso ad-hoc, a llevar a cabo para cada URL a examinar, mediante ensayo y error. Esto es justo lo que se quiere evitar, pues buscamos un procedimiento que se pueda automatizar y sea válido para cualquier web.
2) la generación de expresiones regulares, aún teniendo en cuenta que disponemos de herramientas gráficas de ayuda como Visual Regexp, están fuera del alcance del usuario medio
3) el código que descargamos por medio de Wget contiene instrucciones Javascript sin ejecutar (se ejecutan por el navegador – siempre que éste soporte Javascript – cuando la página se carga, es decir, cuando se genera el evento onLoad) [2]
4) el código que descargamos puede estar mal formado y lo qu es peor, en general, eso es precisamente lo que ocurrirá.
[1] Sabe navegar y recibir correo, y diferencia entre cuestiones básicas (navegador e Internet, cliente web y cliente de correo, sistema operativo y navegador, etc.) Son cuestiones básicas que muchos usuarios lamentablemente no saben distinguir.
[2] por tanto, como veremos en otra entrega de esta serie, el árbol DOM de la página que descargamos y el que se genera en un navegador como Firefox al cargar esta misma página ¡son distintos!