6
\$\begingroup\$

Description

I'm not sure if it Covid-19 but lately it is impossible to book a tennis court in my area on time. It's always full or maybe just don't check enough :)

To beat the queue and get notified earlier, I started making a scraper to ensure I can book a court on time.

But because of iframe hell and the lack (to my knowledge) of an API it was impossible to do it the easy way, hence I used selenium. I coded this today and it works pretty well, although it's already become a bit of a mess.

Todo

Dynamically detecting the hours available and starting time of the day, so I don't have to supply those values anymore, because my guess is they will change (depending on the time it will get dark etc.)

You can still flame me for it, but I just haven't found a good way to do it yet, now I just supply them in the COURTS dictionary

Code

#!/usr/bin/python3

import time

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

# At what minimal time I am willing/able to book
WEEKEND_TIME = 10
WEEK_TIME = 18

COURTS = {
    # Some courts have different hours available and the hour at which you can start can differ
    # Thus it is key -> name with values -> url, hours available, time diff start
    "Bethnal Green Gardens" : ["https://www.towerhamletstennis.org.uk/bethnal-green-gardens-court-bo", 14, 0],
    "St Johns Park"         : ["https://www.towerhamletstennis.org.uk/st-johns-park-court-boo", 14, 1],
    "Poplar Rec"            : ["https://www.towerhamletstennis.org.uk/poplar-rec-court-bookin", 12, 0],
    "King Edward's Park"    : ["https://www.towerhamletstennis.org.uk/kemp-court-bookin", 12, 0],
    "Victoria Park"         : ["https://www.towerhamletstennis.org.uk/victoria-park-court-book", 12, 0],
}

def get_week_schedule(driver, time_length, time_start, first_week=True):
    elem = driver.find_element_by_xpath('//div[@class="cal_time cal_time_time"]')
    sched = [e.text for e in elem.find_elements_by_xpath('//div[@class="inc"]')]
    times = sched[time_start:17]
    active = sched[17:]

    # When first week we only want from x till sunday and second week monday till x
    days = ["Sunday", "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday"]
    active_days = days[:len(active) // time_length][::-1] if first_week \
             else days[::-1][:len(active) // time_length]
    
    week_schedule = {}
    for i, active_day in enumerate(active_days):
        week_schedule[active_day] = {
            int(times[j].split(":")[0]) : active[i * time_length + j]
            for j in range(time_length)
        }
    return week_schedule

def get_court_schedule(driver, data):
    url, time_length, time_start = data
    driver.get(url)
    
    # Yo dawg, I heard you like iframes so I put an iframe in your iframe...
    WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH, "//iframe")))    
    driver.switch_to.frame(driver.find_element_by_tag_name("iframe"))
    driver.switch_to.frame(driver.find_element_by_tag_name("iframe"))

    court_schedule = {}
    court_schedule["this"] = get_week_schedule(driver, time_length, time_start)

    # Next week is second element
    elem = driver.find_elements_by_xpath("//a[@href]/span")
    elem[1].click()
    # TODO Fix time.sleep...  but same page so how can we wait?
    time.sleep(5)
    court_schedule["next"] = get_week_schedule(driver, time_length, time_start, first_week=False)

    return court_schedule

def get_schedule():
    driver = webdriver.Chrome()

    court_schedules = {
        court_name : get_court_schedule(driver, url)
        for court_name, url in COURTS.items()
    }

    driver.quit()
    return court_schedules

def get_available_dates(schedule):
    for court_name, court_schedule in schedule.items():
        for week, week_schedule in court_schedule.items():
            for day, day_schedule in week_schedule.items():
                earliest_time = WEEKEND_TIME if day in ("Saturday", "Sunday") else WEEK_TIME
                for hour, available in day_schedule.items():
                    if hour >= earliest_time and available not in ("Full", " "):
                        print(f"Can book at court {court_name} {week} week on {day} at {hour} for price {available}")

if __name__ == "__main__":
    schedule = get_schedule()
    get_available_dates(schedule)

Example output

./tennis.py
Can book at court Poplar Rec next week on Saturday at 10 for price £6
Can book at court Poplar Rec next week on Saturday at 11 for price £6
Can book at court King Edward's Park this week on Saturday at 18 for price £6
Can book at court King Edward's Park next week on Friday at 18 for price £6
Can book at court King Edward's Park next week on Saturday at 10 for price £6
Can book at court King Edward's Park next week on Saturday at 11 for price £6
\$\endgroup\$
  • 1
    \$\begingroup\$ Hey. Are the various PEP 8 violations intentional? \$\endgroup\$ – Peilonrayz Sep 25 at 19:01
  • \$\begingroup\$ I am aware of them, yes. :) \$\endgroup\$ – Ludisposed Sep 25 at 19:03
  • \$\begingroup\$ Ah, I though so. :) Might be worth mentioning that in the post. \$\endgroup\$ – Peilonrayz Sep 25 at 19:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.