Webscraping S1E1: BeautifulSoup
En coatoría con German Rosati, puede consultarse el notebook original con mayor detalle aquí:
Ya sabemos que en Internet hay una cantidad masiva de información la cual e puede ser una gran fuente para los campos de estudio que querramos abordar. Ahora bien, para poder recolectar esta información es necesario aplicar herramientas de web scraping.
Esta técnica busca lograr la automatización de la recolección de datos de un sitio. En líneas generales, la lógica del proceso es la siguiente:
- entrar a una página
- buscar en el código HTML la información que nos interesa y
- pasarla a algún formato tabular (filas y columas)
Antes de empezar a armar un scrapper, es una buena práctica inspeccionar la estructura del sitio del que vamos a construir la información, para tener una idea de dónde está dicha información.
Supongamos que queremos realizar un análisis sobre el texto de letras de canciones infantiles. Podríamos ponernos a buscar compilaciones impresas al respecto…
Pero quizás podría ser útil extraer letras de canciones de ciertos sitios de canciones. Veamos, por ejemplo musica.com…
Los elementos de una página web suelen estar escritos en algo que se llama código HTML . El HTML está compuesto por diferentes objetos llamados tags
(por ejemplo <div>
, <a>
<title>
, <body>
,<head>
) que tienen atributos <class>
, <ref>
<id>
, etc.).
Si se fijan, el tag <p>
está “adentro” del tag <div>
con class=letra
.
Esto quiere decir que <p>
es el hijo (o child) de <div>
, y que <div>
es el padre (o parent) de <p>
. A su vez, los elementos <p>
son hermanos (o siblings) entre ellos, y así se completa la familia de HTML.
Existen distintas herramientas para desarrollar un scraper. En este volumen vamos a introducir BeautifulSoup y requests, dos librerías de Python útiles para scrapear una página estática.
Nuestro objetivo es conseguir la letra de todas las canciones de María Elena Walsh a partir de la página que vimos más arriba.
En esta página tenemos un listado de sus canciones con el link a sus letras, así que lo primero que hay que hacer es conseguir el listado de estos links.
Primero, vamos a importar las librerías que queremos:
import requestsfrom bs4 import BeautifulSoupimport reimport pandas as pd
Ahora tenemos que hacer un request al sitio que queremos scrapear, extraer su contenido y “parsearlo” para obtener la estructura del árbol de HTML:
links_me = requests.get('https://www.musica.com/letras.asp?letras=37155')links_me = links_me.contentsoup_me = BeautifulSoup(links_me)
Si imprimen el objeto soup_me
, van a ver que devuelve una representación del árbol del sitio parecida a la que vemos cuando inspeccionamos la página. Podemos ver que el link (el atributo href
) a cada una de las letras de M. E. Walsh aparece dentro de una clase:
<div class="letra_data"><a href="letras.asp?letra=1852580">El reino del reves</a><p>Maria Elena Walsh</p></div></li><li class=" li50" onclick="window.location='letras.asp?letra=1814885'">
Las funciones más útiles de BeautifulSoup
para encontrar estos elementos son find
o findAll
(cuya única diferencia es que devuelven sólo el primer elemento o una lista):
findAll(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)
En la mayoría de los casos van a necesitar solamente los dos primeros argumentos de la función: el tag buscado y sus atributos.
tag
: pasamos un string con el nombre del tag que buscamos; por ejemplo, la línea siguiente va a traer todos los headers en el documento:.findAll({"h1","h2","h3","h4","h5","h6"})
attributes
: toma un diccionario de atributos y matchea tags que contiene cualquiera de esos atributos; por ejemplo,.findAll("span", {"class":"green", "class":"red"})
va a devolver los tags conclass=green
yclass=red
Por lo tanto, vamos a hacer una función que recorra el html y que con findAll
traiga todos los links del sitio. Nos vamos a quedar con los que nos interesan.
def getLinks(url): html = requests.get(url) # Abre la conexión html= html.content bsObj = BeautifulSoup(html) # Parsea el objeto links = [] for l in bsObj.find_all("a", href=True): # Recorre todos los links if ('letra=' in l.attrs['href']) & ~(l.has_attr('title')):
#Nos quedamos nada más con los links de canciones links.append('https://www.musica.com/' + l.attrs['href']) else: pass return(links)links = getLinks('https://www.musica.com/letras.asp?letras=37155')
El objeto links
va a tener entonces una lista con cada uno de los vínculos de las letras de María Elena Walsh en el sitio. Como nuestro objetivo es terminar con un dataframe que tenga el título y la letra de cada canción, vamos a hacer una función que entre a cada link y busque esos elementos en la página.
Como vimos más arriba, las estrofas están en el tag p
dentro de objetos div
que contienen los atributos class=letra.
Así que vamos a usar findAll para encontrar todos los elementos que coinciden con este criterio, y la función get_text()
que va eliminar todos los tags del documento y devuelve un string que solamente contiene texto. Creamos una función que nos devuelve un diccionario con el título y la letra.
def getSong(song_url): links_ = requests.get(song_url) links_ = links_.content soup_ = BeautifulSoup(links_) title = soup_.find('h3').get_text() #obtenemos los títulos, que son un elemento único en el tag h3 lyrics = [] for i in soup_.find('div', {'class':'letra'}).find_all('p'): if i.has_attr('class'): #filtramos los elementos p que no son estrofas break elif i.a: #filtramos los elementos p que no son estrofas pass else: lyrics.append((i.get_text(strip=True, separator=' '))) lyrics = ' '.join([str(elem) for elem in lyrics]) song = {'title':title, 'lyrics':lyrics} return song
Ahora, corremos la función en el listado de links que obtuvimos antes.
songs = []for l in links: print(l) songs.append(getSong(l))
El elemento vacío songs
se va a llenar con los datos que conseguimos en el scrapeo de getSong
. Ahora, nada más falta convertir este diccionario en un DataFrame.
df_songs_mewalsh = pd.DataFrame(songs)df_songs_mewalsh['title'] = df_songs_mewalsh['title'].str.replace('\(Letra/Lyrics\)', '') #emprolijamos el formato del título
Y listo, ya tenemos todas letras de María Elena Walsh =)
Código completo:
import requests
from bs4 import BeautifulSoup
import re
import pandas as pddef getLinks(url):
html = requests.get(url) # Abre la conexión
html= html.content
bsObj = BeautifulSoup(html) # Parsea el objeto
links = []
for l in bsObj.find_all("a", href=True): # Recorre todos los links
if ('letra=' in l.attrs['href']) & ~(l.has_attr('title')):
links.append('https://www.musica.com/' + l.attrs['href'])
else:
pass
return(links)links = getLinks('https://www.musica.com/letras.asp?letras=37155')def getSong(song_url):links_ = requests.get(song_url)
links_ = links_.content
soup_ = BeautifulSoup(links_)title = soup_.find('h3').get_text() #obtenemos los títulos, que son un elemento único en el tag h3lyrics = []for i in soup_.find('div', {'class':'letra'}).find_all('p'):if i.has_attr('class'): #filtramos los elementos p que no son estrofas
break
elif i.a: #filtramos los elementos p que no son estrofas
pass
else:
lyrics.append((i.get_text(strip=True, separator=' ')))lyrics = ' '.join([str(elem) for elem in lyrics])song = {'title':title, 'lyrics':lyrics}return songsongs = []for l in links:
print(l)
songs.append(getSong(l))
df_songs_mewalsh = pd.DataFrame(songs)