Instalación y elección del parser
Para instalar el paquete principal se utiliza pip:
pip install beautifulsoup4
Beautiful Soup puede valerse de distintos parsers. El más rápido y tolerante a fallos es lxml, aunque también se puede optar por html5lib (100% Python) o por el parser estándar de la biblioteca estándar. La instalación de las alternativas se realiza con:
# lxml
pip install lxml
# html5lib
pip install html5lib
La siguiente tabla resume las características prnicipales de cada parser:
| Parser | Invocación | Ventajas | Desventajas |
|---|---|---|---|
| Python estándar | BeautifulSoup(markup, "html.parser") |
- Incluido en Python - Velocidad aceptable - Buena tolerancia a errores | - En versiones anteriores a Python 2.7.3 o 3.2.2 la tolerancia es escasa |
| lxml HTML | BeautifulSoup(markup, "lxml") |
- Muy rápido - Excelente tolerancia a errores | - Requiere bibliotecas C externas |
| lxml XML | BeautifulSoup(markup, ["lxml", "xml"]) o BeautifulSoup(markup, "xml") |
- Muy rápido - Único parser con soporte pleno para XML | - Requiere bibliotecas C externas |
| html5lib | BeautifulSoup(markup, "html5lib") |
- Máxima tolerancia - Interpreta el documento como un navegador - Salida en formato HTML5 | - Más lento - No depende de extansiones nativas |
Primeros pasos y estructura básica
El siguiente fragmento HTML ejemplifica la capacidad de Beautiful Soup para analizar código mal formado y presentarlo de forma indentada con prettify().
from bs4 import BeautifulSoup
html_crudo = '''
<html><head><title>Biblioteca</title></head>
<body>
<h1 class="titulo">Libros disponibles</h1>
<ul class="catalogo">
<li class="libro"><a href="libro1.html">El Quijote</a></li>
<li class="libro"><a href="libro2.html">1984</a></li>
<li class="libro"><a href="libro3.html">Cien años de soledad</a></li>
</ul>
</body></html>
'''
documento = BeautifulSoup(html_crudo, 'lxml')
print(documento.prettify())
Navegación directa por el árbol
La forma más rápida de acceder a un elemento es utilizando su nombre de etiqueta como atributo. Si hay repeticiones, se devuelve sólo la primera coincidnecia. A continuación se muestran algunos ejemplos de navegación, extracción de nombre, atributos y contenido.
# Usando el HTML anterior
# Obtener la primera etiqueta 'li'
primer_li = documento.li
print(primer_li)
# Nombre de la etiqueta
print(primer_li.name)
# Atributos
print(primer_li.attrs)
# Contenido textual
print(primer_li.string) # None cuando hay múltiples nodos
print(primer_li.text) # Todo el texto interior
# Iterar líneas sin espacios vacíos
for cadena in primer_li.stripped_strings:
print(cadena)
# Anidación
print(documento.head.title.string)
# Hijos directos e iteración
print(documento.ul.contents)
for idx, hijo in enumerate(documento.ul.children):
print(idx, hijo)
# Descendientes (recursivo)
for idx, descendiente in enumerate(documento.ul.descendants):
print(idx, descendiente)
# Padres y ancestros
enlace = documento.find('a')
print(enlace.parent)
for ancestro in enlace.parents:
print(ancestro.name)
# Hermanos
segundo_li = documento.find_all('li')[1]
print('Siguiente hermano:', segundo_li.next_sibling)
print('Anterior hermano:', segundo_li.previous_sibling)
print('Todos los siguientes:', list(segundo_li.next_siblings))
Métodos de búsqueda y filtros
Beautiful Soup ofrece find() y find_all() como herramientas principales. Se puede refinar la búsqueda mediante cinco tipos de filtros.
Tipos de filtro
import re
# Filtro por cadena: nombre exacto de etiqueta
print(documento.find_all('li'))
# Filtro por expresión regular
print(documento.find_all(re.compile('^h'))) # etiquetas que empiezan con 'h'
# Filtro por lista: varios tipos
print(documento.find_all(['a', 'li']))
# Filtro booleano True: cualquier etiqueta
print(documento.find_all(True))
# Filtro mediante función personalizada
def tiene_clase_y_sin_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
print(documento.find_all(tiene_clase_y_sin_id))
Parámetros de find_all()
El método admite name, attrs, recursive, text y argumentos por palabra clave.
# Búsqueda por nombre con expresión regular
print(documento.find_all(name=re.compile('^l')))
# Uso de atributos con filtros
print(documento.find_all(class_='libro')) # clase exacta
print(documento.find_all(href=re.compile('libro'), limit=2))
# Atributo data- personalizado (se pasa mediante attrs)
html_especial = BeautifulSoup('<span data-info="oculto">secreto</span>', 'lxml')
print(html_especial.find_all(attrs={'data-info': 'oculto'}))
# Búsqueda por texto
print(documento.find_all(text=re.compile('Quijote')))
# Limitar resultados
print(documento.find_all('li', limit=2))
# Búsqueda solo en hijos directos (recursive=False)
print(documento.html.find_all('li', recursive=False))
El método find() es equivalente a find_all() con limit=1, pero devuelve un único elemento o None. También se puede invocar la búsqueda usando el objeto como función: documento('a') equivale a documento.find_all('a').
Uso de selectores CSS
El método select() permite emplear selectores CSS estándar para localizar elementos, y los resultados soportan extracción de atributos y texto.
# Documento con estructura anidada para ejemplificar CSS
html_complejo = '''
<div class="panel">
<ul class="items" id="lista-a">
<li class="elemento"><span>Manzana</span></li>
<li class="elemento destacado">Pera</li>
<li class="elemento">Uva</li>
</ul>
<ul class="items chicos" id="lista-b">
<li class="elemento"><em>A</em></li>
<li class="elemento especial">B</li>
</ul>
</div>'''
sopa = BeautifulSoup(html_complejo, 'lxml')
# Selectores variados
print(sopa.select('.items .elemento'))
print(sopa.select('#lista-a .destacado'))
print(sopa.select('li.elemento.especial'))
# Anidamiento de select
bloque = sopa.select('#lista-b')[0]
print(bloque.select('.elemento'))
# Obtener atributos
print(sopa.select('#lista-a li span')[0].attrs)
# Obtener texto
print(sopa.select('#lista-a li span')[0].get_text())