Web scraping con BeautifulSoup
Contents
Web scraping con BeautifulSoup#
En este script hacemos una pequeña introducción al uso de BeautifulSoup para extraer datos de una página HTML.
En ocasiones deseamos trabajar con datos que están dispersos en páginas HTML.
En este ejemplo mostramos cómo obtener datos de precios de libros del sitio web de una librería ficticia
http://books.toscrape.com/
Para cada uno de los libros en venta en este sitio, deseamos saber lo siguiente: - título - precio - disponibilidad - rating
import pandas as pd
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
URL_LIBROS = 'http://books.toscrape.com/catalogue/'
Esta funciones nos ayudará a convertir los datos a pandas
def extraer_info(articulo):
"""
La información de cada libro viene contenida en una etiqueta "article", como en este ejemplo
<article class="product_pod">
<div class="image_container">
<a href="a-light-in-the-attic_1000/index.html"><img alt="A Light in the Attic" class="thumbnail" src="../media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"/>
</a>
<p class="star-rating Three">
<i class="icon-star"/>
<i class="icon-star"/>
<i class="icon-star"/>
<i class="icon-star"/>
<i class="icon-star"/>
</p>
<h3>
<a href="a-light-in-the-attic_1000/index.html" title="A Light in the Attic">A Light in the ...</a>
</h3>
<div class="product_price">
<p class="price_color">£51.77</p>
<p class="instock availability">
<i class="icon-ok"/>
In stock
</p>
<form>
<button class="btn btn-primary btn-block" data-loading-text="Adding..." type="submit">Add to basket</button>
</form>
</div>
</div>
</article>
Esta función extrae el título, precio, disponibilidad y rating y lo devuelve como una tupla.
"""
titulo = articulo.h3.a['title']
precio = articulo.find('p', attrs=['price_color']).get_text()[1:]
disponible = articulo.find('p', attrs=['instock availability']).get_text().strip()
rating = articulo.find('p', attrs=[re.compile('star-rating ([A-Za-z]+)')])['class']
return (titulo, precio, disponible, rating.split()[1])
def procesar_pagina(n):
"""
Procesa la página número "n" del sitio http://books.toscrape.com/
Devuelve un dataframe de pandas con columnas título, precio, disponibilidad y rating,
donde cada fila corresponde a un libro.
"""
with urlopen(URL_LIBROS + f'page-{n}.html') as archivo:
soup = BeautifulSoup(archivo, 'xml', from_encoding='UTF-8')
datos_crudos = [extraer_info(articulo) for articulo in soup.findAll('article')]
return pd.DataFrame(datos_crudos, columns=('Título', 'Precio', 'Disponibilidad', 'Rating'))
Procesar toda la librería#
datos = pd.concat([procesar_pagina(n) for n in range(1, 51)], ignore_index=True)
Convertir datos de precio a tipo numérico
datos['Precio'] = datos['Precio'].astype(float)
Información de la tabla de datos
datos.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Título 1000 non-null object
1 Precio 1000 non-null float64
2 Disponibilidad 1000 non-null object
3 Rating 1000 non-null object
dtypes: float64(1), object(3)
memory usage: 31.4+ KB
Descripción de las columnas numéricas
datos.describe()
Precio | |
---|---|
count | 1000.00000 |
mean | 35.07035 |
std | 14.44669 |
min | 10.00000 |
25% | 22.10750 |
50% | 35.98000 |
75% | 47.45750 |
max | 59.99000 |
¿Son más caros los libros mejor calificados?
print("Precio por rating", datos.groupby('Rating')['Precio'].mean(), sep='\n\n')
Precio por rating
Rating
Five 35.374490
Four 36.093296
One 34.561195
Three 34.692020
Two 34.810918
Name: Precio, dtype: float64
Convirtamos los datos de Rating a Categorías
datos['Rating'] = pd.Categorical(datos['Rating'], categories=('One', 'Two', 'Three', 'Four', 'Five'), ordered=True)
print("Precio por rating, ya ordenados", datos.groupby('Rating')['Precio'].mean(), sep='\n\n')
Precio por rating, ya ordenados
Rating
One 34.561195
Two 34.810918
Three 34.692020
Four 36.093296
Five 35.374490
Name: Precio, dtype: float64