Blog
Utilizar varios métodos de análisis de texto HTML: HTMLParser, BeautifulSoup
Extraer datos de páginas web con ambos métodos
El análisis de los archivos HTML es una tarea muy importante en el Web Scraping, ya que este permite extraer la información deseada de las páginas web. Existen varias maneras de analizar HTML con Python, ya sea definiendo un analizador HTML con la librería HTMLParser
o creando un objeto BeautifulSoup. Veremos varios enfoques en este lenguaje de programación que nos permitirán analizar y extraer toda la información necesaria de archivos HTML.
Analizar HTML con Python: HTMLParser
El módulo html.parse
define la clase HTMLParser la cual sirve de base para el análisis de archivos de texto con formato en HTML y XHTML. Cuando se vincula un archivo HTML a un objeto HTMLParser, este lo procesará de principio a fin, encontrando las etiquetas de apertura, etiquetas de cierre, datos de texto y otros componentes en el archivo fuente y “procesar” cada uno de estos elementos.
In [1]: from html.parser import HTMLParser In [2]: with open('/home/odars/Desktop/results.html') as file: ...: parser = HTMLParser() ...: parser.feed(file.read()) ...: In [3]: parser Out[3]: <html.parser.HTMLParser at 0x7f4e2483c828>
Para crear un objeto HTMLParser, usamos su constructor y luego llamamos a la función feed()
pasandole como argumento el contenido de un archivo HTML como string. Los objetos HTMLParser dividen el contenido string en tokens o elementos que corresponden a las etiquetas de apertura y cerrado, texto y otros componentes, esto quiere decir que por cada elemento hay un método handler invocado. Los handlers son métodos de la clase HTMLParser.
- Token: <tag attrs>
- Handler: handle_starttag(tag, attrs) : Maneja las etiquetas de apertura como <html>, <head>, <body>, etc.
- Token: </tag>
- Handler: handle_endtag(tag): Maneja las etiquetas de cerrado como </html>, </head>, </body>, etc.
- Token: data
- Handler: handle_data(data): Maneja el contenido de texto dentro del HTML
Estos métodos son ejecutados cuando se hace la llamada a la función HTMLParser.feed()
, cada vez que se encuentra con uno de los tokens, llama a la función definida para ese tipo de token.
Sobrecargando los handlers de la clase HTMLParser
Vamos a desarrollar objeto HTMLParser que nos permita analizar HTML con Python y que imprima la URL en el atributo href contenido en cada etiqueta <a> (anchor), la cual es la etiqueta usada para anclar enlaces externos e internos. Para lograr esto debemos reescribir (o sobrecargar) el método handle_starttag(tag, attrs)
de la clase HTMLParser. Recuerda que este método solo maneja las etiquetas de apertura. La función que le daremos será la de verificar si la etiqueta de entrada es una etiqueta de anclaje <a>, si lo es, buscar el atributo href en la lista de atributos e imprima su valor.
from html.parser import HTMLParser class LinkParser(HTMLParser): '''Analizador HTML que imprime el valor de el atributo href en las etiquetas de anclaje''' def handle_starttag(self, tag, attrs): 'Imprime el valor del atributo href, si existe' if tag == 'a': for attr in attrs: if attr[0] == 'href': print(attr[1])
Probemos nuestro objeto HTMLParser con el siguiente archivo HTML:
<html> <body> <h4>Absolute HTTP link</h4> <a href="http://www.google.com">Absolute link to Google</a> <h4>Relative HTTP link</h4> <a href="w3c.html">Relative link to w3c.html.</a> <h4>mailto scheme</h4> <a href="mailto:me@example.net">Click here to email me.</a> </body> </html>
Hay tres etiquetas de enlaces en el archivo HTML. La primera contiene una URL que es un enlace a Google, la segunda contiene una URL que es un link a un archivo local y la tercera contiene una URL que inicia el cliente de email. Vamos a crear un objeto LinkParser y a vincularlo con este archivo:
In [1]: from htmlparser import LinkParser In [2]: with open('links.html') as links_file: ...: content = links_file.read() ...: linkparser = LinkParser() ...: linkparser.feed(content) ...: http://www.google.com w3c.html mailto:me@example.net
Podemos observar como nuestro objeto LinkParser pudo extraer correctamente las URLs en los atributos href de las etiquetas de enlace. Así modificando otros métodos de la clase HTMLParser como: handle_endtag(), handle_data(), podemos crear analizadores de archivos HTML para extraer la información que necesitemos.
Analizar HTML con la librería BeautifulSoup en Python
BeautifulSoup es una librería de Python que sirve para extraer información de archivos HTML y XHTML. Trabaja con el analizador de su preferencia para proveer formas idiomáticas de navegar, buscar y modificar el arbol de análisis.
Creando un objeto BeautifulSoup
Para analizar HTML con Python usando la librería BeautifulSoup debemos crear objetos de la clase definida en esta librería. Para esto llamamos al constructor BeautifulSoup(file.html, 'parser')
.
In [1]: from bs4 import BeautifulSoup In [2]: import requests In [3]: response = requests.get('http://www.aprenderpython.net') In [4]: bs = BeautifulSoup(response.text, 'html.parser') In [5]: bs.html.body.div Out[5]: <div class="main-container popup"> <header...
En la primera línea importamos la librería bs4
(BeautifulSoup) que contiene la clase BeautifulSoup
. Importamos la librería requests para obtener un archivo HTML para analizar, luego llamamos al constructor de la clase BeautifulSoup, pasándole el texto (HTML) de nuestra respuesta como primer argumento y el tipo de analizador con el que queremos trabajar como segundo argumento.
La clase BeautifulSoup, al ser vinculada con un archivo HTML, lo analiza y divide el archivo en tokens, al igual que la clase HTMLParser. La diferencia radica en como podemos acceder a estos tokens y como podemos extraer información de ellos. Ya no es necesario sobreescribir métodos de la clase ni nada por el estilo. Una de las formas más sencillas de acceder a las etiquetas dentro del objeto BeautifulSoup es a través de la nomenclatura de parámetro. Por ejemplo para obtener la etiqueta <head> del archivo hacemos: bs.html.head
.
Métodos find y find_all
Para hacer un extractor de links como lo hicimos con la clase HTMLParser podemos hacer uso de los métodos find()
y find_all()
.
In [1]: from bs4 import BeautifulSoup In [2]: import requests In [3]: response = requests.get('http://www.aprenderpython.net') In [4]: bs = BeautifulSoup(response.text, 'html.parser') In [5]: links = bs.find_all('a') In [6]: for link in links: ...: print(link.get('href')) https://www.aprenderpython.net https://www.facebook.com/Aprender-Python-1746581518691793/ https://www.youtube.com/channel/UCe0GlySXvnv1OrLXLOoq6yA Tweets by Aprender_Python https://plus.google.com/104088592517519164644 https://www.aprenderpython.net/ https://www.aprenderpython.net/curso-python-nivel-principiante/ https://www.aprenderpython.net/curso-procesamiento-imagenes-opencv-python/
En nuestro ejemplo usamos el método find_all(name, attrs, recursive, string, limit, **kwargs)
, pero solo usamos el primer argumento que define el nombre de las etiquetas que queremos, este método retorna una lista de todos los objetos con la etiqueta name. Veamos que podemos lograr con los otros argumentos:
- attrs: atributos que contiene el objeto u objetos especificos que queremos. Ejemplo: href, src, etc.
- recursive: si llamamos a
mytag.find_all()
BeautifulSoup examinará todos los descendientes de mytag: sus hijos y todos los anidados a estos sucesivamente. Si solo quieres que BeautifulSoup considere los hijos directos, puedes pasar False a este argumento. - string: con este argumento podemos buscar strings en lugar de etiquetas. Como con los argumentos name y kwagrs, podemos pasar una string, una expresión regular, una lista, una función o el valor True.
- limit: find_all() retornará todas las etiquetas y strings que correspondan a tus filtros. Pero no siempre necesitamos todos los resultados, en este caso podemos pasar un número a este argumento para definir el número de ocurrencias que queremos.
- kwargs: cualquier argumento que no sea reconocido será convertido en un filtro en uno de los atributos de la etiqueta. Si pasamos un valor para un argumento llamado id, BeautifulSoup filtrará todos los atributos con ese nombre.
En muchas ocasiones, al analizar HTML con Python tendremos que buscar etiquetas o atributos específicos en nuestros archivos HTML. Digamos que queremos extraer todos los enlaces que aparecen en la página principal de este sitio web aprenderpython.net. Si usamos la opción de “Inspeccionar elemento” de nuestro navegador, podremos ver que el objeto contenedor de estos enlaces es una etiqueta div, que contiene varias etiquetas ul (unordered list). Creemos un objeto BeautifulSoup y realizemos una búsqueda para extraer esta información específica.
from bs4 import BeautifulSoup import requests import re url = 'http://www.aprenderpython.net/' response = requests.get(url) bs = BeautifulSoup(response.text, 'lxml') # buscamos el contenedor de los enlaces, en este caso # una etiqueta div con la clase entry-content link_container = bs.find('div', {'class':'entry-content'}) # creamos un set para no reingresar enlaces repetidos links = set() for ul in link_container.find_all('ul'): a = ul.find('a', {'href':re.compile('.*\/curso\-.*')}) if a == None: break else: link = a.get('href') if link not in links: print('[*] Obteniendo enlace: {}'.format(link)) links.add(link)
Resultado en consola:
(aprendePython) ➜ python aprenderpythonlinks.py [*] Obteniendo enlace: https://www.aprenderpython.net/curso-python-nivel-principiante/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-procesamiento-imagenes-opencv-python/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-analisis-datos/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-de-sqlite1/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-raspberry-pi/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-selenium/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-web-scraping/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-de-dash/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-python-de-procesamiento-de-textos/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-de-blockchain/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-python-desarrollo-videojuegos/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-machine-learning/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-de-deep-learning/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-natural-language-processing-deep-learning/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-desarrollo-web-django-python/ [*] Obteniendo enlace: https://www.aprenderpython.net/curso-de-python-avanzado-con-dlib-opencv/
Nuestro código
Creamos una variable links que será un set(), esto lo hacemos para no recoger enlaces que ya hayamos ingresado en nuestro set. Los enlaces a los cursos en la página principal de este sitio web están en varias etiquetas <ul> contenidas en una etiqueta <div> que tiene la clase “entry-content“. Hacemos las selecciones necesarias con la librería BeautifulSoup usando el método find() para encontrar nuestro div y luego usando el método find_all() para obtener una lista de etiquetas <ul> e iterar sobre esta, recopilando un enlace en cada iteración.
De esta manera podemos analizar HTML con Python o incorporar estas funcionalidades a nuestros programas.
➡ El análisis de código HTML es una parte importante que todo programador debe dominar. Te invitamos tomar nuestro curso de redes en Python en el que aprendes de esto y mucho mas:
[…] Curso de Redes en Python, 4º clase […]