Ayer vimos un patrón muy simple, que te permite extraer el texto que hay entre los tags de apertura y cierre de un elemento HTML (o XML), y también vimos cómo extraer el valor de un atributo. Si tenemos en cuenta que en HTML todo son tags o atributos, parece que con estos dos patrones deberíamos tener resuelto cualquier problema ¿no?

En realidad se resuelven la mayoría de los casos, pero estos patrones tienen dos limitaciones importantes que vamos a intentar resolver usando las dos técnicas que se describen en esta entrada.

La primera limitación está en el hecho de que en HTML unos tags pueden estar dentro de otros, lo que complica bastante la elaboración de expresiones regulares cuando sólo buscas los símbolos «<» y «>».

Y la segunda limitación, más práctica que tecnológica, está en la dificultad que pueden llegar a tener las expresiones regulares en las páginas HTML más complejas. Esto hace muy difícil la depuración, y el posterior mantenimiento, por lo que en este caso atacaremos el problema con un enfoque pragmático que nos permitirá hacer más fácil la tarea.


El patrón más potente

Si bien es cierto que los dos patrones vistos hasta ahora permiten resolver la mayoría de los casos, puedes encontrarte con construcciones HTML tan complejas que acabarían resultando en una expresión regular descomunal. Lo que además de ser difícil de depurar, acaba resultando muy frágil ante cualquier cambio que se realice en la página.

Basta que aparezca un tag intercalado, o algún espacio en blanco inesperado, para que tu sistema falle.

Imagina que quieres extraer el argumento de un vídeo, algo que en muchas páginas web se suele incluir dentro de un elemento «div» de una forma similar a esta:

<div class="argumento">
<strong>Blade Runner</strong> es una película de ciencia ficción estadounidense, 
dirigida por <a href="http://es.wikipedia.org/wiki/Ridley_Scott">Ridley Scott</a>,
estrenada en 1982 y basada parcialmente en la novela de Philip K. Dick ¿Sueñan los
androides con ovejas eléctricas?</div>

Si tenemos en cuenta que los enlaces y negritas del argumento van a cambiar de una peli a otra ¿cómo hacer un patrón que extraiga el argumento?

Pues la verdad es que hay una forma muy sencilla, usando en la expresión regular el patrón de «cualquier caracter hasta encontrar X». Esto se hace con este símbolo:

.*?

El «.» significa «cualquier carácter», el «*» significa que puede aparecer «0 o más veces», y el «?» aplicado aquí representa «la más corta de las subcadenas».

Así que podemos extraer el argumento diciendo «todo lo que esté después de <div class=»argumento»> y hasta que encuentres el primer </div>», utilizando un patrón de este tipo:

<div class="argumento">(.*?)</div>

Veamos el resultado:

plugintools.find_multiple_matches(cadena,'<div class="argumento">(.*?)</div>')
->
['<strong>Blade Runner</strong> es una película de ciencia ficción estadounidense,
dirigida por <a href="http://es.wikipedia.org/wiki/Ridley_Scott">Ridley Scott</a>, 
estrenada en 1982 y basada parcialmente en la novela de Philip K. Dick ¿Sueñan los
androides con ovejas eléctricas?']

Esto también es aplicable para los casos de la entrada de ayer, y además de una forma mucho más sencilla que la que se explicaba.

Buscar el título de la peli del ejemplo se haría con un patrón que podríamos leer como «empieza por <h3> y luego acepta cualquier cosa hasta encontrar </h3>». Vendría a ser algo así:

<h3>(.*?)</h3>

Y podemos comprobar que nos lleva al mismo resultado que ayer:

plugintools.find_multiple_matches(cadena,"<h3>(.*?)</h3>")
->
["Blade Runner","Transformers"]

Sin embargo el uso de este patrón tiene una trampa, motivo por el cual no lo expliqué al principio y que debes tener en cuenta al usarlo.

Este patrón es computacionalmente mucho más complejo que el anterior, lo que significa que si buscas usando un patrón que tiene muchos símbolos «.*?» puede llegar a resultar muy lento encontrar caracteres con él, especialmente en cadenas de texto muy grandes.

Buscando un sólo valor

Hasta ahora hemos buscado todo lo que coincidiera con nuestro patrón, pero si sólo estás buscando un valor es mejor utilizar la función «find_single_match» de PluginTools.

Donde «find_multiple_matches» devuelve una lista, esta función «find_single_match» devolverá una cadena sólo con el primer valor encontrado, y si no encuentra nada devolverá una cadena vacía.

plugintools.find_single_match(cadena,'<div class="argumento">(.*?)</div>')
->
'<strong>Blade Runner</strong> es una película de ciencia ficción estadounidense,
dirigida por <a href="http://es.wikipedia.org/wiki/Ridley_Scott">Ridley Scott</a>, 
estrenada en 1982 y basada parcialmente en la novela de Philip K. Dick ¿Sueñan los
androides con ovejas eléctricas?'

Esto viene muy bien para afinar la búsqueda, lo que nos lleva al último punto de expresiones regulares de este tutorial.

Usar varios pasos para afinar la búsqueda

Con lo que hemos visto en estas dos últimas entradas, es fácil entender el esquema básico de funcionamiento de un add-on y cómo las expresiones regulares permiten sacar los datos de la página web para añadirlos a la lista de items de XBMC.

Supongamos que la lista de pelis está en una página de esta forma:

<a href="http://www.iskalatan.com/transformers">Transformers</a><br/>
<a href="http://www.iskalatan.com/blade-runner">Blade Runner</a><br/>
...

Así que usamos este código en el add-on:

# 1) Lee la página web de origen
data = plugintools.read( params.get("url") )

# 2) Extrae los datos
pattern = '<a href="([^"]+)">([^<]+)</a>'
matches = plugintools.find_multiple_matches(data,pattern)

# 3) Añade los datos a la lista de items de XBMC
for title,url in matches:
    plugintools.add_item( action="show_mirrors" , title=title , url=url, folder=True )

Pero es más que probable que al ejecutar esto en una página web real nos encontremos con que aparecen más entradas de las que esperábamos. ¿A qué se debe?

Se debe a que nuestro patrón es tan común que prácticamente todos los links coincidirán. Todos los elementos «a» que tengan un atributo «href» coincidirán con ese patrón, así que aparecerán como entradas en el listado de items nuestras pelis pero también otros tipos de enlaces.

La solución es buscar la zona del HTML en la que están las películas, puesto que probablemente estén dentro de un «div» o una tabla, y aplicar el patrón sólo a ese trozo de la cadena. Es lo mismo, pero hecho en dos pasos:

# 1) Lee la página web de origen
data = plugintools.read( params.get("url") )

# 2.1) Delimita la búsqueda al contenedor donde estan las pelis
data = plugintools.find_single_match(data,'<div id="container">(.*?)</div>')

# 2.2) Extrae los datos
pattern = '<a href="([^"]+)">([^<]+)</a>'
matches = plugintools.find_multiple_matches(data,pattern)

# 3) Añade los datos a la lista de items de XBMC
for title,url in matches:
    plugintools.add_item( action="show_mirrors" , title=title , url=url, folder=True )

Esta es una técnica potente. Puedes ahorrártela haciendo un patrón lo suficientemente elaborado para que encuentre exactamente lo que buscas, pero descubrirás que de esta forma el segundo patrón siempre es más fácil de preparar.

Próxima entrega

Para que veas lo aplicables que son estas fórmulas, te invito a revisar uno por uno todos los canales de pelisalacarta siguiendo este enlace.

Verás que en todos ellos se repiten una y otra vez las técnicas de expresiones regulares descritas, sin usar expresiones más complejas excepto en casos especiales.

Así que recopilemos lo que necesitamos saber para empezar:

<tag>([^<]+)</tag> para extraer el texto entre dos tags
"([^"]+)" para extraer el valor de un atributo
\d+ para saltar números
\s+ para saltar espacios en blanco
(.*?) cuando la cosa se pone complicada

Con estas herramientas ya podemos sacar datos de cualquier página web, siempre que podamos leerla. Así que en la próxima entrada veremos cómo hacer un add-on algo más completo que el que vimos hace unos días.

Esta era la parte más complicada, a partir de aquí todo va a ser mucho más sencillo.

4 comentarios

  1. Hola Jesus. Tengo una pequeña duda.
    Estoy intentando hacer un plugin para ver vídeos pero tengo un problema.En las descripciones de los vídeos si hay algo en negrita, me aparece texto y si hay una referencia a una pagina web me aparece <a href. Veo que en el ejemplo de arriba pasa lo mismo.Como hago para que en el xbmc me aparezca el texto en negrita o simplemente sin el ?

    Muchas gracias por todo lo que estas haciendo.

  2. Perdona que el strong ha echo bien su función en este caso jaja.

    Lo que quería decir es que no me aparezca strong y me aparezca el texto en negrita,o simplemente que no aparezca el stron, href y todas esas cosas.

    Muchas gracias otra vez y perdona las molestias

  3. El problema es que XBMC no entiende HTML, así que tienes que quitar esos tags y dejar el texto plano.

    En pelisalacarta yo utilizo una función de andar por casa «htmlclean()», puedes verla en el módulo scrapertools:

    https://xbmc-tvalacarta.googlecode.com/svn/trunk/pelisalacarta/core/scrapertools.py

  4. Funciona perfectamente.

    Muchísimas gracias!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *