3 Procesar documentos HTML

Un documento HTML no es realmente un documento XML. El formato XML correcto para crear documentos para internet es XHTML, que frecuentemente incluye la primera línea "<?xml...", y suele contener la declaración del tipo de documento, con el DTD correspondiente a la versión de XHTML usada.

Por ejemplo:

 <?xml version="1.0" encoding="ISO-8859-1" ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">

Pero libxml2 dispone de funciones para procesar documentos HTML que no sean XML, y esto puede resultar sumamente útil a la hora de procesar información procedente de internet.

Existen funciones para para analizar documentos HTML, en el fichero de cabecera HTMLparser.h. Aunque la versión actual de la librería no soporta HTML 5, el analizador funcionará correctamente con esos documentos.

También existen funciones para procesar árboles HTML, en el fichero de cabecera HTMLtree.h.

Al analizar un documento HTML se creará un árbol en el que cada entidad corresponde a una etiqueta HTML. Algunas etiquetas disponen de atributos, por ejemplo, la etiqueta 'a' que define enlaces, dispone de un atributo 'href' con el destino del enlace. Todas las etiquetas admiten el atributo 'class', para indicar el estilo CSS, el atributo 'id' que sirve para identificar una etiqueta concreta, etc.

Errores y avisos

Una primera aplicación del analizador es encontrar errores en el formato del documento HTML. Para ello podemos procesar el documento mediante cualquiera de las funciones de lectura htmlReadFile, htmlReadMemory, etc. Pero sin usar las opciones HTML_PARSE_NOERROR y HTML_PARSE_NOWARNING, de modo que se nos notifiquen todos los errores y avisos. La opción HTML_PARSE_PEDANTIC activa un nivel más estricto de análisis.

El analizador nos mostrará un mensaje, indicando el error y la línea en que lo encontró, para cada error detectado.

Veamos por ejemplo este desastroso documento HTML:

<h1>Ejemplo de HTML con errores</h2>

<p>Cosas que provocan errores en HTML:

<ul>
  <li>Elementos sin etiqueta de cierre: Si un elemento <strong>no se termina adecuadamente
      su efecto puede extenderse a áreas que no se pretendía.

  <li>Elementos mal anidados: Es importante anidar los elementos adecuadamente
      para que el documento se comporte adecuadamente. <strong>negrita <em>negrita enfatizado</strong>
      ¿Y esto qué?</em>

  <li>Atributos sin cierre: Otra fuente frecuente de problemas HTML. Por ejemplo:
      <a href="https://conclase.net/>enlace a Con Clase</a>
</ul>

Un sencillo programa puede ayudarnos a encontrar los errores en este documento:

#include <iostream>
#include <libxml/HTMLparser.h>

using namespace std;

int main()
{
    int opts = HTML_PARSE_NOBLANKS | HTML_PARSE_PEDANTIC;
    htmlDocPtr doc = htmlReadFile("ejemplo.html", NULL, opts);

    return 0;
}

El resultado es:

ejemplo.html:1: HTML parser error : Unexpected end tag : h2
<h1>Ejemplo de HTML con errores</h2>
                                    ^
ejemplo.html:10: HTML parser error : Opening and ending tag mismatch: strong and em
umento se comporte adecuadamente. <strong>negrita <em>negrita enfatizado</strong
                                                                               ^
ejemplo.html:11: HTML parser error : Unexpected end tag : em
      ├é┬┐Y esto qu├â┬®?</em>
                             ^
ejemplo.html:16: HTML parser error : AttValue: " expected
ejemplo.html:16: HTML parser error : Couldn't find end of Start Tag a

No hay que preocuparse demasiado por los caracteres extraños, el documento está codificado en UTF8 y la salida de consola utiliza otra codificación.

Ejemplo 4

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 4 html_001.zip 2024-02-25 1340 bytes 138

Buscar enlaces

Una de las aplicaciones más interesantes de poder analizar documentos HTML es crear crawlers, spiders o robots. Se trata de programas que pueden navegar automáticamente a través de la red para recopilar datos o analizar documentos.

Los motores de búsqueda, por ejemplo, usan estos programas para indexar los contenidos de las páginas. Otras aplicaciones permiten recorrer un sitio web y hacer una copia local. También se pueden usar para recuperar información cuando no se dispone de un API web.

Siguiendo con nuestra tarea de depuración de documentos web, podemos usar esta librería para buscar enlaces rotos, es decir, enlaces que enlazan a documentos que no existen.

Pero primero deberemos localizar todos los enlaces del documento HTML, y para ello podemos usar un eje, usando como nodo de contexto el documento completo, o solo el elemento 'body'.

Para localizar las urls a las que apuntan los enlaces de un documento HTML hay que tener en cuenta que la etiqueta para los enlaces es 'a', y que la url de destino se indica en el atributo 'href'.

<a href="url">texto</a>

Por ejemplo, este programa extrae todos los enlaces de un documento HTML.

#include <iostream>
#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>

using namespace std;

void MostrarNodos(xmlNodeSetPtr nodos);

int main()
{
    int opts = HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET;
    htmlDocPtr doc = htmlReadFile("numeracion.html", NULL, opts);
    xmlChar *xpath = (xmlChar*) "//a/@href";
    xmlXPathContextPtr context = xmlXPathNewContext(doc);
    xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context);
    MostrarNodos(result->nodesetval);
    xmlXPathFreeContext(context);

    return 0;
}

Por cierto, que no todos los enlaces se tienen que referir forzosamente a otras urls.

Por defecto, si no se indica lo contrario mediante un prefijo, el atributo 'href' contendrá una url.

El formato general de una url es:

<esquema>://<usuario>:<contraseña>:<dominio>:<puerto>/<ruta>#<ancla>?<parámetros>

Algunas partes de este formato son opcionales.

El usuario y contraseña no se suelen usar en enlaces, ya que contienen información sensible, pero se pueden usar directamente en la barra de direcciones del navegador para entrar en sitios que las requieran.

Si se indica, el esquema define el protocolo que debe usar el navegador. Los más comunes son:

  • http: protocolo web.
  • https: protocolo web seguro.
  • mailto: correo electrónico.
  • ftp: transferencia de ficheros.
  • news: grupos de noticias.
  • file: recursos locales disponibles.
  • tel: número de teléfono.
  • javascript: ejecutar un script.
  • etc.

Si no se indica se asume el mismo protocolo usado para cargar el documento.

El dominio tiene el formato 'www.ejemplo.com'. De nuevo, las tres uves dobles no siempre son necesarias, y el texto a continuación del punto suele variar. También es válido usar una IP en lugar de un dominio, aunque no siempre es equivalente. Esto es porque muchos dominios comparten la misma IP.

El puerto también es opcional, la mayor parte de los protocolos tienen un puerto asociado por defecto, 80 para http, 443 para https, etc. Cuando una url no use el puerto por defecto habrá que indicarlo.

La ruta equivale al camino usado para localizar un fichero en un sistema de archivos, consistente en una lista de ítems separados con '/', donde cada ítem es un directorio, salvo el último que es un fichero. Pero las urls no tienen por qué tener una correspondencia física con un sistema de archivos, dependerá del modo en que se implemente cada sitio, puede ser un sistema de archivos, un sistema de consulta de bases de datos, un sistema virtual, basado en software, o una mezcla de todos ellos.

El ancla, también opcional, permite enlazar con una parte concreta del documento.

Los parámetros son opcionales, y puede haber varios separados con el carácter '&'. Cada parámetro puede contener un identificador y un valor, separados con un '=', pero el identificador es opcional.

En esquemas como 'http' o 'https' puede tratarse de una url relativa o absoluta. Las absolutas contienen el dominio, y las relativas no.

Tendremos que tener en cuenta toda esta información a la hora de crear un crawler, para distinguir enlaces que llevan a otros documentos HTML, o para no extender la navegación fuera del sitio actual, es decir, evitar enlaces externos.

Ejemplo 5

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 5 html_002.zip 2024-02-25 11357 bytes 142

Crear esquemas

Otra posible utilidad es crear esquemas o tablas de contenido de una página. Para ello podemos extraer todas las entidades que contengan títulos, es decir, h1, h2, h3, etc.

Lo más sencillo es crear un eje como "//h1|//h2|//h3", hasta el nivel de profundidad que deseemos crear el esquema.

Otra opción es buscar cada elemento h1, mediante un eje "//h1", y usar cada uno de los nodos como contexto para el siguiente nivel, h2, hasta el nivel deseado. Pero este método asume que los elementos 'h?' están anidados correctamente, y es probable que no siempre funcione como se espera.

Ejemplo 6

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 6 html_003.zip 2024-02-25 11416 bytes 145