From f186973aded9198b0589eab6468d5270d059a425 Mon Sep 17 00:00:00 2001 From: David Park Date: Tue, 9 Apr 2024 23:27:15 +0100 Subject: [PATCH] feat: Add support for West Berkshire Council --- .../features/validate_council_outputs.feature | 5 + uk_bin_collection/tests/input.json | 9 ++ .../councils/WestBerkshireCouncil.py | 113 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py diff --git a/uk_bin_collection/tests/features/validate_council_outputs.feature b/uk_bin_collection/tests/features/validate_council_outputs.feature index 76e10c8509..2a8baf9974 100644 --- a/uk_bin_collection/tests/features/validate_council_outputs.feature +++ b/uk_bin_collection/tests/features/validate_council_outputs.feature @@ -676,6 +676,11 @@ Feature: Test each council output matches expected results | council | selenium_url | selenium_mode | | WelhatCouncil | None | None | + @WestBerkshireCouncil + Examples: WestBerkshireCouncil + | council | selenium_url | selenium_mode | + | WestBerkshireCouncil | http://selenium:4444 | local | + @WestLindseyDistrictCouncil Examples: WestLindseyDistrictCouncil | council | selenium_url | selenium_mode | diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index f0285b9699..c2e841fc32 100644 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -937,6 +937,15 @@ "url": "https://www.welhat.gov.uk/xfp/form/214", "wiki_name": "Welhat Council" }, + "WestBerkshireCouncil": { + "house_number": "8", + "postcode": "RG14 7DP", + "skip_get_url": true, + "url": "https://www.westberks.gov.uk/binday", + "web_driver": "http://selenium:4444", + "wiki_name": "West Berkshire Council", + "wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes and the postcode in the postcode parameter" + }, "WestLindseyDistrictCouncil": { "house_number": "PRIVATE ACCOMODATION", "postcode": "LN8 2AR", diff --git a/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py new file mode 100644 index 0000000000..9d2f462e90 --- /dev/null +++ b/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py @@ -0,0 +1,113 @@ +import time + +from bs4 import BeautifulSoup +from dateutil.relativedelta import relativedelta +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.wait import WebDriverWait + +from uk_bin_collection.uk_bin_collection.common import * +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + + +# import the wonderful Beautiful Soup and the URL grabber +class CouncilClass(AbstractGetBinDataClass): + """ + Concrete classes have to implement all abstract operations of the + base class. They can also override some operations with a default + implementation. + """ + + def parse_data(self, page: str, **kwargs) -> dict: + driver = None + try: + data = {"bins": []} + collections = [] + user_paon = kwargs.get("paon") + user_postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + check_paon(user_paon) + check_postcode(user_postcode) + + # Create Selenium webdriver + # driver = create_webdriver(web_driver, headless) + driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install())) + driver.get( + "https://www.westberks.gov.uk/binday" + ) + + # Wait for the postcode field to appear then populate it + inputElement_postcode = WebDriverWait(driver, 30).until( + EC.presence_of_element_located( + (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPPOSTCODE") + ) + ) + inputElement_postcode.send_keys(user_postcode) + + # Click search button + findAddress = WebDriverWait(driver, 10).until( + EC.presence_of_element_located( + (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPSEARCH") + ) + ) + findAddress.click() + + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + ( + By.XPATH, + "" + "//*[@id='FINDYOURBINDAYS_ADDRESSLOOKUPADDRESS']//option[contains(., '" + + user_paon + + "')]", + ) + ) + ).click() + + # Wait for the submit button to appear, then click it to get the collection dates + WebDriverWait(driver, 30).until( + EC.presence_of_element_located( + (By.XPATH, '//*[@id="FINDYOURBINDAYS_RUBBISHDATE"]/div') + ) + ) + time.sleep(2) + + soup = BeautifulSoup(driver.page_source, features="html.parser") + soup.prettify() + + rubbish_date = datetime.strptime(' '.join(soup.find("div", {"id": "FINDYOURBINDAYS_RUBBISHDATE_OUTERDIV"}).get_text(strip=True).split()[6:8]), "%d %B").replace(year=datetime.now().year) + recycling_date = datetime.strptime(' '.join(soup.find("div", {"id": "FINDYOURBINDAYS_RECYCLINGDATE_OUTERDIV"}).get_text(strip=True).split()[6:8]), "%d %B").replace(year=datetime.now().year) + food_date = datetime.strptime(' '.join(soup.find("div", {"id": "FINDYOURBINDAYS_FOODWASTEDATE_OUTERDIV"}).get_text(strip=True).split()[8:10]), "%d %B").replace(year=datetime.now().year) + + if datetime.now().month == 12 and rubbish_date.month == 1: + rubbish_date = rubbish_date + relativedelta(years=1) + if datetime.now().month == 12 and recycling_date.month == 1: + recycling_date = recycling_date + relativedelta(years=1) + if datetime.now().month == 12 and food_date.month == 1: + food_date = food_date + relativedelta(years=1) + + collections.append(("Rubbish bin", rubbish_date)) + collections.append(("Recycling bin", recycling_date)) + collections.append(("Food waste bin", food_date)) + + ordered_data = sorted(collections, key=lambda x: x[1]) + for item in ordered_data: + dict_data = { + "type": item[0].capitalize(), + "collectionDate": item[1].strftime(date_format), + } + data["bins"].append(dict_data) + + print() + except Exception as e: + # Here you can log the exception if needed + print(f"An error occurred: {e}") + # Optionally, re-raise the exception if you want it to propagate + raise + finally: + # This block ensures that the driver is closed regardless of an exception + if driver: + driver.quit() + return data