Automatizando el llenado de formularios en Python con Selenium

· 6 min de lectura
Automatizando el llenado de formularios en Python con Selenium


En Uruguay se está llevando a cabo la campaña de vacunación para el COVID-19 y el mismo se hace por etapas, en mi caso particular, por mi franja etaria, estaba listo para vacunarme pero tenía que esperar a que se cumplan tres meses desde que contraje COVID.

Para comprobar si ya estaba habilitado para agendarme y que luego me asignen un turno para recibir la vacuna, requería que complete un formulario en una web.

Como no estaba dispuesto a entrar cada tanto a revisarlo y estoy aprendiendo Python, dije, ¿por qué no automatizarlo con Selenium? así que lo que hice fue un programa que al correrlo llene el formulario, lo envíe y el resultado me lo mande como una captura de pantalla a un canal de Telegram que uso para todo tipo de notificaciones.

Veamos... Este "programa" que "desarrolle" corre en ambientes Unix-Like. Para Windows seguramente haya que ajustar algunas cosas.

¿Qué necesitamos?

Bueno, este programa necesita de algunos componentes que hay que descargar aparte. Los mismos son los siguientes:

  • El navegador Chrome. Necesitan descargarlo e instalarlo, pueden hacerlo desde acá:
Navegador web Google Chrome
Más sencillo, seguro y rápido que nunca gracias a las funciones inteligentes de Google.
  • Chromedriver: Este es clave y necesario. Protip. Asegúrense de que bajen la que tiene el mismo número de versión que el navegador:
Downloads - ChromeDriver - WebDriver for Chrome
WebDriver for Chrome
  • Selenium: Este es quien va a llenar el formulario.
$ pip3 install selenium
  • Telepot: Este paquete es el que va a enviar el resultado a nuestro canal o chat de Telegram.
$ pip3 install telepot

Una vez que está todo esto instalado, veamos el código.

El código

El mismo está acá y más abajo lo pueden encontrar en un repo de Github donde estoy empezando a subir todos estos proyectos.

from selenium.webdriver import Chrome
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.chrome.options import Options
import telepot
import time

bot = telepot.Bot('token-bot-telegram')

driver_options = Options()
driver_options.add_argument('--headless')
driver_options.add_argument('--no-sandbox')
driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=driver_options)
driver.get('https://agenda.vacunacioncovid.gub.uy/')

def slow_typing(element, text):
    for character in text:
        element.send_keys(character)
        time.sleep(0.3)

def ci():
    ci = driver.find_element_by_id('numberCID')
    slow_typing(ci, 'numero-ci')

def db():
    daybirth = driver.find_element_by_name('dateBirth[day]')
    slow_typing(daybirth, 'xx')

def mb():
    monthbirth = Select(driver.find_element_by_id('mes'))
    monthbirth_selected = monthbirth.select_by_visible_text("noviembre")

def yb():
    daybirth = driver.find_element_by_name('dateBirth[year]')
    slow_typing(daybirth, 'xxxx')

def sb():
    submit = driver.find_element_by_id('btnCheckData')
    submit.click()

def screenshot():
    time.sleep(3)
    screenshot = driver.save_screenshot("screenshot.png")

def message():
    bot.sendMessage(chat-id, 'Acabo de chequear si puedes vacunarte')
    bot.sendPhoto(chat-id, photo=open('screenshot.png', 'rb'))

ci()
db()
mb()
yb()
sb()
screenshot()
message()

Ahora bien, vamos a analizar cada parte para entender qué es lo que necesitamos cambiar y que es lo que hace.

Primero, cada parte está definida en una función, esto lo hice así por un tema de prolijidad y si es que esto necesita escalar de alguna manera, puedo jugar con la función y no estar tocando directamente el contenido de la función.

bot = telepot.Bot('token-bot-telegram')

Esto de acá arriba es donde definimos el token de nuestro Bot de Telegram. Si querés saber cómo crear un Bot de Telegram, mirate este artículo:

Bots: An introduction for developers
Bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands…

Lo siguiente que vamos a revisar acá abajo es alguna configuración de Chrome y Chromedriver. Veamos:

driver_options = Options()
driver_options.add_argument('--headless')
driver_options.add_argument('--no-sandbox')
driver_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=driver_options)
driver.get('https://agenda.vacunacioncovid.gub.uy/')

Primero debemos pasar algunos argumentos:

  • --headless: Este es para que el mismo no abra una ventana de Chrome, esto es mucho muy importante, ya que si esto lo corremos en una terminal, no hay ventana que abrir y el programa crasheara.
  • --no-sandbox: Acá le decimos a Chrome que esto no es un ambiente de dev o testing.
  • Lo siguiente que debemos especificar es el path donde descargamos e instalamos Chromedriver, mi recomendación es que una vez descargado, lo tiremos en /usr/local/bin/.
  • Por último definiremos el sitio web con el que queremos que nuestro programa trabaje. En mi caso, fue el sitio la agenda de vacunación del MSP.

Sigamos adelante, ahora veamos la función que sigue...

def slow_typing(element, text):
    for character in text:
        element.send_keys(character)
        time.sleep(0.3)

Esta es muy sencilla, lo que defino acá es que el tipo de datos a ingresar sea a una velocidad de 0.3 segundos por letra.

Lo siguiente es la definición en funciones de cada uno de los campos que necesito completar:

def ci():
    ci = driver.find_element_by_id('numberCID')
    slow_typing(ci, 'numero-ci')

def db():
    daybirth = driver.find_element_by_name('dateBirth[day]')
    slow_typing(daybirth, 'xx')

def mb():
    monthbirth = Select(driver.find_element_by_id('mes'))
    monthbirth_selected = monthbirth.select_by_visible_text("noviembre")

def yb():
    daybirth = driver.find_element_by_name('dateBirth[year]')
    slow_typing(daybirth, 'xxxx')

def sb():
    submit = driver.find_element_by_id('btnCheckData')
    submit.click()

def screenshot():
    time.sleep(3)
    screenshot = driver.save_screenshot("screenshot.png")

def message():
    bot.sendMessage(chat-id, 'Acabo de chequear si puedes vacunarte')
    bot.sendPhoto(chat-id, photo=open('screenshot.png', 'rb'))

ci()
db()
mb()
yb()
sb()
screenshot()
message()

Vean que necesitaba definir mi cédula de identidad, la fecha de nacimiento en tres campos, dos que contaban con datos a ingresar y una en donde necesitaba seleccionar el mes de una lista.

Defino el clic, tomó un screenshot que lo guardó en un archivo llamado screenshot.png y el mismo es enviado junto con un mensaje a un chat de Telegram.

Lo importante de acá es saber cómo saber dónde "poner" los datos, si se fijan en la función donde ingreso mi número de Cédula, día de nacimiento, mes, y año me baso en el elemento HTML por nombre y algunos otros por ID. Ahora, la siguiente pregunta puede ser ¿Cómo encontramos esto? y la respuesta es viendo el código del sitio web.

En mi caso, definí el ingreso de mi CI por el ID del elemento (numberCID):

<input required="" class="Form-widget" name="numberCID" type="text" id="numberCID" inputmode="numeric">

Y Así con el resto de los dos valores que necesitaba ingresar.

El que fue medio tricky para mi, fue el seleccionar desde un dropdown el mes de nacimiento. Para esa parte lo resolví de la siguiente manera:

def mb():
    monthbirth = Select(driver.find_element_by_id('mes'))
    monthbirth_selected = monthbirth.select_by_visible_text("noviembre")

Una vez definido todo, llamo a todas las funciones así:

ci()
db()
mb()
yb()
sb()
screenshot()
message()

El resultado se veía así:

Para ir cerrando

Armar soluciones chiquitas de este estilo me ayudan un montón a seguir aprendiendo Python. Probablemente, ante un ojo experimentado, todo esto pueda hacerse mejor y esta bien, creo que iré aprendiendo a ir mejorando mi código en cada iteración.

Ahora bien, una buena forma de seguir practicando es pensar en qué otros casos esto se podría aplicar...

Espero que este artículo te sirva y estén atentos porque vienen más como estos, donde con Python automatizo pequeñas cosas todos los días.

Repo de Gihub con el código completo:

xe-nvdk/awesome-python-projects
Place where I save small programs that I created in the process of learning Python. - xe-nvdk/awesome-python-projects

Créditos imagen de portada:

Photo by Chris Ried on Unsplash
Download this photo by Chris Ried on Unsplash

baehost