Paul Chris Jones's coding blog
PHP
JS

Using Python to book an appointment with Spain's driving licence agency

11th July 2019

Recently I had to make an appointment with the Spanish driving authority (the DGT) so I could switch my UK driving licence to a Spanish one. However, the simple task of making an appointment was much harder than it sounds. This is because every time I went to the DGT website, it would tell me, "El horario de atención al cliente está completo para los próximos días. Inténtelo más tarde." which roughly translates to "There are no appointments available. Try again later, you fucking fucktard."

I started checking the webpage a few times every day, but still without success. I kept getting the "no appointments available" message.

So I thought, "Fuck this: what I need is a script to check the website for me".

Over the next few days, I wrote a Python script that would keep checking to see if there are any appointments available. If there are appointments available, then the script attempts to book one for me.

I'll go through the script bit by bit. This was my first time using Python so do excuse me if some parts look retarded.

Set the location of Python

The first line indicates where python is on my computer. I think the line is needed for cron to run the script but I'm not really sure anymore to be honest.

#!/usr/bin/python

Set the character set

The second line sets the character set to latin-1. We need to do this because Spanish is a stupid language as some of the letters have accents on them.

# -*- coding: latin-1 -*-

Import modules

This next part imports a bunch of crap that we'll need later.

import time
import datetime
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

Set location of the log file

After this we set the location of a log file so we can come back later and see the results of the script:

log_file = "/home/paul/appointments_log.txt"

The function that checks for available appointments

Next is a function that checks if a "Canje" appointment is available for a citizen of the United Kingdom:

def check_if_appointments_are_available():
        driver.get('https://sedeapl.dgt.gob.es/WEB_NCIT_CONSULTA/solicitarCita.faces')
        change_select_box_to_value("publicacionesForm:oficina",city)
        wait_for("//select[@id='publicacionesForm:tipoTramite']/option[2]")
        change_select_box_to_value("publicacionesForm:tipoTramite","Canje de permiso de conducción europeo")
        wait_for("//select[@id='publicacionesForm:paiscee']/optgroup[2]")
        change_select_box_to_value("publicacionesForm:paiscee","Reino Unido")
        time.sleep(2)
        click_button("publicacionesForm:j_id69")
        time.sleep(5)
        if driver.find_elements_by_class_name("msgError"):
                return False
        else:
                return True
        #elif ("Para estos dos" in driver.page_source) or ("Debido a un problema" in driver.page_source):

The function that books an appointment

After this is a function that tries to book an appointment:

def book_appointment():
        write_message("Attempting to book...")
        click_button("publicacionesForm:area:0:j_id112")
        wait_for("//input[@id='publicacionesForm:j_id169:0:CEEminif']")
        fill_input_box("publicacionesForm:j_id169:0:CEEminif",nie)
        fill_input_box("publicacionesForm:j_id173:3:nombreCEE",first_name)
        fill_input_box("publicacionesForm:j_id178:1:primerApellidoCEE",last_name)
        fill_input_box("publicacionesForm:j_id194:4:CEEelsalvador6",phone_number)
        fill_input_box("publicacionesForm:j_id199:5:CEEelsalvador7",date_of_birth)
        fill_input_box("publicacionesForm:j_id206:11:mypermisocond",driving_licence_number)
        fill_input_box("publicacionesForm:j_id211:12:myemail",email)
        click_button("publicacionesForm:j_id249:8:B") #Class B licence
        click_button("publicacionesForm:autorizacion") #Authorise
        click_button("publicacionesForm:j_id2121") #Solicitar

        #Choose date and time
        wait_for("//select[@id='publicacionesForm:j_id45:0:horario']")
        change_select_box_to_last_value("publicacionesForm:j_id45:0:horario")
        click_button("publicacionesForm:j_id45:0:j_id56")
        time.sleep(10)
        click_button("j_id31:j_id272") #confirm button

        #booking completed page
        time.sleep(10)
        write_message("Appointment booked")
        write_message(driver.page_source)
        click("j_id441:j_id446")
        time.sleep(10)
        write_message(driver.page_source)

I'm proud of the line that begins "change_select_box_to_last_value". What this line does is select the latest appointment of the day. I wrote that line because I don't like early appointments; I'd rather have a lie-in.

Other functions

Next comes an utter fuckload of other functions:

def get_driver():
        if use_headless_driver:
                return get_headless_firefox_driver()
        else:
                return webdriver.Firefox()

def get_headless_firefox_driver():
        from selenium.webdriver.firefox.options import Options as FirefoxOptions
        options = FirefoxOptions()
        options.add_argument("--headless")
        return webdriver.Firefox(options=options)

def change_select_box_to_value(select_box_id, option_to_select):
        select = Select(driver.find_element_by_id(select_box_id))
        select.select_by_visible_text(option_to_select)

def change_select_box_to_last_value(select_box_id):
        select = Select(driver.find_element_by_id(select_box_id))
        length_of_select_box = len(select.options)
        select.select_by_index(length_of_select_box-1)

def wait_for(element_to_wait_for):
        wait = WebDriverWait(driver,10).until(
                EC.presence_of_element_located((By.XPATH, element_to_wait_for))
        )

def click_button(name):
        button = driver.find_element_by_name(name)
        button.click()

def write_message(message):
        message = message.encode('ascii','ignore')
        with open(log_file, 'a') as out:
            out.write(str(datetime.datetime.now()))
            out.write(' ' + message + '\n')

def check_if_appointment_is_already_booked():
        if 'Appointment booked' in open(log_file).read():
            return True

Set the variables

Before we run the script, we have to set the variables.

city = "Girona"
nie = "foobar"
first_name = "foobar"
last_name = "foobar"
phone_number = "foobar"
date_of_birth = "foobar"
driving_licence_number = "foobar"
email = "foobar"

You might notice that I put "foobar" several times. That's not because my name is foobar foobar and I was born on foobar. It's actually because I don't want to reveal my personal details on my blog. I'm smart like that. But rest assured that the actual script contained the real information.

Run all the shit above

Finally comes the main script. This is the part that basically controls all the shit above.

use_headless_driver = False
write_message("Running...")
appointment_is_already_booked = check_if_appointment_is_already_booked()
appointment_is_already_booked = False
if appointment_is_already_booked:
        write_message("An appointment is already booked.")
else:
        driver = get_driver()
        appointments_are_available = check_if_appointments_are_available()
        if appointments_are_available:
                write_message("Appointments available in " + city + "!")
                book_appointment()
        else:
                write_message("No appointments available in " + city)

Automate the script

To make the script run every five minutes, I opened a terminal and wrote the command:

crontab -e

That command opens crontab, which is the system on Linux for automating tasks. Then I inserted this line into the crontab file:

*/5 * * * * python ~/check_for_appointments.py

Then I closed all the files, sat back and waited.

The result

Later that same day, I checked the log file and LO AND BEHOLD I HAD AN APPOINTMENT! The script actually worked!

What's more, the appointment was for two days later. So two days later, I showed up at the driving licence office, showed them all my papers, and wham, bam, thank you ma'am, I had a Spanish driving licence! Well, truthfully, they only gave me a temporary licence. I have to wait for the real one to arrive in the post. But it's the same difference.

Leave a comment