Hay un fantasma en mi browser… se llama Selenium

TL;DR Ya sea para realizar automatización de procesos o para hacer pruebas automaticas de software, tenemos en nuestro kit de herramientas un paquete muy importante para ello. Selenium crea una instancia de tu browser (Chrome, Chromium, Firefox, IE… Si aun IE) y te deja interactuar con cada uno de los elementos de las páginas de manera que puedas revisar desde que existe, hasta automatizar procesos repetitivos dentro del explorador.

Hola todos! hoy vamos a hablar un poco sobre una herramienta que debe estar en todos sus bati-cinturones, es Selenium, que nos permite interactuar con un browser para poder crear pruebas automaticas para nuestras páginas o automatizar procesos repetitivos.

En mi caso particular, utilizo python pero puedes utilizar la herramienta con diferentes lenguajes. siendo así comencemos 😁

Lo primero a hacer, es instalar selenium, y basta con la magia de pip para instalar.

$ pip install selenium

Lo segundo a entender que es el webdriver, webdriver es una interfaz que se conecta a un explorador (chrome, firefox, etc) y que puede enviar ordenes (traducidas) a cada uno de ellós de manera que realiza las veces de usuario dentro de la interfaz grafica del sitio. Para crear una instancia de un explorador (en nuestro ejemplo usaremos firefox).

from selenium.webdriver.firefox.webdriver import WebDriver
driver = WebDriver()

Pero, oh sorpresa, probablemente econtrarán un error, ‘geckodriver’ executable needs to be in PATH. Esto nos dice que el archivo con el driver “gecko” (que es para mozilla firefox, para chrome es chromedriver, etc.) necesita estar dentro de nuestro path, para ello entonces descargamos el geckodriver de aqui, según el sistema operativo que esten utilizando. Una vez hecho eso, descomprimimos dentro de nuestra carpeta de proyecto. Yo utilizo mucho el siguiente árbol de directorios:

—/
|——————–driver/
|——————–|————– geckodriver(chromedriver…etc)
|——————–main.py

con esto podemos recoger el directorio actual (donde se encuentra el main.py) al estilo de django de la siguiente manera:

BASE_DIR = os.path.dirname(__file__)

y así podemos cambiar la ejecución un poco de nuestro WebDriver, para enviar la dirección en la que se encuentra nuestro driver de la siguiente manera:

from selenium.webdriver.firefox.webdriver import WebDriver
driver = WebDriver(executable_path=os.path.join(BASE_DIR, 'driver', 'geckodriver'))

Perfecto, así instanciamos un explorador firefox con el cual podemos interactuar y revisar, por ello el siguiente paso es cargar una url la cual vamos a probar, por ejemplo, podemos intentar entrar a youtube y buscar un buen video para elló le indicaremos al driver cual es la dirección de Youtube.

driver.get("https://google.com")

Esta linea forzará al explorador a ir a google por nosotros y esperará en ejecución hasta que la página este 100% cargada. veremos algo como lo siguiente:

Google.png
selenium entrando a google

Perfecto, ahora comencemos a interactuar con los elementos del dom de la página, para ello vamos a revisar conceptos elementales de HTML y CSS, que son los ID, las clases, los atributos de las tags, selectores CSSs y ¿que diablos es un XPATH?.

Primero entremos en otro navegador y abramos el inspector de elementos realizando click derecho en cualquier parte de la página y le damos en inspector de elementos:

Screen Shot 2018-07-12 at 11.28.41 AM.png
es necesario aprender sobre inspeccionar elemento para trabajar con selenium
Screen Shot 2018-07-12 at 11.28.54 AM.png
inspector de elementos, el mejor amigo de selenium

Perfecto, con el inspector vamos a revisar cada una de las propiedades de los elementos con los que vamos a interactuar, lo primero intentaremos recuperar el buscador a través de ID, el atributo id en una tag debería ser “único” en todo el Dom (No hay quien coloque mas de una véz un id en el código, pero no debería ser así). Para ello vamos a obtener ese elemento de search con id “lst-ib” a través de la linea de código:

search = driver.find_element_by_id("lst-ib")

de esta manera obtenemos el elemento de la barra de search, y ahora podemos escribir en ella, de la siguiente manera, por ejemplo:

search.send_keys("Zombie Cranberries")

Y listo!, así escribimos dentro de la barra de busqueda, veamos el resultado:

Screen Shot 2018-07-12 at 11.30.26 AM.png
selenium escribiendo sobre el buscador de google

Ahora, vamos a interactuar con el boton de busqueda, en un navegador propio veamos cual es el ID o clase que utiliza el boton de google:

Screen Shot 2018-07-12 at 11.31.50 AM.png

Como vemos, el boton no tiene un ID especifico, pero tiene un “name” que podemos utilizar para localizarlo, para ello cambiaremos el metodo utilizado, de find_element_by_id a find_element_by_name:

btn = driver.find_element_by_name("btnK")

Perfecto!, la otra interacción que haremos con el, es realizar un click

btn.click()

Eureka! realizado, ya vamos bastante avanzados, ¡vamos con selectores!

Screen Shot 2018-07-12 at 11.42.39 AM.png
selenium trae los resultados de la busqueda

Ahora, la idéa es entrar en ese enlace de Youtube que esta en primer lugar en google, para ello inspeccionamos nuevamente:

Screen Shot 2018-07-12 at 11.50.18 AM.png
buscando elementos para pasar la referencia a selenium

como vemos, la etiqueta “a” no tiene un identificador propio, por ello vamos a utilizar la etiqueta “h3” con clase “r”, y luego buscamos los “a” que se encuentren dentro de el, para ello vamos a probar con document.querySelectorAll en la consola de firefox:

Screen Shot 2018-07-12 at 11.53.58 AM.png
si funciona en un queryselector, funciona en selenium

al ejecutar vemos que hay 9 elementos que cumplen el selector “h3.r a” (h3 con classe r y dentro un a”), entonces revisemos cuales son esos a que lo cumplen, veamos el primero al colocar el mouse sobre el cuadrado que se encuentra al lado de cada a del resultado (icono de target).

Screen Shot 2018-07-12 at 11.55.34 AM.png
buscando un link con selenium

Efectivamente, el primero es el a que tiene el link de youtube, y si seguimos nos daremos cuenta que todas las etiquetas “a” son resultados de busqueda, entonces utilicemos nuestra siguiente línea de código.

link = driver.find_element_by_css_selector("h3.r a")

Y luego hacemos click. Hay que tener en cuenta el nombre de la funcion, al ser “find_element_by_css_selector” el solo va a traer el primer elemento que se encuentre con esta deficinición de selector, si utilizamos “find_elements_by_css_selector”, vemos que esta traerá una lista de resultados, por esto hay que tener cuidado con cual funcion vamos a utilizar.

link.click()

Y listo, ya estamos dentro de youtube, con nuestro video reproduciendo 🙂

Screen Shot 2018-07-12 at 12.07.19 PM.png
selenium abriendo youtube

(Sorry por la propaganda de LG!)

Ok, ya hemos hablado sobre varias formas de seleccionar nuestros elementos, a través de ID, de selectores css y por name, ahora que tal por clase, cambiemos de video, vamos a ahcer click al video de “a continuación” a través de clases, entonces vamos a la consola de inspección de firefox nuevamente 😁

Screen Shot 2018-07-13 at 10.39.08 PM.png
un elemento de selenium, también actúa como su propio DOM

Bien, al hacer un queryselector con la class .ytd-compact-video-renderer obtenemos todo el div con el video, y hasta allí nos sirve, luego vamos a ver como sacamos solo el link, por ahora obtengamos el thumbnail del video.

thumb = driver.find_element_by_class_name('ytd-compact-video-renderer')

Igual que en el anterior hay que fijarse que existen dos metodos, uno con find_elements que retorna una lista y uno find_element que solo devuelve el primer elemento, pero ahora, que haremos con eso, bueno revisando el inspector de firefox nos damos cuenta que el elemento a dar click es, si, un “a” entonces en selenium, un nodo ya encontrado actua como un DOM, por ende puedo seguir la busqueda a partir del nodo de thumb y buscar por etiqueta.

link = thumb.find_element_by_tag_name('a')
link.click()

Y así podemos realizar mas busquedas exactas del elemento, y cubrimos dos nuevas funciones, una para buscar por clase y otra para buscar por nombre de la etiqueta 😁

pero oh sorpresa al ejecutar… la ejecución a veces es mas rápida que el navegador, y si es un problema, por ahora utilizaremos la función sleep de la librería time para aguantar la ejecución un poco antes que intente hacer clicks en los links.

import time
...
link = thumb.find_element_by_tag_name('a')
time.sleep(2)
link.click()

Perfecto, funcionando! si tienen algun problema antes con la “asincronidad” de los navegadores, pro ahora podemos realizar scripts, mas adelante veremos como realizar esto de una manera mas “inteligente”.

Screen Shot 2018-07-13 at 11.07.15 PM.png
Si, selenium también trabaja con XPATH

Ahora, intentemos lo mismo pero utilizando xpath, vamos nuevamente a nuestro inspector y busquemos el “a” al que haremos click con selenium, al encontrarlo vamos a hacer click derecho sobre el en el inspector y vamos al menú copiar, y allí seleccionaremos xpath:

al pegar encontraremos algo parecido a lo siguiente “/html/body/ytd-app/div[1]/ytd-page-manager/ytd-watch/div[2]/div[2]/div/div[1]/ytd-watch-next-secondary-results-renderer/div[2]/ytd-compact-autoplay-renderer/div[2]/ytd-compact-video-renderer/div[1]/a”

¿Ok, pero que es eso?, es exactamente la ruta en el arbol html hacia el a a realizar click, por ejemplo, todo documento de html comienza con una etiqueta “html”, de allí pasamos a una etiqueta “body”, hmm youtube utiliza algun framework de front end (¿100% seguro no?), pues la etiqueta ytd-app no es una etiqueta html, pero es una custom que se puede crear para encapsular componentes, pero sigamos, de allí vamos a buscar el primer div dentro de la etiqueta ytd-app, eso significa el uno entre los corchetes (div[1]), y así continua etiqueta por etiqueta hasta llegar a la etiqueta “a” (¿un poco largo no?) pero a veces es el ultimo recurso que tenemos para algunos elementos, entonces realizaremos un click a ese elemento como lo hicimos anterior mente pero con un nuevo metodo:

xpath = "/html/body/ytd-app/div[1]/ytd-page-manager/ytd-watch/div[2]/div[2]/div/div[1]/ytd-watch-next-secondary-results-renderer/div[2]/ytd-compact-autoplay-renderer/div[2]/ytd-compact-video-renderer/div[1]/a"
time.sleep(2)
link = driver.find_element_by_xpath(xpath)
link.click()

ok, si corremos y vemos que nos cambia igual al siguiente video 😃

Pero… a este punto comenzamos a sentir un comportamiento erratico dentro del driver, y tantos sleeps dentro de nuestro codigo, ¿es esto necesario, o esta bien?, la respuesta es no… selenium puede esperar los elementos de una manera mas “inteligente”, para elló voy a adentrarme un poco mas ¡en mi proximo post!, así que por ahora practiquen mucho y hasta la proxima.

TBD: Voy a dejar el código realizado en mi github en un enlace aquí.

TBD II: Voy a dejar el link del proximo post de selenium por aquí, una vez lo tengo cambiaré este TBD.

Nos vemos 😉

 

Inky Ghost by ✦ Shmidt Sergey ✦ from the Noun Project.