Guía para analizar HTML con BeautifulSoup en Python

G

Introducción

El web scraping consiste en recopilar información mediante programación de varios sitios web. Si bien hay muchas bibliotecas y marcos en varios lenguajes que pueden extraer datos web, Python ha sido durante mucho tiempo una opción popular debido a su gran cantidad de opciones para el web scraping.

Este artículo le brindará un curso intensivo sobre web scraping en Python con Beautiful Soup, una popular biblioteca de Python para analizar HTML y XML.

El Web Scraping Ético

El web scraping es omnipresente y nos proporciona datos como obtendríamos con una API. Sin embargo, como buenos ciudadanos de Internet, es nuestra responsabilidad respetar a los propietarios de los sitios de los que salimos. A continuación, se muestran algunos principios a los que debe adherirse un web scraper:

  • No reclame el contenido extraído como propio. Los propietarios de sitios web a veces dedican mucho tiempo a crear artículos, recopilar detalles sobre productos o recopilar otro contenido. Debemos respetar su labor y originalidad.
  • No scrapees un sitio web que no quiere ser scrapeado. Los sitios web a veces vienen con un robots.txt archivo: que define las partes de un sitio web que se pueden raspar. Muchos sitios web también tienen Términos de uso que pueden no permitir el scraping. Debemos respetar los sitios web que no quieren ser scrapeados.
  • ¿Existe ya una API disponible? Espléndido, no es necesario que escribamos un scrapeador. Las API se crean para proporcionar acceso a los datos de forma controlada según lo definido por los propietarios de los datos. Preferimos usar API si están disponibles.
  • Hacer solicitudes a un sitio web puede afectar el rendimiento de un sitio web. Un scrapeador web que realiza demasiadas solicitudes puede ser tan debilitante como un ataque DDOS. Debemos raspar de manera responsable para no causar ninguna interrupción en el funcionamiento regular del sitio web.

Una descripción general de la hermosa sopa

El contenido HTML de las páginas web se puede analizar y extraer con Beautiful Soup. En la siguiente sección, cubriremos aquellas funciones que son útiles para raspar páginas web.

Lo que hace que Beautiful Soup sea tan útil son las innumerables funciones que proporciona para extraer datos de HTML. Esta imagen a continuación ilustra algunas de las funciones que podemos usar:

Pongámonos manos a la obra y veamos cómo podemos analizar HTML con Beautiful Soup. Considere la siguiente página HTML guardada en un archivo como doc.html:

<html>
<head>
  <title>Head's title</title>
</head>

<body>
  <p class="title"><b>Body's title</b></p>
  <p class="story">line begins
    <a href="http://example.com/element1" class="element" id="link1">1</a>
    <a href="http://example.com/element2" class="element" id="link2">2</a>
    <a href="http://example.com/avatar1" class="avatar" id="link3">3</a>
  <p> line ends</p>
</body>
</html>

Los siguientes fragmentos de código se prueban en Ubuntu 20.04.1 LTS. Puede instalar el BeautifulSoup módulo escribiendo el siguiente comando en la terminal:

$ pip3 install beautifulsoup4

El archivo HTML doc.html necesita estar preparado. Esto se hace pasando el archivo al BeautifulSoup constructor, usemos el shell interactivo de Python para esto, para que podamos imprimir instantáneamente el contenido de una parte específica de una página:

from bs4 import BeautifulSoup

with open("doc.html") as fp:
    soup = BeautifulSoup(fp, "html.parser")

Ahora podemos usar Beautiful Soup para navegar por nuestro sitio web y extraer datos.

Del objeto de sopa creado en la sección anterior, obtengamos la etiqueta de título de doc.html:

soup.head.title   # returns <title>Head's title</title>

Aquí hay un desglose de cada componente que usamos para obtener el título:

Beautiful Soup es poderoso porque nuestros objetos Python coinciden con la estructura anidada del documento HTML que estamos raspando.

Para obtener el texto de la primera <a> etiqueta, ingrese esto:

soup.body.a.text  # returns '1'

Para obtener el título dentro de la etiqueta del cuerpo del HTML (indicado por la clase “título”), escriba lo siguiente en su terminal:

soup.body.p.b     # returns <b>Body's title</b>

Para documentos HTML profundamente anidados, la navegación puede volverse tediosa rápidamente. Afortunadamente, Beautiful Soup viene con una función de búsqueda para que no tengamos que navegar para recuperar elementos HTML.

Búsqueda de elementos de etiquetas

los find_all() El método toma una etiqueta HTML como argumento de cadena y devuelve la lista de elementos que coinciden con la etiqueta proporcionada. Por ejemplo, si queremos todos a etiquetas en doc.html:

soup.find_all("a")

Veremos esta lista de a etiquetas como salida:

[<a class="element" href="http://example.com/element1" id="link1">1</a>, <a class="element" href="http://example.com/element2" id="link2">2</a>, <a class="element" href="http://example.com/element3" id="link3">3</a>]

A continuación, se muestra un desglose de cada componente que usamos para buscar una etiqueta:

También podemos buscar etiquetas de una clase específica proporcionando el class_ argumento. Usos hermosos de la sopa class_ porque class es una palabra clave reservada en Python. Busquemos por todos a etiquetas que tienen la clase “elemento”:

soup.find_all("a", class_="element")

Como solo tenemos dos enlaces con la clase “elemento”, verá este resultado:

[<a class="element" href="http://example.com/element1" id="link1">1</a>, <a class="element" href="http://example.com/element2" id="link2">2</a>]

¿Y si quisiéramos obtener los enlaces incrustados dentro de la a etiquetas? Recuperemos un enlace href atributo usando el find() opción. Funciona igual que find_all() pero devuelve el primer elemento coincidente en lugar de una lista. Escriba esto en su caparazón:

soup.find("a", href=True)["href"] # returns http://example.com/element1

los find() y find_all() Las funciones también aceptan una expresión regular en lugar de una cadena. Detrás de escena, el texto se filtrará utilizando la expresión regular compilada search() método. Por ejemplo:

import re

for tag in soup.find_all(re.compile("^b")):
    print(tag)

La lista al iterar, recupera las etiquetas que comienzan con el carácter b que incluye <body> y <b>:

<body>
 <p class="title"><b>Body's title</b></p>
 <p class="story">line begins
       <a class="element" href="http://example.com/element1" id="link1">1</a>
 <a class="element" href="http://example.com/element2" id="link2">2</a>
 <a class="element" href="http://example.com/element3" id="link3">3</a>
 <p> line ends</p>
 </p></body>
 <b>Body's title</b>

Hemos cubierto las formas más populares de obtener etiquetas y sus atributos. A veces, especialmente para páginas web menos dinámicas, solo queremos el texto de ellas. ¡Veamos cómo podemos conseguirlo!

Obtener el texto completo

los get_text() La función recupera todo el texto del documento HTML. Consigamos todo el texto del documento HTML:

soup.get_text()

Su salida debería ser así:

Head's title


Body's title
line begins
      1
2
3
 line ends

A veces, los caracteres de nueva línea se imprimen, por lo que su salida también puede verse así:

"nnHead's titlennnBody's titlenline beginsn    1n2n3n line endsnn"

Ahora que sabemos cómo usar Beautiful Soup, ¡vamos a crear un sitio web!

Beautiful Soup en acción: Scraping a Book List

Ahora que dominamos los componentes de Beautiful Soup, es hora de poner en práctica nuestro aprendizaje. Construyamos un scraper para extraer datos de https://books.toscrape.com/ y guárdelo en un archivo CSV. El sitio contiene datos aleatorios sobre libros y es un gran espacio para probar sus técnicas de web scraping.

Primero, cree un nuevo archivo llamado scraper.py. Importemos todas las bibliotecas que necesitamos para este script:

import requests
import time
import csv
import re
from bs4 import BeautifulSoup

En los módulos mencionados anteriormente:

  • requests – realiza la solicitud de URL y obtiene el HTML del sitio web
  • time – limita la cantidad de veces que raspamos la página a la vez
  • csv – nos ayuda a exportar nuestros datos extraídos a un archivo CSV
  • re – nos permite escribir expresiones regulares que serán útiles para elegir texto según su patrón
  • bs4 – tuyo de verdad, el módulo de scrape para analizar el HTML

Tu tendrías bs4 ya instalado, y time, csvy re son paquetes integrados en Python. Necesitarás instalar el requests módulo directamente así:

$ pip3 install requests

Antes de comenzar, debe comprender cómo está estructurado el HTML de la página web. En tu navegador, vayamos a http://books.toscrape.com/catalogue/page-1.html. A continuación, haga clic con el botón derecho en los componentes de la página web que desea eliminar y haga clic en el inspeccionar para comprender la jerarquía de las etiquetas como se muestra a continuación.

Esto le mostrará el HTML subyacente de lo que está inspeccionando. La siguiente imagen ilustra estos pasos:

Al inspeccionar el HTML, aprendemos cómo acceder a la URL del libro, la imagen de la portada, el título, la calificación, el precio y más campos del HTML. Escribamos una función que raspe un elemento de un libro y extraiga sus datos:

def scrape(source_url, soup):  # Takes the driver and the subdomain for concats as params
    # Find the elements of the article tag
    books = soup.find_all("article", class_="product_pod")

    # Iterate over each book article tag
    for each_book in books:
        info_url = source_url+"/"+each_book.h2.find("a")["href"]
        cover_url = source_url+"/catalogue" + 
            each_book.a.img["src"].replace("..", "")

        title = each_book.h2.find("a")["title"]
        rating = each_book.find("p", class_="star-rating")["class"][1]
        # can also be written as : each_book.h2.find("a").get("title")
        price = each_book.find("p", class_="price_color").text.strip().encode(
            "ascii", "ignore").decode("ascii")
        availability = each_book.find(
            "p", class_="instock availability").text.strip()

        # Invoke the write_to_csv function
        write_to_csv([info_url, cover_url, title, rating, price, availability])

La última línea del fragmento anterior apunta a una función para escribir la lista de cadenas extraídas en un archivo CSV. Agreguemos esa función ahora:

def write_to_csv(list_input):
    # The scraped info will be written to a CSV here.
    try:
        with open("allBooks.csv", "a") as fopen:  # Open the csv file.
            csv_writer = csv.writer(fopen)
            csv_writer.writerow(list_input)
    except:
        return False

Como tenemos una función que puede raspar una página y exportar a CSV, queremos otra función que rastree el sitio web paginado, recopilando datos de libros en cada página.

Para hacer esto, veamos la URL para la que estamos escribiendo este scrapeador:

"http://books.toscrape.com/catalogue/page-1.html"

El único elemento variable en la URL es el número de página. Podemos formatear la URL de forma dinámica para que se convierta en una URL semilla:

"http://books.toscrape.com/catalogue/page-{}.html".format(str(page_number))

Esta URL con formato de cadena con el número de página se puede obtener mediante el método requests.get(). Entonces podemos crear un nuevo BeautifulSoup objeto. Cada vez que obtenemos el objeto de sopa, se verifica la presencia del botón “siguiente” para que podamos detenernos en la última página. Realizamos un seguimiento de un contador para el número de página que se incrementa en 1 después de raspar una página con éxito.

def browse_and_scrape(seed_url, page_number=1):
    # Fetch the URL - We will be using this to append to images and info routes
    url_pat = re.compile(r"(http://.*.com)")
    source_url = url_pat.search(seed_url).group(0)

   # Page_number from the argument gets formatted in the URL & Fetched
    formatted_url = seed_url.format(str(page_number))

    try:
        html_text = requests.get(formatted_url).text
        # Prepare the soup
        soup = BeautifulSoup(html_text, "html.parser")
        print(f"Now Scraping - {formatted_url}")

        # This if clause stops the script when it hits an empty page
        if soup.find("li", class_="next") != None:
            scrape(source_url, soup)     # Invoke the scrape function
            # Be a responsible citizen by waiting before you hit again
            time.sleep(3)
            page_number += 1
            # Recursively invoke the same function with the increment
            browse_and_scrape(seed_url, page_number)
        else:
            scrape(source_url, soup)     # The script exits here
            return True
        return True
    except Exception as e:
        return e

La función de arriba, browse_and_scrape(), se llama de forma recursiva hasta que la función soup.find("li",class_="next") devoluciones None. En este punto, el código raspará la parte restante de la página web y saldrá.

Para la pieza final del rompecabezas, iniciamos el flujo de scrapeado. Definimos el seed_url y llama al browse_and_scrape() para obtener los datos. Esto se hace bajo el if __name__ == "__main__" bloquear:

if __name__ == "__main__":
    seed_url = "http://books.toscrape.com/catalogue/page-{}.html"
    print("Web scraping has begun")
    result = browse_and_scrape(seed_url)
    if result == True:
        print("Web scraping is now complete!")
    else:
        print(f"Oops, That doesn't seem right!!! - {result}")

Si desea obtener más información sobre if __name__ == "__main__" block, consulte nuestra guía sobre cómo funciona.

Puede ejecutar el script como se muestra a continuación en su terminal y obtener el resultado como:

$ python scraper.py
Web scraping has begun
Now Scraping - http://books.toscrape.com/catalogue/page-1.html
Now Scraping - http://books.toscrape.com/catalogue/page-2.html
Now Scraping - http://books.toscrape.com/catalogue/page-3.html
.
.
.
Now Scraping - http://books.toscrape.com/catalogue/page-49.html
Now Scraping - http://books.toscrape.com/catalogue/page-50.html
Web scraping is now complete!

Los datos extraídos se pueden encontrar en el directorio de trabajo actual bajo el nombre de archivo allBooks.csv. Aquí hay una muestra del contenido del archivo:

http://books.toscrape.com/a-light-in-the-attic_1000/index.html,http://books.toscrape.com/catalogue/media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg,A Light in the Attic,Three,51.77,In stock
http://books.toscrape.com/tipping-the-velvet_999/index.html,http://books.toscrape.com/catalogue/media/cache/26/0c/260c6ae16bce31c8f8c95daddd9f4a1c.jpg,Tipping the Velvet,One,53.74,In stock
http://books.toscrape.com/soumission_998/index.html,http://books.toscrape.com/catalogue/media/cache/3e/ef/3eef99c9d9adef34639f510662022830.jpg,Soumission,One,50.10,In stock

¡Buen trabajo! Si desea ver el código del web scraper como un todo, puede encontrarlo en GitHub.

Conclusión

En este tutorial, aprendimos la ética de escribir buenos web scrapers. Luego usamos Beautiful Soup para extraer datos de un archivo HTML usando las propiedades del objeto de Beautiful Soup, y varios métodos como find(), find_all() y get_text(). Luego, creamos un scraper que recupera una lista de libros en línea y la exporta a CSV.

El web scraping es una habilidad útil que ayuda en diversas actividades, como extraer datos como una API, realizar un control de calidad en un sitio web, verificar URL rotas en un sitio web y más. ¿Cuál es el próximo scrapeador que vas a construir?

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad