====== 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 ]]