====== Python: Beautiful Soup ======
Biblioteca de Python para analizar documentos HTML y XML (incluyendo los que tienen un marcado incorrecto). Se utiliza para el Web scraping.
* [[https://www.crummy.com/software/BeautifulSoup/|Web oficial]]
* [[https://www.crummy.com/software/BeautifulSoup/bs4/doc/|Documentación]]
===== Instalación =====
pip install beautifulsoup4
También es necesario instalar ''requests'' para poder descargar contenido web, añadir cabeceras, etc.
pip install requests
===== Uso =====
Pasos:
- URL a analizar
- Descarga de la URL con ''requests'' (indicando cabeceras si es necesario).
- Conversión de la respuesta en un objeto de Beautiful Soup
- Búsqueda de los datos que queremos (''find()'')
- Tratamiento del resultado para adaptarlo a nuestro gusto.
Si queremos usar la misma cabecera que la de nuestro navegador, podemos ir a la web http://www.xhaus.com/headers y anotar lo que ponga en ''User-Agent'':
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0"
}
===== Ejemplos =====
==== Amazon ====
import bs4
import requests # es el módulo que hará la descarga
# Para las webs que no permiten scraping, le hacemos creer que
# nos estamos conectando con un navegador
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36'}
res = requests.get("https://www.amazon.es/dp/B078211KBB/", headers = headers)
# Nos aseguramos de que no ha habido errores al descargar la web anterior
res.raise_for_status()
soup = bs4.BeautifulSoup(res.text, "html.parser")
titulo = soup.find("span", attrs={"id": "productTitle"}).string.strip()
try:
# Precios normales
precio = soup.find("span", attrs={'id':'priceblock_ourprice'}).string.strip()
except AttributeError:
# Precios de oferta
precio = soup.find("span", attrs={'id':'priceblock_dealprice'}).string.strip()
# Se crea un objeto 'BeautifulSoup' donde se podrán hacer búsquedas
# utilizando selectores CSS, por ejemplo:
#elementos = soup.select('html head title')
print(titulo)
print(precio)
# Eliminamos el HTML quedándonos solo con el texto:
#elementos[0].text
# Quitamos también saltos de línea y espacios:
#elementos[0].text.strip()
Otro similar:
#! /usr/bin/env python
import requests
from bs4 import BeautifulSoup
import smtplib
headers = {
"User-agent": 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
URL = 'https://www.amazon.es/Raspberry-PI-4B-8GB-RAM/dp/B0899VXM8F/ref=sr_1_6?__mk_es_ES=%C3%85M%C3%85%C5%BD%C3%95%C3%91&dchild=1&keywords=raspberry+pi&qid=1612905463&sr=8-6'
def amazon():
page = requests.get(URL, headers=headers)
soup = BeautifulSoup(page.content, 'html.parser')
title = soup.find(id="productTitle").get_text()
price = soup.find(id="priceblock_ourprice").get_text()
sep = ','
con_price = price.split(sep, 1)[0]
converted_price = int(con_price.replace('.', ''))
# título y precio
print(title.strip())
print(converted_price)
amazon()
===== Ejemplo 2: leyendo HTML de una web =====
from bs4 import BeautifulSoup
import requests
url = "https://direccion.web"
result = requests.get(url)
# Contenido de la petición
# print(result.text)
doc = BeautifulSoup(result.text, "html.parser")
# Mostrarlo bien sangrado
# print(doc.prettify())
"""
Buscar por un texto (y aprovechar el resultado para obtener el elemento padre)
Queremos llegar a un precio que comienza por el signo "$"
"""
prices = doc.find_all(text="$")
parent = prices[0].parent
strong = parent.find("strong")
print(strong.string)
===== Búsquedas =====
* ''find()'': busca el primer elemento que encuentre
* ''find_all()'': busca todos los elementos
Podemos buscar más de un elemento a la vez:
doc.find_all(["p", "div", "li"])
Podemos buscar un elemento que tenga cierto texto:
doc.find_all(["option"], text="Rojo")
Buscar atributos de cierto elemento, por ejemplo el atributo ''value'' de la etiqueta ''option'':
doc.find_all(["option"], value="rojo")
Búsqueda por clases CSS:
tag = doc.find_all(class_="btn-item")
''class'' es una palabra reservada de Python, así que el argumento para buscar por clases lleva un guion bajo, y así diferenciarlo.
Búsqueda por expresión regular:
import re
# Buscar un signo de dólar y lo que venga después
tags = doc.find_all(text=re.compile("\$.*"))
Limitar los resultados de búsqueda:
import re
# Buscar un signo de dólar y lo que venga después
tags = doc.find_all(text=re.compile("\$.*"), limit=1)
Búsqueda por proximidad en la estructura de árbol, por ejemplo, nodos hermanos, padre y descendientes:
from bs4 import BeautifulSoup
import requests
url = "https://coinmarketcap.com/"
result = requests.get(url).text
doc = BeautifulSoup(result, "html.parser")
tbody = doc.tbody
trs = tbody.contents
print(trs[1].next_sibling)
# Nodo padre:
print(trs[0].parent)
# Descendientes
print(trs[0].descendants)
#print(trs[0].children)
#print(trs[0].contents)
Recorrer una tabla buscando precios:
from bs4 import BeautifulSoup
import requests
url = "https://coinmarketcap.com/"
result = requests.get(url).text
doc = BeautifulSoup(result, "html.parser")
tbody = doc.tbody
trs = tbody.contents
prices = {}
for tr in trs[:10]:
for td in tr.contents:
name, price = tr.contents[2:4]
fixed_name = name.p.string
fixed_price = price.a.string
prices[fixed_name] = fixed_price
print(prices)
===== Recursos =====
* [[https://towardsdatascience.com/top-5-beautiful-soup-functions-7bfe5a693482|Top 5 Beautiful Soup Functions That Will Make Your Life Easier]]
* [[https://www.youtube.com/watch?v=gRLHr664tXA| Beautiful Soup 4 Tutorial #1 - Web Scraping With Python ]]