Search

Scraping con Selenium y BeautifulSoup

El pasado fin de semana estuve leyendo sobre Selenium, y se me ocurrió una idea para un nuevo post.

El tema iba de hacer un ejemplo con un modelo de Machine Learning. Y claro, necesitaba unos datos que tuvieran relación con la temática del artículo.

Como primer paso, necesitaba nombres del universo Star Wars.

Así que estuve buscando por Internet un generador de nombres. Una simple búsqueda en Google y aparecen unos cuantos. El problema que me encontré es que la mayoría te generan nombres de uno en uno. Hasta que di con este:

The Star Wars Random Name Generator

En esta web puedes elegir si quieres nombres masculinos, femeninos o de ambos. Y lo mejor, la cantidad. Hasta 100 nombres haciendo un simple click.

Oh, oh, problema. ¿Cómo leches hago ese simple click?

Pues aquí entra en juego Selenium, y no, no es el elemento químico.

Selenium es una herramienta para automatizar pruebas en aplicaciones web. Selenium lo integran varios componentes. El que vamos a utilizar nosotros se llama Selenium WebDriver. Con él podemos realizar acciones como si fueramos un usuario que está navegando por la web, pero de manera automática. Para poder utilizar la potencia de Selenium WebDriver debemos instalar el controlador correspondiente al navegador con el que vayamos a realizar las acciones. Nosotros utilizaremos Mozilla Firefox. Además, deberemos instalar el paquete de Selenium para Python.

La instalación de el driver puede resultar un poco confusa. Lo voy a explicar para Windows, para Ubuntu es similar. Primero tienes que descargarte el driver del navegador que vayas a utilizar. Nosotros, como he dicho antes, utilizaremos el driver para Firefox. Viene en un archivo comprimido. Descomprimes, y el archivo .exe (en Windows) lo colocas en la carpeta que te parezca bien, y te copias la ruta.

A continuación, añadimos a nuestra variable de sistema llamada Path, una nueva entrada, con la ruta al archivo.

Y ya casi estaría. Solo quedaría instalar el paquete de selenium para python, algo tan sencillo como hacer, por ejemplo:

pip install selenium

Así pues, con la ayuda de Selenium y BeautifulSoup, vamos a ‘escrapear’ 100000 nombres del mundo Star Wars.

La url que vamos a atacar tiene esta pinta:

Examinando el código html, podemos ver que los elementos que activar son:

  • El radio button con valor 100, que tiene como propiedades name = “choice y value=”100”.
  • El botón Generate!, que tiene como propiedades name=”submit” y value=”Generate!”.

Con éstas propiedades podremos identificar los objetos y lanzar acciones sobre ellos.

Para buscar los elementos a través del código html, utilizaremos la función find_element_by_xpath que nos proporciona Selenium Driver, y a la que le pasaremos como parámetros las propiedades de los objetos.

Una vez encontrado el objeto, utilizaremos la función click.

Al hacer click en el botón Generate!, se crea una tabla con los nombres. Haremos un scraping para obtener los nombres de la tabla utilizando BeautifulSoup, como ya hicimos aquí.

Resumiendo, el script realizará un loop, en el que Selenium automatizará el siguiente proceso:

  1. Selecciona la opción de 100 nombres.
  2. Hace click en el botón Generate.

Y gracias a BeautifulSoup captaremos los nombres de la tabla generada y los iremos añadiendo a una lista, evitando repeticiones. A continuación, pasaremos la lista a un dataframe y lo guardaremos en un csv.

El código quedaría así:

import pandas as pd
from selenium import webdriver
from bs4 import BeautifulSoup
from datetime import datetime

start_time = datetime.now()
print (f'Start time: {start_time}')

driver = webdriver.Firefox()
url = 'http://www.dimfuture.net/starwars/random/generate.php'
driver.get(url)

names = []
while len(names)<100000:
    try:
        #Click on Radio button with value 100
        one_hundred = driver.find_element_by_xpath("//input[@name='choice' and @value='100']")
        one_hundred.click()

        #Click on Generate! button
        generate = driver.find_element_by_xpath("//input[@name='submit' and @value='Generate!']")
        generate.click()

        #Transfer info to BeautifulSoup
        starwars_names_soup = BeautifulSoup(driver.page_source, 'lxml')
        table = starwars_names_soup.find_all('table')
        rows = table[3].find_all("td")
        for row in rows:
            newname = row.get_text().strip().replace(u'\xa0', u' ')
            if newname not in names:
                names.append([newname])
    except:
        pass
    print(len(names))

driver.close()

starwars_names_df = pd.DataFrame(names, columns = ['name'])
now = datetime.now()
filename  = f'starwars_names_{now.year}{now.month}{now.day}.csv'
starwars_names_df.to_csv(filename, sep = ';', index = False, encoding = 'utf-8')

end_time = datetime.now()
print (f'End time: {end_time}')
total_time = end_time - start_time
print (f'Finished in: {total_time}')
#Finished in: 1:43:59.618806

Como podéis observar, le llevó una hora y tres cuartos finalizar el proceso.

Además, ya que estaba, me puse a  ‘escrapear’ también los planetas y las razas del universo Star Wars.

Los planetas los he ‘escrapeado’ de esta web, y con este script:

import pandas as pd
import requests
from bs4 import BeautifulSoup
from datetime import datetime

start_time = datetime.now()
print (f'Start time: {start_time}')

starwars_planet_list= []

url_planets = 'http://starwars.wikia.com/wiki/List_of_planets'

url_planets_req = requests.get(url_planets)
planets_html = url_planets_req.text
planets_soup = BeautifulSoup(planets_html, "html.parser")

tables = planets_soup.find_all("tr")
for t, table in enumerate(tables):
    for i, value in enumerate(table.find_all("td")):
        if i == 0 and t >= 6:
            starwars_planet_list.append([value.get_text().strip()])

starwars_planets_df = pd.DataFrame(starwars_planet_list, columns = ['name'])
now = datetime.now()
filename  = f'starwars_planets_{now.year}{now.month}{now.day}.csv'
starwars_planets_df.to_csv(filename, sep = ';', index = False, encoding = 'utf-8')

end_time = datetime.now()
print (f'End time: {end_time}')
total_time = end_time - start_time
print (f'Finished in: {total_time}')
#Finished in: 0:00:00.437466

Y las razas las he obtenido de estas 5 webs: 1, 2, 3, 4, 5. Y este es el script:

import pandas as pd
import requests
from bs4 import BeautifulSoup
from datetime import datetime

species_urls = ['https://en.wikipedia.org/wiki/List_of_Star_Wars_species_(A%E2%80%93E)',
                'https://en.wikipedia.org/wiki/List_of_Star_Wars_species_(F%E2%80%93J)',
                'https://en.wikipedia.org/wiki/List_of_Star_Wars_species_(K%E2%80%93O)',
                'https://en.wikipedia.org/wiki/List_of_Star_Wars_species_(P%E2%80%93T)',
                'https://en.wikipedia.org/wiki/List_of_Star_Wars_species_(U%E2%80%93Z)']

start_time = datetime.now()
print (f'Start time: {start_time}')

def parse_species(url):
    species_list = []
    url_species_req = requests.get(url)
    species_html = url_species_req.text
    species_soup = BeautifulSoup(species_html, "html.parser")

    species = species_soup.find_all("span", {'class': 'toctext'})
    for specie in species:
        name = specie.get_text().strip()
        if name not in ('References', 'External links', 'Bibliography'):
            species_list.append([name])
    return species_list

starwars_species_list = []

for url in species_urls:
    species_sublist = parse_species(url)
    starwars_species_list += species_sublist

starwars_species_df = pd.DataFrame(starwars_species_list, columns = ['name'])
now = datetime.now()
filename  = f'starwars_species_{now.year}{now.month}{now.day}.csv'
starwars_species_df.to_csv(filename, sep = ';', index = False, encoding = 'utf-8')

end_time = datetime.now()
print (f'End time: {end_time}')
total_time = end_time - start_time
print (f'Finished in: {total_time}')
#Finished in: 0:00:02.234633

Os dejo a vosotros investigar qué hacen los scripts para obtener los planetas y las razas.

Ahora a preparar el post donde utilizaré toda esta info ‘scrapeada’.

¡Estad atentos!

Puedes descargarte el código de mi 

Deja una respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.