uk_bin_collection 0.138.1__py3-none-any.whl → 0.140.0__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.
- uk_bin_collection/tests/input.json +63 -26
- uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +2 -1
- uk_bin_collection/uk_bin_collection/councils/AberdeenshireCouncil.py +1 -0
- uk_bin_collection/uk_bin_collection/councils/ArdsAndNorthDownCouncil.py +1 -0
- uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +1 -0
- uk_bin_collection/uk_bin_collection/councils/BroadlandDistrictCouncil.py +185 -0
- uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py +7 -3
- uk_bin_collection/uk_bin_collection/councils/CeredigionCountyCouncil.py +157 -0
- uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py +95 -61
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +1 -0
- uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py +4 -1
- uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +52 -41
- uk_bin_collection/uk_bin_collection/councils/GooglePublicCalendarCouncil.py +3 -4
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughOfRichmondUponThames.py +11 -9
- uk_bin_collection/uk_bin_collection/councils/MiddlesbroughCouncil.py +13 -4
- uk_bin_collection/uk_bin_collection/councils/MonmouthshireCountyCouncil.py +5 -1
- uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py +1 -3
- uk_bin_collection/uk_bin_collection/councils/NorthDevonCountyCouncil.py +159 -0
- uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py +15 -3
- uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py +873 -871
- uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +3 -6
- uk_bin_collection/uk_bin_collection/councils/SouthHollandDistrictCouncil.py +136 -0
- uk_bin_collection/uk_bin_collection/councils/WalsallCouncil.py +6 -2
- uk_bin_collection/uk_bin_collection/councils/WalthamForest.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +6 -3
- uk_bin_collection/uk_bin_collection/councils/WychavonDistrictCouncil.py +1 -0
- {uk_bin_collection-0.138.1.dist-info → uk_bin_collection-0.140.0.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.138.1.dist-info → uk_bin_collection-0.140.0.dist-info}/RECORD +32 -28
- {uk_bin_collection-0.138.1.dist-info → uk_bin_collection-0.140.0.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.138.1.dist-info → uk_bin_collection-0.140.0.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.138.1.dist-info → uk_bin_collection-0.140.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
from time import sleep
|
2
|
+
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from selenium.webdriver.common.by import By
|
5
|
+
from selenium.webdriver.support import expected_conditions as EC
|
6
|
+
from selenium.webdriver.support.ui import Select, WebDriverWait
|
7
|
+
|
8
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
9
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
10
|
+
|
11
|
+
|
12
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
13
|
+
class CouncilClass(AbstractGetBinDataClass):
|
14
|
+
"""
|
15
|
+
Concrete classes have to implement all abstract operations of the base
|
16
|
+
class. They can also override some operations with a default
|
17
|
+
implementation.
|
18
|
+
"""
|
19
|
+
|
20
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
21
|
+
driver = None
|
22
|
+
try:
|
23
|
+
user_uprn = kwargs.get("uprn")
|
24
|
+
user_postcode = kwargs.get("postcode")
|
25
|
+
web_driver = kwargs.get("web_driver")
|
26
|
+
headless = kwargs.get("headless")
|
27
|
+
check_uprn(user_uprn)
|
28
|
+
check_postcode(user_postcode)
|
29
|
+
|
30
|
+
# Create Selenium webdriver
|
31
|
+
driver = create_webdriver(web_driver, headless, None, __name__)
|
32
|
+
driver.get(
|
33
|
+
"https://my.northdevon.gov.uk/service/WasteRecyclingCollectionCalendar"
|
34
|
+
)
|
35
|
+
|
36
|
+
# Wait for iframe to load and switch to it
|
37
|
+
WebDriverWait(driver, 30).until(
|
38
|
+
EC.frame_to_be_available_and_switch_to_it((By.ID, "fillform-frame-1"))
|
39
|
+
)
|
40
|
+
|
41
|
+
# Wait for postcode entry box
|
42
|
+
postcode = WebDriverWait(driver, 10).until(
|
43
|
+
EC.presence_of_element_located((By.ID, "postcode_search"))
|
44
|
+
)
|
45
|
+
# Enter postcode
|
46
|
+
postcode.send_keys(user_postcode.replace(" ", ""))
|
47
|
+
|
48
|
+
# Wait for address selection dropdown to appear
|
49
|
+
address = Select(
|
50
|
+
WebDriverWait(driver, 10).until(
|
51
|
+
EC.visibility_of_element_located((By.ID, "chooseAddress"))
|
52
|
+
)
|
53
|
+
)
|
54
|
+
|
55
|
+
# Wait for spinner to disappear (signifies options are loaded for select)
|
56
|
+
WebDriverWait(driver, 10).until(
|
57
|
+
EC.invisibility_of_element_located(
|
58
|
+
(By.CLASS_NAME, "spinner-outer")
|
59
|
+
) # row-fluid spinner-outer
|
60
|
+
)
|
61
|
+
|
62
|
+
# Sometimes the options aren't fully there despite the spinner being gone, wait another 2 seconds.
|
63
|
+
sleep(2)
|
64
|
+
|
65
|
+
# Select address by UPRN
|
66
|
+
address.select_by_value(user_uprn)
|
67
|
+
|
68
|
+
# Wait for spinner to disappear (signifies data is loaded)
|
69
|
+
WebDriverWait(driver, 10).until(
|
70
|
+
EC.invisibility_of_element_located((By.CLASS_NAME, "spinner-outer"))
|
71
|
+
)
|
72
|
+
|
73
|
+
sleep(2)
|
74
|
+
|
75
|
+
address_confirmation = WebDriverWait(driver, 10).until(
|
76
|
+
EC.presence_of_element_located(
|
77
|
+
(By.XPATH, "//h2[contains(text(), 'Your address')]")
|
78
|
+
)
|
79
|
+
)
|
80
|
+
|
81
|
+
next_button = WebDriverWait(driver, 10).until(
|
82
|
+
EC.presence_of_element_located(
|
83
|
+
(By.XPATH, "//button/span[contains(@class, 'nextText')]")
|
84
|
+
)
|
85
|
+
)
|
86
|
+
|
87
|
+
next_button.click()
|
88
|
+
|
89
|
+
results = WebDriverWait(driver, 10).until(
|
90
|
+
EC.presence_of_element_located(
|
91
|
+
(By.XPATH, "//h4[contains(text(), 'Key')]")
|
92
|
+
)
|
93
|
+
)
|
94
|
+
|
95
|
+
# Find data table
|
96
|
+
data_table = WebDriverWait(driver, 10).until(
|
97
|
+
EC.presence_of_element_located(
|
98
|
+
(
|
99
|
+
By.XPATH,
|
100
|
+
'//div[@data-field-name="html1"]/div[contains(@class, "fieldContent")]',
|
101
|
+
)
|
102
|
+
)
|
103
|
+
)
|
104
|
+
|
105
|
+
# Make a BS4 object
|
106
|
+
soup = BeautifulSoup(
|
107
|
+
data_table.get_attribute("innerHTML"), features="html.parser"
|
108
|
+
)
|
109
|
+
|
110
|
+
# Initialize the data dictionary
|
111
|
+
data = {"bins": []}
|
112
|
+
|
113
|
+
# Loop through each list of waste dates
|
114
|
+
waste_sections = soup.find_all("ul", class_="wasteDates")
|
115
|
+
|
116
|
+
current_month_year = None
|
117
|
+
|
118
|
+
for section in waste_sections:
|
119
|
+
for li in section.find_all("li", recursive=False):
|
120
|
+
if "MonthLabel" in li.get("class", []):
|
121
|
+
# Extract month and year (e.g., "April 2025")
|
122
|
+
header = li.find("h4")
|
123
|
+
if header:
|
124
|
+
current_month_year = header.text.strip()
|
125
|
+
elif any(
|
126
|
+
bin_class in li.get("class", [])
|
127
|
+
for bin_class in ["BlackBin", "GreenBin", "Recycling"]
|
128
|
+
):
|
129
|
+
bin_type = li.find("span", class_="wasteType").text.strip()
|
130
|
+
day = li.find("span", class_="wasteDay").text.strip()
|
131
|
+
weekday = li.find("span", class_="wasteName").text.strip()
|
132
|
+
|
133
|
+
if current_month_year and day:
|
134
|
+
try:
|
135
|
+
full_date = f"{day} {current_month_year}"
|
136
|
+
collection_date = datetime.strptime(
|
137
|
+
full_date, "%d %B %Y"
|
138
|
+
).strftime(date_format)
|
139
|
+
dict_data = {
|
140
|
+
"type": bin_type,
|
141
|
+
"collectionDate": collection_date,
|
142
|
+
}
|
143
|
+
data["bins"].append(dict_data)
|
144
|
+
except Exception as e:
|
145
|
+
print(f"Skipping invalid date '{full_date}': {e}")
|
146
|
+
|
147
|
+
data["bins"].sort(
|
148
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
149
|
+
)
|
150
|
+
except Exception as e:
|
151
|
+
# Here you can log the exception if needed
|
152
|
+
print(f"An error occurred: {e}")
|
153
|
+
# Optionally, re-raise the exception if you want it to propagate
|
154
|
+
raise
|
155
|
+
finally:
|
156
|
+
# This block ensures that the driver is closed regardless of an exception
|
157
|
+
if driver:
|
158
|
+
driver.quit()
|
159
|
+
return data
|
@@ -53,7 +53,12 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
53
53
|
if alternateCheck:
|
54
54
|
bin_types = strong[2].text.strip().replace(".", "").split(" and ")
|
55
55
|
for bin in bin_types:
|
56
|
-
collections.append(
|
56
|
+
collections.append(
|
57
|
+
(
|
58
|
+
bin.capitalize(),
|
59
|
+
datetime.strptime(strong[1].text.strip(), date_format),
|
60
|
+
)
|
61
|
+
)
|
57
62
|
|
58
63
|
else:
|
59
64
|
p_tag = soup.find_all("p")
|
@@ -63,11 +68,18 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
63
68
|
p.text.split("Your ")[1].split(" is collected")[0].split(" and ")
|
64
69
|
)
|
65
70
|
for bin in bin_types:
|
66
|
-
collections.append(
|
71
|
+
collections.append(
|
72
|
+
(
|
73
|
+
bin.capitalize(),
|
74
|
+
datetime.strptime(strong[1].text.strip(), date_format),
|
75
|
+
)
|
76
|
+
)
|
67
77
|
i += 2
|
68
78
|
|
69
79
|
if len(strong) > 3:
|
70
|
-
collections.append(
|
80
|
+
collections.append(
|
81
|
+
("Garden", datetime.strptime(strong[4].text.strip(), date_format))
|
82
|
+
)
|
71
83
|
|
72
84
|
ordered_data = sorted(collections, key=lambda x: x[1])
|
73
85
|
for item in ordered_data:
|