Introducción
Contenido
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 webtime
– limita la cantidad de veces que raspamos la página a la vezcsv
– nos ayuda a exportar nuestros datos extraídos a un archivo CSVre
– nos permite escribir expresiones regulares que serán útiles para elegir texto según su patrónbs4
– tuyo de verdad, el módulo de scrape para analizar el HTML
Tu tendrías bs4
ya instalado, y time
, csv
y 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?