uk_bin_collection 0.152.8__py3-none-any.whl → 0.152.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. uk_bin_collection/tests/input.json +14 -20
  2. uk_bin_collection/uk_bin_collection/councils/AngusCouncil.py +69 -46
  3. uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +119 -37
  4. uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +158 -115
  5. uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py +87 -66
  6. uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py +5 -1
  7. uk_bin_collection/uk_bin_collection/councils/BlaenauGwentCountyBoroughCouncil.py +91 -66
  8. uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py +103 -67
  9. uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +67 -56
  10. uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +63 -95
  11. uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py +39 -18
  12. uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +106 -97
  13. uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py +80 -75
  14. uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py +191 -67
  15. uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py +6 -2
  16. uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py +27 -116
  17. uk_bin_collection/uk_bin_collection/councils/EastLothianCouncil.py +27 -39
  18. uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py +61 -56
  19. uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py +80 -10
  20. uk_bin_collection/uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py +1 -1
  21. uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +112 -36
  22. uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py +23 -1
  23. uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +4 -1
  24. uk_bin_collection/uk_bin_collection/get_bin_data.py +1 -1
  25. {uk_bin_collection-0.152.8.dist-info → uk_bin_collection-0.152.10.dist-info}/METADATA +1 -1
  26. {uk_bin_collection-0.152.8.dist-info → uk_bin_collection-0.152.10.dist-info}/RECORD +29 -30
  27. uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +0 -69
  28. {uk_bin_collection-0.152.8.dist-info → uk_bin_collection-0.152.10.dist-info}/LICENSE +0 -0
  29. {uk_bin_collection-0.152.8.dist-info → uk_bin_collection-0.152.10.dist-info}/WHEEL +0 -0
  30. {uk_bin_collection-0.152.8.dist-info → uk_bin_collection-0.152.10.dist-info}/entry_points.txt +0 -0
@@ -3,19 +3,14 @@ from selenium.webdriver.common.by import By
3
3
  from selenium.webdriver.support import expected_conditions as EC
4
4
  from selenium.webdriver.support.ui import Select
5
5
  from selenium.webdriver.support.wait import WebDriverWait
6
+ import re
7
+ import time
6
8
 
7
9
  from uk_bin_collection.uk_bin_collection.common import *
8
10
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
9
11
 
10
12
 
11
- # import the wonderful Beautiful Soup and the URL grabber
12
13
  class CouncilClass(AbstractGetBinDataClass):
13
- """
14
- Concrete classes have to implement all abstract operations of the
15
- base class. They can also override some operations with a default
16
- implementation.
17
- """
18
-
19
14
  def parse_data(self, page: str, **kwargs) -> dict:
20
15
  driver = None
21
16
  try:
@@ -29,85 +24,115 @@ class CouncilClass(AbstractGetBinDataClass):
29
24
 
30
25
  # Create Selenium webdriver
31
26
  driver = create_webdriver(web_driver, headless, None, __name__)
32
- driver.get(
33
- "https://iportal.itouchvision.com/icollectionday/collection-day/?uuid=238D5F9796C12643D190E3505931401A8C003F0D&lang=en"
27
+
28
+ # Navigate to the main page first
29
+ driver.get("https://www.blaenau-gwent.gov.uk/en/resident/waste-recycling/")
30
+
31
+ # Handle cookie overlay if present
32
+ try:
33
+ # Wait a moment for any overlays to appear
34
+ WebDriverWait(driver, 3).until(
35
+ EC.presence_of_element_located((By.ID, "ccc-overlay"))
36
+ )
37
+ # Try to find and click cookie accept buttons
38
+ cookie_buttons = [
39
+ "//button[contains(text(), 'Accept')]",
40
+ "//button[contains(text(), 'OK')]",
41
+ "//button[@id='ccc-recommended-settings']",
42
+ "//button[contains(@class, 'cookie')]"
43
+ ]
44
+ for button_xpath in cookie_buttons:
45
+ try:
46
+ cookie_button = driver.find_element(By.XPATH, button_xpath)
47
+ if cookie_button.is_displayed():
48
+ cookie_button.click()
49
+ break
50
+ except:
51
+ continue
52
+ except:
53
+ pass # No cookie overlay found
54
+
55
+ # Find and extract the collection day URL
56
+ find_collection_link = WebDriverWait(driver, 10).until(
57
+ EC.presence_of_element_located((By.XPATH, "//a[contains(text(), 'Find Your Collection Day')]"))
34
58
  )
59
+ collection_url = find_collection_link.get_attribute("href")
60
+
61
+ # Navigate to the collection portal
62
+ driver.get(collection_url)
35
63
 
36
- # Wait for the postcode field to appear then populate it
37
- inputElement_postcode = WebDriverWait(driver, 10).until(
64
+ # Wait for the postcode field and enter postcode
65
+ postcode_input = WebDriverWait(driver, 10).until(
38
66
  EC.presence_of_element_located((By.ID, "postcodeSearch"))
39
67
  )
40
- inputElement_postcode.send_keys(user_postcode)
68
+ postcode_input.send_keys(user_postcode)
41
69
 
42
- # Click search button
43
- findAddress = WebDriverWait(driver, 10).until(
44
- EC.presence_of_element_located(
45
- (By.XPATH, '//button[@class="govuk-button mt-4"]')
46
- )
70
+ # Click Find button
71
+ find_button = WebDriverWait(driver, 10).until(
72
+ EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Find')]"))
47
73
  )
48
- findAddress.click()
74
+ find_button.click()
49
75
 
50
- # Wait for the dropdown to be visible
76
+ # Wait for address dropdown and select by UPRN
51
77
  WebDriverWait(driver, 10).until(
52
78
  EC.presence_of_element_located((By.ID, "addressSelect"))
53
79
  )
54
-
55
80
  dropdown = Select(driver.find_element(By.ID, "addressSelect"))
56
81
  dropdown.select_by_value(user_uprn)
57
82
 
58
- # Wait for the collections table to appear
59
- WebDriverWait(driver, 10).until(
60
- EC.presence_of_element_located(
61
- (
62
- By.XPATH,
63
- '//div[@class="ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"]',
64
- )
65
- )
83
+ # Wait for collection data to load
84
+ time.sleep(3) # Give JavaScript time to process the selection
85
+
86
+ # Wait for the actual collection data to appear
87
+ WebDriverWait(driver, 20).until(
88
+ lambda d: "Your next collections" in d.page_source and ("Recycling" in d.page_source or "Refuse" in d.page_source)
66
89
  )
67
90
 
68
91
  soup = BeautifulSoup(driver.page_source, features="html.parser")
69
-
70
- recyclingcalendar = soup.find(
71
- "div",
72
- {
73
- "class": "ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"
74
- },
75
- )
76
-
77
- rows = recyclingcalendar.find_all(
78
- "div",
79
- {
80
- "class": "ant-col ant-col-xs-12 ant-col-sm-12 ant-col-md-12 ant-col-lg-12 ant-col-xl-12 css-2rgkd4"
81
- },
82
- )
83
-
84
- current_year = datetime.now().year
85
- current_month = datetime.now().month
86
-
87
- for row in rows:
88
- BinType = row.find("h3").text
89
- collectiondate = datetime.strptime(
90
- row.find("div", {"class": "text-white fw-bold"}).text,
91
- "%A %d %B",
92
- )
93
- if (current_month > 10) and (collectiondate.month < 3):
94
- collectiondate = collectiondate.replace(year=(current_year + 1))
95
- else:
96
- collectiondate = collectiondate.replace(year=current_year)
97
-
98
- dict_data = {
99
- "type": BinType,
100
- "collectionDate": collectiondate.strftime("%d/%m/%Y"),
101
- }
102
- data["bins"].append(dict_data)
92
+ page_text = soup.get_text()
93
+
94
+ # Find the collections section in the text
95
+ if "Your next collections" in page_text:
96
+ # Extract the section after "Your next collections"
97
+ collections_section = page_text.split("Your next collections")[1]
98
+ collections_section = collections_section.split("Related content")[0] # Stop at Related content
99
+
100
+ # Use regex to find collection patterns
101
+ # Pattern to match: "Collection Type" followed by "Day Date Month" (stopping before 'followed')
102
+ pattern = r'(Recycling collection|Refuse Bin)([A-Za-z]+ \d+ [A-Za-z]+)(?=followed|$|[A-Z])'
103
+ matches = re.findall(pattern, collections_section)
104
+
105
+ for bin_type, date_text in matches:
106
+ try:
107
+ # Clean up the date text
108
+ date_text = date_text.strip()
109
+ if "followed by" in date_text:
110
+ date_text = date_text.split("followed by")[0].strip()
111
+
112
+ # Parse the date
113
+ collection_date = datetime.strptime(date_text, "%A %d %B")
114
+
115
+ # Set the correct year
116
+ current_year = datetime.now().year
117
+ current_month = datetime.now().month
118
+
119
+ if (current_month > 10) and (collection_date.month < 3):
120
+ collection_date = collection_date.replace(year=(current_year + 1))
121
+ else:
122
+ collection_date = collection_date.replace(year=current_year)
123
+
124
+ dict_data = {
125
+ "type": bin_type,
126
+ "collectionDate": collection_date.strftime("%d/%m/%Y"),
127
+ }
128
+ data["bins"].append(dict_data)
129
+ except ValueError:
130
+ pass # Skip if date parsing fails
103
131
 
104
132
  except Exception as e:
105
- # Here you can log the exception if needed
106
133
  print(f"An error occurred: {e}")
107
- # Optionally, re-raise the exception if you want it to propagate
108
134
  raise
109
135
  finally:
110
- # This block ensures that the driver is closed regardless of an exception
111
136
  if driver:
112
137
  driver.quit()
113
- return data
138
+ return data
@@ -1,83 +1,119 @@
1
1
  from datetime import datetime
2
+ import time
2
3
 
3
- import requests
4
4
  from bs4 import BeautifulSoup
5
+ from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.common.keys import Keys
7
+ from selenium.webdriver.support import expected_conditions as EC
8
+ from selenium.webdriver.support.ui import Select, WebDriverWait
5
9
 
6
10
  from uk_bin_collection.uk_bin_collection.common import *
7
11
  from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
8
12
 
9
13
 
10
14
  class CouncilClass(AbstractGetBinDataClass):
11
- """
12
- Concrete classes have to implement all abstract operations of the
13
- base class. They can also override some operations with a default
14
- implementation.
15
- """
16
-
17
15
  def parse_data(self, page: str, **kwargs) -> dict:
18
16
  user_uprn = kwargs.get("uprn")
19
17
  user_postcode = kwargs.get("postcode")
18
+ web_driver = kwargs.get("web_driver")
19
+ headless = kwargs.get("headless")
20
+
20
21
  check_uprn(user_uprn)
21
22
  check_postcode(user_postcode)
23
+
22
24
  bindata = {"bins": []}
23
-
24
- API_URL = "https://www.broxbourne.gov.uk/xfp/form/205"
25
-
26
- post_data = {
27
- "page": "490",
28
- "locale": "en_GB",
29
- "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_0_0": user_postcode,
30
- "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_1_0": user_uprn,
31
- "next": "Next",
32
- }
33
-
34
- r = requests.post(API_URL, data=post_data)
35
- r.raise_for_status()
36
-
37
- soup = BeautifulSoup(r.content, features="html.parser")
38
- soup.prettify()
39
-
40
- form__instructions = soup.find(attrs={"class": "form__instructions"})
41
- table = form__instructions.find("table")
42
-
43
- rows = table.find_all("tr")
44
-
45
- current_year = datetime.now().year
46
- current_month = datetime.now().month
47
-
48
- # Process each row into a list of dictionaries
49
- for row in rows[1:]: # Skip the header row
50
- columns = row.find_all("td")
51
- collection_date_text = (
52
- columns[0].get_text(separator=" ").replace("\xa0", " ").strip()
25
+ # Use a realistic user agent to help bypass Cloudflare
26
+ user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
27
+ driver = create_webdriver(web_driver, headless, user_agent, __name__)
28
+
29
+ try:
30
+ driver.get("https://www.broxbourne.gov.uk/bin-collection-date")
31
+
32
+ # Wait for Cloudflare challenge to complete
33
+ print("Waiting for page to load (Cloudflare check)...")
34
+ try:
35
+ WebDriverWait(driver, 45).until(
36
+ lambda d: "Just a moment" not in d.title and d.title != "" and len(d.find_elements(By.TAG_NAME, "input")) > 0
37
+ )
38
+ print(f"Page loaded: {driver.title}")
39
+ except:
40
+ print(f"Timeout waiting for page load. Current title: {driver.title}")
41
+ # Try to continue anyway
42
+ pass
43
+
44
+ time.sleep(8)
45
+
46
+ # Handle cookie banner with multiple attempts
47
+
48
+ try:
49
+ cookie_btn = WebDriverWait(driver, 15).until(
50
+ EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Allow all')]"))
51
+ )
52
+ cookie_btn.click()
53
+ except:
54
+ pass
55
+
56
+ # Find postcode input
57
+ postcode_input = WebDriverWait(driver, 20).until(
58
+ EC.element_to_be_clickable((By.XPATH, "//input[@autocomplete='postal-code']"))
53
59
  )
54
- service = columns[1].get_text(separator=" ").replace("\xa0", " ").strip()
55
-
56
- # Safely try to parse collection date
57
- if collection_date_text:
58
- try:
59
- collection_date = datetime.strptime(
60
- collection_date_text, "%a %d %b"
61
- )
62
- if collection_date.month == 1 and current_month != 1:
63
- collection_date = collection_date.replace(year=current_year + 1)
64
- else:
65
- collection_date = collection_date.replace(year=current_year)
66
-
67
- formatted_collection_date = collection_date.strftime(
68
- "%d/%m/%Y"
69
- ) # Use your desired date format
70
- dict_data = {
71
- "type": service,
72
- "collectionDate": formatted_collection_date,
73
- }
74
- bindata["bins"].append(dict_data)
75
- except ValueError:
76
- # Skip invalid collection_date
77
- continue
78
-
79
- # Sort valid bins by collectionDate
80
- bindata["bins"].sort(
81
- key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
82
- )
83
- return bindata
60
+ postcode_input.clear()
61
+ postcode_input.send_keys(user_postcode)
62
+
63
+ # Press Enter to lookup
64
+ postcode_input.send_keys(Keys.RETURN)
65
+
66
+ # Select address
67
+ address_select = WebDriverWait(driver, 15).until(
68
+ EC.presence_of_element_located((By.XPATH, "//select"))
69
+ )
70
+ Select(address_select).select_by_value(user_uprn)
71
+
72
+ # Click Next button
73
+ next_btn = WebDriverWait(driver, 15).until(
74
+ EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Next')]"))
75
+ )
76
+ next_btn.click()
77
+
78
+ # Get results
79
+ WebDriverWait(driver, 15).until(
80
+ EC.presence_of_element_located((By.XPATH, "//h1[contains(text(), 'When is my bin collection date?')]"))
81
+ )
82
+
83
+ table = WebDriverWait(driver, 15).until(
84
+ EC.presence_of_element_located((By.XPATH, "//h1[contains(text(), 'When is my bin collection date?')]/following::table[1]"))
85
+ )
86
+
87
+ soup = BeautifulSoup(table.get_attribute('outerHTML'), 'html.parser')
88
+ rows = soup.find_all('tr')
89
+
90
+ current_year = datetime.now().year
91
+ current_month = datetime.now().month
92
+
93
+ for row in rows[1:]:
94
+ columns = row.find_all('td')
95
+ if len(columns) >= 2:
96
+ collection_date_text = columns[0].get_text().strip()
97
+ service = columns[1].get_text().strip()
98
+
99
+ if collection_date_text:
100
+ try:
101
+ collection_date = datetime.strptime(collection_date_text, "%a %d %b")
102
+ if collection_date.month == 1 and current_month != 1:
103
+ collection_date = collection_date.replace(year=current_year + 1)
104
+ else:
105
+ collection_date = collection_date.replace(year=current_year)
106
+
107
+ bindata["bins"].append({
108
+ "type": service,
109
+ "collectionDate": collection_date.strftime("%d/%m/%Y")
110
+ })
111
+ except ValueError:
112
+ continue
113
+
114
+ bindata["bins"].sort(key=lambda x: datetime.strptime(x["collectionDate"], "%d/%m/%Y"))
115
+
116
+ finally:
117
+ driver.quit()
118
+
119
+ return bindata
@@ -20,6 +20,7 @@ class CouncilClass(AbstractGetBinDataClass):
20
20
  data = {"bins": []}
21
21
  user_paon = kwargs.get("paon")
22
22
  user_postcode = kwargs.get("postcode")
23
+ user_uprn = kwargs.get("uprn")
23
24
  web_driver = kwargs.get("web_driver")
24
25
  headless = kwargs.get("headless")
25
26
  check_paon(user_paon)
@@ -27,9 +28,13 @@ class CouncilClass(AbstractGetBinDataClass):
27
28
 
28
29
  # Create Selenium webdriver
29
30
  driver = create_webdriver(web_driver, headless, None, __name__)
30
- driver.get(
31
- "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en"
31
+ driver.get(kwargs.get("url"))
32
+
33
+ # Click "Check now" button
34
+ check_now_button = WebDriverWait(driver, 10).until(
35
+ EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Check now')]"))
32
36
  )
37
+ check_now_button.click()
33
38
 
34
39
  # Wait for the postcode field to appear then populate it
35
40
  inputElement_postcode = WebDriverWait(driver, 10).until(
@@ -37,71 +42,77 @@ class CouncilClass(AbstractGetBinDataClass):
37
42
  )
38
43
  inputElement_postcode.send_keys(user_postcode)
39
44
 
40
- # Click search button
41
- findAddress = WebDriverWait(driver, 10).until(
42
- EC.presence_of_element_located(
43
- (By.XPATH, '//button[@class="govuk-button mt-4"]')
44
- )
45
+ # Click Find button
46
+ find_button = WebDriverWait(driver, 10).until(
47
+ EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Find')]"))
45
48
  )
46
- findAddress.click()
49
+ find_button.click()
47
50
 
48
- # Wait for the 'Select address' dropdown to appear and select option matching the house name/number
49
- WebDriverWait(driver, 10).until(
50
- EC.element_to_be_clickable(
51
- (
52
- By.XPATH,
53
- "//select[@id='addressSelect']//option[contains(., '"
54
- + user_paon
55
- + "')]",
56
- )
51
+ # Wait for the address dropdown and select by UPRN
52
+ if user_uprn:
53
+ address_option = WebDriverWait(driver, 10).until(
54
+ EC.element_to_be_clickable((By.XPATH, f"//option[@value='{user_uprn}']"))
57
55
  )
58
- ).click()
59
-
60
- # Wait for the collections table to appear
61
- WebDriverWait(driver, 10).until(
62
- EC.presence_of_element_located(
63
- (
64
- By.XPATH,
65
- '//div[@class="ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"]',
56
+ address_option.click()
57
+ else:
58
+ # Fallback to selecting by address text
59
+ address_option = WebDriverWait(driver, 10).until(
60
+ EC.element_to_be_clickable(
61
+ (By.XPATH, f"//select[@id='addressSelect']//option[contains(., '{user_paon}')]")
66
62
  )
67
63
  )
68
- )
69
-
70
- soup = BeautifulSoup(driver.page_source, features="html.parser")
64
+ address_option.click()
71
65
 
72
- recyclingcalendar = soup.find(
73
- "div",
74
- {
75
- "class": "ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"
76
- },
77
- )
66
+ # Wait a moment for the page to update after address selection
67
+ import time
68
+ time.sleep(2)
78
69
 
79
- rows = recyclingcalendar.find_all(
80
- "div",
81
- {
82
- "class": "ant-col ant-col-xs-12 ant-col-sm-12 ant-col-md-12 ant-col-lg-12 ant-col-xl-12 css-2rgkd4"
83
- },
84
- )
70
+ # Wait for collection information to appear - try multiple possible selectors
71
+ try:
72
+ WebDriverWait(driver, 15).until(
73
+ EC.presence_of_element_located((By.XPATH, "//h2[contains(text(), 'Your next collections')]"))
74
+ )
75
+ except:
76
+ # Alternative wait for collection data structure
77
+ WebDriverWait(driver, 10).until(
78
+ EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'ant-row') and contains(@class, 'd-flex')]//h3[@class='text-white']"))
79
+ )
85
80
 
81
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
82
+
83
+ # Find all collection items with the specific structure - try multiple class patterns
84
+ collection_items = soup.find_all("div", class_=lambda x: x and "ant-col" in x and "ant-col-xs-12" in x)
85
+ if not collection_items:
86
+ # Fallback to finding items by structure
87
+ collection_items = soup.find_all("div", class_=lambda x: x and "p-2" in x and "d-flex" in x and "flex-column" in x)
88
+
86
89
  current_year = datetime.now().year
87
90
  current_month = datetime.now().month
88
91
 
89
- for row in rows:
90
- BinType = row.find("h3").text
91
- collectiondate = datetime.strptime(
92
- row.find("div", {"class": "text-white fw-bold"}).text,
93
- "%A %d %B",
94
- )
95
- if (current_month > 10) and (collectiondate.month < 3):
96
- collectiondate = collectiondate.replace(year=(current_year + 1))
97
- else:
98
- collectiondate = collectiondate.replace(year=current_year)
99
-
100
- dict_data = {
101
- "type": BinType,
102
- "collectionDate": collectiondate.strftime("%d/%m/%Y"),
103
- }
104
- data["bins"].append(dict_data)
92
+ for item in collection_items:
93
+ # Extract bin type from h3 element
94
+ bin_type_elem = item.find("h3", class_="text-white")
95
+ # Extract date from div with specific classes
96
+ date_elem = item.find("div", class_="text-white fw-bold")
97
+
98
+ if bin_type_elem and date_elem:
99
+ bin_type = bin_type_elem.get_text().strip()
100
+ date_text = date_elem.get_text().strip()
101
+
102
+ try:
103
+ collection_date = datetime.strptime(date_text, "%A %d %B")
104
+ if (current_month > 10) and (collection_date.month < 3):
105
+ collection_date = collection_date.replace(year=(current_year + 1))
106
+ else:
107
+ collection_date = collection_date.replace(year=current_year)
108
+
109
+ dict_data = {
110
+ "type": bin_type,
111
+ "collectionDate": collection_date.strftime("%d/%m/%Y"),
112
+ }
113
+ data["bins"].append(dict_data)
114
+ except ValueError:
115
+ continue
105
116
 
106
117
  except Exception as e:
107
118
  # Here you can log the exception if needed