Webscraping S1E2: SeleniumWebDriver
Cuando queremos conseguir data de páginas de internet, nos podemos encontrar con que esta es dinámica. Esto quiere decir que lo que queremos encontrar aparece después de que scrolleemos hacia abajo y dejemos que cargue, que hagamos click en algún lado, etc. En estos casos, nos conviene usar la herramienta Selenium WebDriver.
¿Qué es Selenium?
El paquete Selenium nos permite automatizar el uso de un navegador web. Esto quiere decir que podemos escribir un código que imite la forma en que navegaríamos una página manualmente. Así, podemos hacer clic, bajar, arrastrar, tipear, o hacer lo que sea necesario para que la información que queremos conseguir aparezca.
Veamos un ejemplo para entenderlo mejor. Supongamos que queremos saber cuál es el precio de los productos menstruales en distintas cadenas de hipermercados del país, y empezamos viendo cuánto salen en DIA%.
Si entran al link donde aparecen los productos, van a ver que el listado no aparece completo de entrada. Hay que scrollear al final de la página, esperar a que carguen los productos nuevos, volver a scrollear y así hasta llegar al final. Esto es lo que vamos a lograr con Selenium.
Manos a la obra. Primero, vas a tener que instalar la librería Selenium en Python.
pip install selenium
Después tenés que instalar un WebDriver, podés elegir uno de Google Chrome, Firefox o Safari. Fijate dónde guardás el archivo .exe porque después vas a tener que llamarlo en el código.
Ahora, importamos en Python las librerías que necesitamos.
import pandas as pd #para el formato
from selenium import webdriver
import time #para controlar tiempos de espera
Ahora, vamos a crear el objeto WebDriver en Python y acceder a la página de destino con Selenium.
driver = webdriver.Chrome('C:/Users/../../Webdriver/chromedriver.exe') #Indican la carpeta donde tienen guardado el WebDriverlink = 'https://diaonline.supermercadosdia.com.ar/perfumeria/proteccion-femenina/'driver.get(link) #dirijo webdriver a la página que quiero
Creamos tres listas vacías que después vamos a llenar con la información que de la página: la descripción del producto, el precio y la marca.
list_marcas = []
list_desc = []
list_precio = []
Hay dos formas de interactuar con el navegador desde el código: podemos usar elementos del Driver o apelar a JavaScript con el comando driver.execute_script()
.
Generalmente, es mejor usar JavaScript. WebDriver intenta imitar lo que sucede cuando un usuario interactúa con la página. Entonces, supongamos que queremos hacer click en un elemento A, pero este está tapado por un elemento B que es transparente. Si le decimos a WebDriver que haga click en A, probablemente este click nunca llegue por la intercepción de B. En cambio, JavaScript interactúa directamente con los elementos, por lo que el click llegaría a A sin importar que haya algo por delante.
Volviendo a DIA%: dijimos que queremos que el Driver baje al final de la página, espere a que carguen los nuevos productos y vuelva a bajar, hasta llegar al final del listado de productos. Para esto vamos a usar el siguiente loop:
last_height = driver.execute_script("return document.body.scrollHeight") # Ejecutamos un script que nos devuelve el valor de la altura actual del documento.while True: driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # Ejecutamos script que baja al final de la página. time.sleep(3) # Esperamos 3 segundos a que carguen los productos nuevos. # Calculamos la nueva altura de la página, y la comparamos con la última que obtuvimos. Si tienen la misma altura, el loop se interrumpe. De lo contrario vuelve a bajar. new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_height
Ya tenemos todos los productos en pantalla, así que ahora vamos a buscar los elementos que contienen la información deseada para incorporarlos en las listas. Esto lo vamos a hacer a partir del HTML de la página, que ya explicamos un poco qué es y cómo está compuesto en nuestro primer tutorial.
Selenium tiene varias formas para ubicar elementos en una página, pero la más útil es find_elements_by_xpath
.
La sintaxis de XPath elige nodos en un documento XML. Este documento sería el HTML de la página, y cada nodo está indicado por un tag. Por ejemplo, podemos ubicar el nodo donde está la marca de cada producto en la página de DIA%:
Las funciones de XPath nos permiten encontrar los nodos a partir de su posición en el documento, sus tags, sus atributos, los siblings o parents, el texto que contiene el objeto o de una porción parcial del texto. Esto último es especialmente útil cuando el valor del atributo que queremos llamar cambia dinámicamente.
Para nuestro ejemplo, vamos a ubicar los elementos nada más a partir de sus tags, atributos y descendientes:
//div[@class='product']//h5
//:
Elige los nodos en el documento que correspondan el criterio que le demos independientemente de su ubicación en el HTML. Cuando lo usamos dos veces en una línea, (en este caso, en//h5
) estamos llamando a los descendientes del primer elemento que seleccionamos (div[@class='product']
).@class='product':
A diferencia de BeautifulSoup que usábamos un diccionario para llamar los atributos con un valor específico, usamos entre corchetes el@
para llamar al atributo y=
para indicar su valor.
La sentencia driver.find_elements_by_xpath
nos va a devolver como resultado una lista de todos los objetos WebDriver que matchean el criterio, por lo que vamos a tener que pasar por cada uno de esos objetos, traer el texto y ponerlo en la lista correspondiente.
Entonces, vamos a hacer que el código encuentre en la página los nodos donde están la marca, la descripción del producto y su precio, y meta en las listas vacías la información encontrada.
desc=driver.find_elements_by_xpath("//div[@class='product']//h4")for d in desc: #leo cada uno de los elementos de la lista
if d.text == "":
list_desc.append("No especificado") #Si el elemento está vacío (lo que quiere decir que no tiene descripción/marca/precio) lo especificamos
else:
list_desc.append(d.text)#Llenamos la lista vacía con los resultados de la búsqueda del Drivermarca=driver.find_elements_by_xpath("//div[@class='product']//h5")for m in marca:
if m.text == "":
list_marcas.append("No especificado")
else:
list_marcas.append(m.text)precio=driver.find_elements_by_xpath("//div[@class='product']//p[@class='best-price']")for p in precio:
if p.text == "":
list_precio.append("No especificado")
else:
list_precio.append(p.text)data=({"Marca":list_marcas,'Descripcion':list_desc,'Precio':list_precio, 'Fuente':'Dia%'}) #Metemos todo en un diccionariodata=pd.DataFrame(data)#Lo convertimos a dataframedata.to_csv('Scrap_DIA.csv', index=False)#Guardamos
Y listo, ya tenemos en un .csv toda la información sobre precios de productos de gestión menstrual en DIA%.
Código completo
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 26 14:56:11 2020@author: perla
"""import pandas as pd
from selenium import webdriver
import timedriver = webdriver.Chrome('C:/Users/../../Webdriver/chromedriver.exe')link = 'https://diaonline.supermercadosdia.com.ar/perfumeria/proteccion-femenina/'driver.get(link)list_marcas = []
list_desc = []
list_precio = []# Get scroll height.
last_height = driver.execute_script("return document.body.scrollHeight")while True: driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(3) new_height = driver.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_heightdesc=driver.find_elements_by_xpath("//div[@class='product']//h4")for d in desc: #leo cada uno de los elementos de la lista
if d.text == "":
list_desc.append("No especificado") #Si el elemento está vacío (lo que quiere decir que no tiene descripción/marca/precio) lo especificamos
else:
list_desc.append(d.text)marca=driver.find_elements_by_xpath("//div[@class='product']//h5")for m in marca:
if m.text == "":
list_marcas.append("No especificado")
else:
list_marcas.append(m.text)precio=driver.find_elements_by_xpath("//div[@class='product']//p[@class='best-price']")for p in precio:
if p.text == "":
list_precio.append("No especificado")
else:
list_precio.append(p.text)
data=({"Marca":list_marcas,'Descripcion':list_desc,'Precio':list_precio, 'Fuente':'Dia%'})data=pd.DataFrame(data)data.to_csv('Scrap_DIA.csv', index=False)