.. _manual: ================================= Documentación de Beautiful Soup ================================= .. py:module:: bs4 .. image:: 6.1.jpg :align: right :alt: "El lacayo-pez empezó por sacarse de debajo del brazo una gran carta, casi tan grande como él." `Beautiful Soup `_ es una librería de Python para extraer datos de archivos en formato HTML y XML. Trabaja con tu analizador favorito para ofrecer maneras bien definidas de navegar, buscar y modificar el árbol analizado. Puede llegar a ahorrar horas o días de trabajo a los programadores. Este manual ilustra con ejemplos la funcionalidades más importantes de Beautiful Soup 4. Te muestro las cosas para las que la librería es buena, cómo funciona, cómo usarla, cómo hacer lo que quieres y qué hacer cuando no se cumplen tus expectativas. Este documento cubre Beautiful Soup versión 4.12.1. Los ejemplos en este documento fueron escritos para Python 3.8. Podrías estar buscando la documentación de `Beautiful Soup 3 `_. Si es así, debes saber que Beautiful Soup 3 ya no se desarrolla y su soporte fue abandonado el 31 de diciembre de 2020. Si quieres conocer la diferencias entre Beautiful Soup 3 y Beautiful Soup 4, mira `Actualizar el código a BS4`_. Esta documentación ha sido traducida a otras lenguas por los usuarios de Beautiful Soup: * `这篇文档当然还有中文版. `_ * このページは日本語で利用できます(`外部リンク `_) * `이 문서는 한국어 번역도 가능합니다. `_ * `Este documento também está disponível em Português do Brasil. `_ * `Эта документация доступна на русском языке. `_ Cómo conseguir ayuda ==================== Si tienes alguna pregunta sobre BeautifulSoup, o si tienes problemas, `envía un correo electrónico al grupo de discusión `_. Si tienes algún problema relacionado con el análisis de un documento HTML, asegúrate de mencionar :ref:`lo que la función diagnose() dice ` sobre dicho documento. Cuando informes de algún error en esta documentación, por favor, indica la traducción que estás leyendo. =============== Inicio rápido =============== Este es un documento HTML que usaré como ejemplo a lo largo de este documento. Es parte de una historia de `Alicia en el país de las maravillas`:: html_doc = """The Dormouse's story

The Dormouse's story

Once upon a time there were three little sisters; and their names were Elsie, Lacie and Tillie; and they lived at the bottom of a well.

...

""" Al procesar el documento de "Las tres hermanas" en Beautiful Soup, se nos devuelve un objeto :py:class:`BeautifulSoup`, que representa el documento como una estructura de datos anidada:: from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'html.parser') print(soup.prettify()) # # # # The Dormouse's story # # # #

# # The Dormouse's story # #

#

# Once upon a time there were three little sisters; and their names were # # Elsie # # , # # Lacie # # and # # Tillie # # ; and they lived at the bottom of a well. #

#

# ... #

# # Estas son algunas de las maneras sencillas para navegar por la estructura de datos:: soup.title # The Dormouse's story soup.title.name # u'title' soup.title.string # u'The Dormouse's story' soup.title.parent.name # u'head' soup.p #

The Dormouse's story

soup.p['class'] # u'title' soup.a # Elsie soup.find_all('a') # [Elsie, # Lacie, # Tillie] soup.find(id="link3") # Tillie Una tarea frecuente es extraer todas las URL encontradas en las etiquetas de una página:: for link in soup.find_all('a'): print(link.get('href')) # http://example.com/elsie # http://example.com/lacie # http://example.com/tillie Otra tarea habitual es extraer todo el texto de una página:: print(soup.get_text()) # The Dormouse's story # # The Dormouse's story # # Once upon a time there were three little sisters; and their names were # Elsie, # Lacie and # Tillie; # and they lived at the bottom of a well. # # ... ¿Esto se parece a lo que necesitas? Si es así, sigue leyendo. ========================= Instalar Beautiful Soup ========================= Si usas una versión reciente de Debian o Ubuntu Linux, puedes instalar Beautiful Soup con el gestor de paquetes del sistema: :kbd:`$ apt-get install python3-bs4` Beautiful Soup 4 está publicado en Pypi, así que si no puedes instalarlo con el gestor de paquetes, puedes instalarlo con ``easy_install`` o ``pip``. El nombre del paquete es ``beautifulsoup4``. Asegúrate de que usas la versión correcta de ``pip`` o ``easy_install`` para tu versión de Python (podrían llamarse ``pip3`` y ``easy_install3``, respectivamente): :kbd:`$ easy_install beautifulsoup4` :kbd:`$ pip install beautifulsoup4` (El paquete :py:class:`BeautifulSoup` ``no`` es el que quieres. Ese es el lanzamiento anterior `Beautiful Soup 3`_. Muchos *software* utilizan BS3, así que aún está disponible, pero si estás escribiendo nuevo código, deberías instalar ``beautifulsoup4``). Si no tienes ``easy_install`` o ``pip`` instalados, puedes `descargar el código de Beautiful Soup 4 comprimido en un tarball `_ e instalarlo con ``setup.py``: :kbd:`$ python setup.py install` Si aún así todo falla, la licencia de Beautiful Soup te permite empaquetar la librería completa con tu aplicación. Puedes descargar el *tarball*, copiar su directorio ``bs4`` en tu base de código y usar Beautiful Soup sin instalarlo en absoluto. Yo empleo Python 3.10 para desarrollar Beautiful Soup, aunque debería funcionar con otras versiones recientes. .. _parser-installation: Instalar un analizador ====================== Beautiful Soup soporta el analizador de HTML incluido en la librería estándar de Python, aunque también soporta varios analizadores de Python de terceros. Uno de ellos es el `analizador de lxml `_. Dependiendo de tu instalación, puedes instalar lxml con uno de los siguientes comandos: :kbd:`$ apt-get install python-lxml` :kbd:`$ easy_install lxml` :kbd:`$ pip install lxml` Otra alternativa es usar el analizador de Python de `html5lib `_, el cual analiza HTML de la misma manera en la que lo haría un navegador web. Dependiendo de tu instalación, puedes instalar html5lib con uno de los siguientes comandos: :kbd:`$ apt-get install python-html5lib` :kbd:`$ easy_install html5lib` :kbd:`$ pip install html5lib` Esta tabla resume las ventajas e inconvenientes de cada librería de los analizadores: +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ | Analizador | Uso típico | Ventajas | Desventajas | +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ | html.parser de Python | ``BeautifulSoup(markup, "html.parser")`` | * Ya incluido | * No tan rápido como lxml, | | | | * Rapidez decente | menos tolerante que | | | | * Tolerante (en Python 3.2) | html5lib. | +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ | Analizador HTML de | ``BeautifulSoup(markup, "lxml")`` | * Muy rápido | * Dependencia externa de C | | lxml | | * Tolerante | | +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ | Analizador XML de | ``BeautifulSoup(markup, "lxml-xml")`` | * Muy rápido | * Dependencia externa de C | | lxml | ``BeautifulSoup(markup, "xml")`` | * El único analizador XML | | | | | actualmente soportado | | +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ | html5lib | ``BeautifulSoup(markup, "html5lib")`` | * Extremadamente tolerante | * Muy lento | | | | * Analiza las páginas de la misma | * Dependencia externa de | | | | manera que un navegador web | Python | | | | * Crea HTML5 válido | | +-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+ Si puedes, te recomiendo que instales y uses lxml para mayor velocidad. Ten en cuenta que si un documento es inválido, analizadores diferentes generarán árboles de Beautiful Soup diferentes para él. Mira `Diferencias entre analizadores`_ para más detalle. ================== Haciendo la sopa ================== Para analizar un documento pásalo al constructor de :py:class:`BeautifulSoup`. Puedes pasar una cadena de caracteres o abrir un manejador de archivos:: from bs4 import BeautifulSoup with open("index.html") as fp: soup = BeautifulSoup(fp, 'html.parser') soup = BeautifulSoup("a web page", 'html.parser') Primero, el documento se convierte a Unicode, y las entidades HTML se convierten a caracteres Unicode:: print(BeautifulSoup("Sacré bleu!", "html.parser")) # Sacré bleu! Entonces Beautiful Soup analiza el documento usando el mejor analizador disponible. Usará un analizador HTML a no ser que se especifique que se use un analizador XML (ver `Analizar XML`_). ================== Tipos de objetos ================== Beautiful Soup transforma un complejo documento HTML en un complejo árbol de objetos de Python. Pero tan solo tendrás que lidiar con cuatro `tipos` de objetos: :py:class:`Tag`, :py:class:`NavigableString`, :py:class:`BeautifulSoup` y :py:class:`Comment`. .. py:class:: Tag Un objeto :py:class:`Tag` corresponde a una etiqueta XML o HTML en el documento original. :: soup = BeautifulSoup('Extremely bold', 'html.parser') tag = soup.b type(tag) # Las etiquetas tienen muchos atributos y métodos, y cubriré la mayoría de ellos en `Navegar por el árbol`_ y `Buscar en el árbol`_. Por ahora, las características más importantes de una etiqueta son su nombre y sus atributos. .. py:attribute:: name Toda etiqueta tiene un nombre:: tag.name # 'b' Si cambias el nombre de una etiqueta, el cambio se verá reflejado en cualquier especificación generada por Beautiful Soup a partir de entonces:: tag.name = "blockquote" tag #
Extremely bold
.. py:attribute:: attrs Una etiqueta HTML o XML puede tener cualquier cantidad de atributos. La etiqueta ```` tiene un atributo "id" cuyo valor es "boldest". Puedes acceder a los atributos de una etiqueta usándola como un diccionario:: tag = BeautifulSoup('bold', 'html.parser').b tag['id'] # 'boldest' Puedes acceder a los atributos del diccionario directamente con ``.attrs``:: tag.attrs # {'id': 'boldest'} Puedes añadir, quitar y modificar los atributos de una etiqueta. De nuevo, esto se realiza usando la etiqueta como un diccionario:: tag['id'] = 'verybold' tag['another-attribute'] = 1 tag # del tag['id'] del tag['another-attribute'] tag # bold tag['id'] # KeyError: 'id' tag.get('id') # None .. _multivalue: Atributos multivaluados ----------------------- HTML 4 define algunos atributos que pueden tomar múltiples valores. HTML 5 elimina un par de ellos, pero define unos cuantos más. El atributo multivaluado más común es ``class`` (esto es, una etiqueta puede tener más de una clase de CSS). Otros incluyen ``rel``, ``rev``, ``accept-charset``, ``headers`` y ``accesskey``. Por defecto, Beautiful Soup transforma los valores de un atributo multivaluado en una lista:: css_soup = BeautifulSoup('

', 'html.parser') css_soup.p['class'] # ['body'] css_soup = BeautifulSoup('

', 'html.parser') css_soup.p['class'] # ['body', 'strikeout'] Si un atributo `parece` que tiene más de un valor, pero no es un atributo multivaluado definido como tal por ninguna versión del estándar de HTML, Beautiful Soup no modificará el atributo:: id_soup = BeautifulSoup('

', 'html.parser') id_soup.p['id'] # 'my id' Cuando transformas una etiqueta en una cadena de caracteres, muchos atributos se combinan:: rel_soup = BeautifulSoup('

Back to the homepage

', 'html.parser') rel_soup.a['rel'] # ['index', 'first'] rel_soup.a['rel'] = ['index', 'contents'] print(rel_soup.p) #

Back to the homepage

Puedes forzar que todos los atributos sean analizados como cadenas de caracteres pasando ``multi_valued_attributes=None`` como argumento clave en el constructor de :py:class:`BeautifulSoup`:: no_list_soup = BeautifulSoup('

', 'html.parser', multi_valued_attributes=None) no_list_soup.p['class'] # 'body strikeout' Puedes usar ``get_attribute_list`` para obtener un valor que siempre sea una lista, sin importar si es un atributo multivaluado:: id_soup.p.get_attribute_list('id') # ["my id"] Si analizas un documento como XML, no hay atributos multivaluados:: xml_soup = BeautifulSoup('

', 'xml') xml_soup.p['class'] # 'body strikeout' Una vez más, puedes configurar esto usando el argumento ``multi_valued_attributes`` :: class_is_multi= { '*' : 'class'} xml_soup = BeautifulSoup('

', 'xml', multi_valued_attributes=class_is_multi) xml_soup.p['class'] # ['body', 'strikeout'] Probablemente no tengas que hacer esto, pero si lo necesitas, usa los parámetros por defecto como guía. Implementan las reglas descritas en la especificación de HTML:: from bs4.builder import builder_registry builder_registry.lookup('html').DEFAULT_CDATA_LIST_ATTRIBUTES .. py:class:: NavigableString ----------------------------- Un *string* corresponde a un trozo de texto en una etiqueta. Beautiful Soup usa la clase :py:class:`NavigableString` para contener estos trozos de texto:: soup = BeautifulSoup('Extremely bold', 'html.parser') tag = soup.b tag.string # 'Extremely bold' type(tag.string) # Un :py:class:`NavigableString` es como una cadena de caracteres de Python Unicode, exceptuando que también soporta algunas de las características descritas en `Navegar por el árbol`_ y `Buscar en el árbol`_. Puedes convertir un objeto :py:class:`NavigableString` a una cadena de caracteres Unicode usando ``str``:: unicode_string = str(tag.string) unicode_string # 'Extremely bold' type(unicode_string) # No puedes editar dicha cadena, pero puedes reemplazar una cadena por otra, usando :ref:`replace_with()`:: tag.string.replace_with("No longer bold") tag # No longer bold :py:class:`NavigableString` soporta la mayoría de las características descritas en `Navegar por el árbol`_ y `Buscar en el árbol`_, pero no todas. En particular, como una cadena no puede contener nada (la manera en la que una etiqueta contiene una cadena de caracteres u otra etiqueta), *strings* no admiten los atributos `.contents`` o ``.string``, o el método ``find()``. Si quieres usar un :py:class:`NavigableString` fuera de Beautiful Soup, deberías llamar ``unicode()`` sobre él para convertirlo en una cadena de caracteres de Python Unicode. Si no, tu cadena arrastrará una referencia a todo el árbol analizado de Beautiful Soup, incluso cuando hayas acabado de utilizar Beautiful Soup. Esto es un gran malgasto de memoria. .. py:class:: BeautifulSoup --------------------------- El objeto :py:class:`BeautifulSoup` representa el documento analizado en su conjunto. Para la mayoría de propósitos, puedes usarlo como un objeto :py:class:`Tag`. Esto significa que soporta la mayoría de métodos descritos en `Navegar por el árbol`_ and `Buscar en el árbol`_. Puedes también pasar un objeto :py:class:`BeautifulSoup` en cualquiera de los métodos definidos en `Modificar el árbol`_, como si fuese un :py:class:`Tag`. Esto te permite hacer cosas como combinar dos documentos analizados:: doc = BeautifulSoup("INSERT FOOTER HEREHere's the footer", "xml") doc.find(text="INSERT FOOTER HERE").replace_with(footer) # 'INSERT FOOTER HERE' print(doc) # #
Here's the footer
Como un objeto :py:class:`BeautifulSoup` no corresponde realmente con una etiqueta HTML o XML, no tiene nombre ni atributos. Aún así, es útil comprobar su ``.name``, así que se le ha dado el ``.name`` especial "[document]":: soup.name # '[document]' Cadenas especiales ================== :py:class:`Tag`, :py:class:`NavigableString` y :py:class:`BeautifulSoup` cubren la mayoría de todo lo que verás en un archivo HTML o XML, aunque aún quedan algunos remanentes. El principal que probablemente encuentres es el :py:class:`Comment`. .. py:class:: Comment :: markup = "" soup = BeautifulSoup(markup, 'html.parser') comment = soup.b.string type(comment) # El objeto :py:class:`Comment` es solo un tipo especial de :py:class:`NavigableString`:: comment # 'Hey, buddy. Want to buy a used parser' Pero cuando aparece como parte de un documento HTML, un :py:class:`Comment` se muestra con un formato especial:: print(soup.b.prettify()) # # # Para documentos HTML -------------------- Beautiful Soup define algunas subclases de :py:class:`NavigableString` para contener cadenas de caracteres encontradas dentro de etiquetas HTML específicas. Esto hace más fácil tomar el cuerpo principal de la página, ignorando cadenas que probablemente representen directivas de programación encontradas dentro de la página. `(Estas clases son nuevas en Beautiful Soup 4.9.0, y el analizador html5lib no las usa)`. .. py:class:: Stylesheet Una subclase de :py:class:`NavigableString` que representa hojas de estilo CSS embebidas; esto es, cualquier cadena en una etiqueta ``