Introducción a la biblioteca lxml de Python

lxml es una biblioteca de Python que permite un fácil manejo de archivos XML y HTML, y también se puede usar para web scraping. Hay una gran cantidad de análisis XML listos para usar...

lxml es una biblioteca de Python que permite un fácil manejo de archivos XML y HTML, y también se puede usar para web scraping. Hay muchos analizadores XML estándar, pero para obtener mejores resultados, los desarrolladores a veces prefieren escribir sus propios analizadores XML y HTML. Aquí es cuando entra en juego la biblioteca lxml. Los beneficios clave de esta biblioteca son que es fácil de usar, extremadamente rápido al analizar documentos grandes, muy bien documentado y proporciona una fácil conversión de datos a tipos de datos de Python, lo que resulta en una manipulación de archivos más fácil.

En este tutorial, profundizaremos en la biblioteca lxml de Python, comenzando con cómo configurarlo para diferentes sistemas operativos y luego discutiendo sus beneficios y la amplia gama de funcionalidades que ofrece.

Instalación

Hay varias formas de instalar lxml en su sistema. Exploraremos algunos de ellos a continuación.

Usando Pip

Pepita es un administrador de paquetes de Python que se utiliza para descargar e instalar bibliotecas de Python en su sistema local con facilidad, es decir, descarga e instala todas las dependencias del paquete que usted\ estamos instalando, también.

Si tiene pip instalado en su sistema, simplemente ejecute el siguiente comando en la terminal o en el símbolo del sistema:

1
$ pip install lxml

Usando apt-get

Si está utilizando MacOS o Linux, puede instalar lxml ejecutando este comando en su terminal:

1
$ sudo apt-get install python-lxml

Usando easy_install

Probablemente no llegarás a esta parte, pero si ninguno de los comandos anteriores te funciona por algún motivo, intenta usar easy_install:

1
$ easy_install lxml

Nota: si desea instalar una versión particular de lxml, simplemente puede indicarlo cuando ejecuta el comando en el símbolo del sistema o terminal como este, lxml==3.x.y.

A estas alturas, debería tener una copia de la biblioteca lxml instalada en su máquina local. Ahora ensuciémonos las manos y veamos qué cosas geniales se pueden hacer usando esta biblioteca.

Funcionalidad

Para poder usar la biblioteca lxml en su programa, primero debe importarla. Puedes hacerlo usando el siguiente comando:

1
from lxml import etree as et

Esto importará el módulo etree, el módulo de nuestro interés, desde la biblioteca lxml.

Creación de documentos HTML/XML

Usando el módulo etree, podemos crear elementos XML/HTML y sus subelementos, lo cual es muy útil si estamos tratando de escribir o manipular un archivo HTML o XML. Intentemos crear la estructura básica de un archivo HTML usando etree:

1
2
3
4
5
6
7
root = et.Element('html', version="5.0")

# Pass the parent node, name of the child node,
# and any number of optional attributes
et.SubElement(root, 'head')
et.SubElement(root, 'title', bgcolor="red", fontsize='22')
et.SubElement(root, 'body', fontsize="15")

En el código anterior, debe saber que la función Element requiere al menos un parámetro, mientras que la función SubElement requiere al menos dos. Esto se debe a que la función Element solo 'requiere' el nombre del elemento que se va a crear, mientras que la función SubElement requiere que se creen el nombre del nodo raíz y del nodo secundario.

También es importante saber que ambas funciones solo tienen un límite inferior para la cantidad de argumentos que pueden aceptar, pero no tienen un límite superior porque puede asociarles tantos atributos como desee. Para agregar un atributo a un elemento, simplemente agregue un parámetro adicional a la función (Sub)Element y especifique su atributo en forma de attributeName='attribute value'.

Intentemos ejecutar el código que escribimos anteriormente para obtener una mejor intuición con respecto a estas funciones:

1
2
# Use pretty_print=True to indent the HTML output
print (et.tostring(root, pretty_print=True).decode("utf-8"))

Producción:

1
2
3
4
5
<html version="5.0">
  <head/>
  <title bgcolor="red" fontsize="22"/>
  <body fontsize="15"/>
</html>

Hay otra forma de crear y organizar sus elementos de forma jerárquica. Exploremos eso también:

1
2
3
root = et.Element('html')
root.append(et.SubElement('head')) 
root.append(et.SubElement('body'))

Entonces, en este caso, cada vez que creamos un nuevo elemento, simplemente lo agregamos al nodo raíz/principal.

Análisis de documentos HTML/XML

Hasta ahora, solo hemos considerado crear nuevos elementos, asignarles atributos, etc. Veamos ahora un ejemplo en el que ya tenemos un archivo HTML o XML, y deseamos analizarlo para extraer cierta información. Suponiendo que tenemos el archivo HTML que creamos en el primer ejemplo, intentemos obtener el nombre de la etiqueta de un elemento específico, seguido de la impresión de los nombres de las etiquetas de todos los elementos.

1
print(root.tag)

Producción:

1
html 

Ahora, para iterar a través de todos los elementos secundarios en el nodo raíz e imprimir sus etiquetas:

1
2
for e in root:
    print(e.tag)

Producción:

1
2
3
head
title
body

Trabajar con atributos

Veamos ahora cómo asociamos atributos a elementos existentes, así como también cómo recuperar el valor de un atributo particular para un elemento dado.

Usando el mismo elemento raíz que antes, pruebe el siguiente código:

1
2
3
4
root.set('newAttribute', 'attributeValue') 

# Print root again to see if the new attribute has been added
print(et.tostring(root, pretty_print=True).decode("utf-8"))

Producción:

1
2
3
4
5
<html version="5.0" newAttribute="attributeValue">
  <head/>
  <title bgcolor="red" fontsize="22"/>
  <body fontsize="15"/>
</html>

Aquí podemos ver que newAttribute="attributeValue" de hecho se ha agregado al elemento raíz.

Intentemos ahora obtener los valores de los atributos que hemos establecido en el código anterior. Aquí accedemos a un elemento secundario usando la indexación de matriz en el elemento raíz, y luego usamos el método get() para recuperar el atributo:

1
2
3
print(root.get('newAttribute'))
print(root[1].get('alpha')) # root[1] accesses the `title` element
print(root[1].get('bgcolor'))

Producción:

1
2
3
attributeValue
None
red

Recuperación de texto de elementos {#recuperación de texto de elementos}

Ahora que hemos visto las funcionalidades básicas del módulo etree, intentemos hacer algunas cosas más interesantes con nuestros archivos HTML y XML. Casi siempre, estos archivos tienen algo de texto entre las etiquetas. Entonces, veamos cómo podemos agregar texto a nuestros elementos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Copying the code from the very first example
root = et.Element('html', version="5.0")
et.SubElement(root, 'head')
et.SubElement(root, 'title', bgcolor="red", fontsize="22")
et.SubElement(root, 'body', fontsize="15")

# Add text to the Elements and SubElements
root.text = "This is an HTML file"
root[0].text = "This is the head of that file"
root[1].text = "This is the title of that file"
root[2].text = "This is the body of that file and would contain paragraphs etc"

print(et.tostring(root, pretty_print=True).decode("utf-8"))

Producción:

1
<html version="5.0">This is an HTML file<head>This is the head of that file</head><title bgcolor="red" fontsize="22">This is the title of that file</title><body fontsize="15">This is the body of that file and would contain paragraphs etc</body></html>

Comprobar si un elemento tiene hijos

A continuación, hay dos cosas muy importantes que deberíamos poder verificar, ya que eso se requiere en muchas aplicaciones de web scraping para el manejo de excepciones. Lo primero que nos gustaría comprobar es si un elemento tiene hijos o no, y lo segundo es si un nodo es o no un Elemento.

Hagamos eso para los nodos que creamos arriba:

1
2
3
4
if len(root) > 0:
    print("True")
else:
    print("False")

El código anterior generará "Verdadero" ya que el nodo raíz tiene nodos secundarios. Sin embargo, si verificamos lo mismo para los nodos secundarios de la raíz, como en el código a continuación, el resultado será "Falso".

1
2
3
4
5
for i in range(len(root)):
    if (len(root[i]) > 0):
        print("True")
    else:
        print("False")

Producción:

1
2
3
False
False
False

Ahora hagamos lo mismo para ver si cada uno de los nodos es un Elemento o no:

1
2
for i in range(len(root)):
    print(et.iselement(root[i]))

Producción:

1
2
3
True
True
True

El método iselement es útil para determinar si tiene un objeto Element válido y, por lo tanto, si puede continuar atravesándolo usando los métodos que hemos mostrado aquí.

Comprobar si un elemento tiene un padre

Justo ahora, mostramos cómo descender en la jerarquía, es decir, cómo verificar si un elemento tiene hijos o no, y ahora en esta sección intentaremos subir en la jerarquía, es decir, cómo verificar y obtener el padre de un nodo hijo.

1
2
3
print(root.getparent())
print(root[0].getparent())
print(root[1].getparent())

La primera línea no debería devolver nada (también conocido como Ninguno) ya que el nodo raíz en sí no tiene ningún padre. Los otros dos deben apuntar al elemento raíz, es decir, la etiqueta HTML. Verifiquemos la salida para ver si es lo que esperábamos:

Producción:

1
2
3
None
<Element html at 0x1103c9688>
<Element html at 0x1103c9688>

Recuperación de elementos hermanos

En esta sección, aprenderemos cómo atravesar de lado en la jerarquía, lo que recupera los hermanos de un elemento en el árbol.

Atravesar el árbol de lado es bastante similar a navegarlo verticalmente. Para el último, usamos getparent y la longitud del elemento, para el primero, usaremos las funciones getnext y getprevious. Probémoslos en nodos que creamos previamente para ver cómo funcionan:

1
2
3
# root[1] is the `title` tag
print(root[1].getnext()) # The tag after the `title` tag
print(root[1].getprevious()) # The tag before the `title` tag

Producción:

1
2
<Element body at 0x10b5a75c8>
<Element head at 0x10b5a76c8>

Aquí puedes ver que root[1].getnext() recuperó la etiqueta "body" ya que era el siguiente elemento, y root[1].getprevious() recuperó la etiqueta "head".

De manera similar, si hubiéramos usado la función getprevious en root, habría devuelto None, y si hubiéramos usado la función getnext en root[2], también habría devuelto None.

Análisis de XML a partir de una cadena

Continuando, si tenemos un archivo XML o HTML y deseamos analizar la cadena sin procesar para obtener o manipular la información requerida, podemos hacerlo siguiendo el ejemplo a continuación:

1
2
3
root = et.XML('<html version="5.0">This is an HTML file<head>This is the head of that file</head><title bgcolor="red" fontsize="22">This is the title of that file</title><body fontsize="15">This is the body of that file and would contain paragraphs etc</body></html>')
root[1].text = "The title text has changed!"
print(et.tostring(root, xml_declaration=True).decode('utf-8'))

Producción:

1
2
<?xml version='1.0' encoding='ASCII'?>
<html version="5.0">This is an HTML file<head>This is the head of that file</head><title bgcolor="red" fontsize="22">The title text has changed!</title><body fontsize="15">This is the body of that file and would contain paragraphs etc</body></html>

Como puede ver, cambiamos con éxito parte del texto en el documento HTML. La declaración de tipo de documento XML también se agregó automáticamente debido al parámetro xml_declaration que pasamos a la función tostring.

Búsqueda de elementos

Lo último que vamos a discutir es bastante útil al analizar archivos XML y HTML. Verificaremos formas a través de las cuales podemos ver si un ‘Elemento’ tiene algún tipo particular de hijos, y si hace lo que contienen.

Esto tiene muchos casos prácticos de uso, como encontrar todos los elementos de enlace en una página web en particular.

1
2
3
print(root.find('a')) # No <a> tags exist, so this will be `None`
print(root.find('head').tag)
print(root.findtext('title')) # Directly retrieve the the title tag's text

Producción:

1
2
3
None
head
This is the title of that file

Conclusión

En el tutorial anterior, comenzamos con una introducción básica a lo que es la biblioteca lxml y para qué se utiliza. Después de eso, aprendimos cómo instalarlo en diferentes entornos como Windows, Linux, etc. A continuación, exploramos diferentes funcionalidades que podrían ayudarnos a atravesar el árbol HTML/XML tanto vertical como lateralmente. Al final, también discutimos formas de encontrar elementos en nuestro árbol y obtener información de ellos.