uk_bin_collection 0.153.0__py3-none-any.whl → 0.157.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 +34 -25
- uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +0 -1
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +45 -120
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +4 -1
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +15 -36
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +55 -24
- uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py +22 -13
- uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +32 -34
- uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py +5 -2
- uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +22 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +1 -1
- uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +3 -1
- uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py +8 -5
- uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +23 -10
- uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +70 -92
- uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py +104 -47
- uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +138 -21
- uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +26 -128
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +245 -82
- uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py +1 -0
- uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +170 -13
- uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py +70 -38
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +4 -2
- uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +4 -11
- uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py +39 -21
- uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +136 -21
- uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +18 -22
- uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +138 -21
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +16 -13
- {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/RECORD +35 -34
- {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.153.0.dist-info → uk_bin_collection-0.157.0.dist-info}/entry_points.txt +0 -0
@@ -1,57 +1,89 @@
|
|
1
|
-
from bs4 import BeautifulSoup
|
2
1
|
from uk_bin_collection.uk_bin_collection.common import *
|
3
2
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
3
|
+
import requests
|
4
|
+
from datetime import datetime
|
4
5
|
|
5
6
|
|
6
|
-
# import the wonderful Beautiful Soup and the URL grabber
|
7
7
|
class CouncilClass(AbstractGetBinDataClass):
|
8
8
|
"""
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Rotherham collections via the public JSON API.
|
10
|
+
Returns the same shape as before:
|
11
|
+
{"bins": [{"type": "Black Bin", "collectionDate": "Tuesday, 29 September 2025"}, ...]}
|
12
|
+
Accepts kwargs['premisesid'] (recommended) or a numeric kwargs['uprn'].
|
12
13
|
"""
|
13
14
|
|
14
15
|
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
-
|
16
|
+
# prefer explicit premisesid, fallback to uprn (if numeric)
|
17
|
+
premises = kwargs.get("premisesid")
|
18
|
+
uprn = kwargs.get("uprn")
|
16
19
|
|
17
|
-
|
20
|
+
if uprn:
|
21
|
+
# preserve original behaviour where check_uprn exists for validation,
|
22
|
+
# but don't fail if uprn is intended as a simple premises id number.
|
23
|
+
try:
|
24
|
+
check_uprn(uprn)
|
25
|
+
except Exception:
|
26
|
+
# silently continue — user may have passed a numeric premises id as uprn
|
27
|
+
pass
|
28
|
+
|
29
|
+
if not premises and str(uprn).strip().isdigit():
|
30
|
+
premises = str(uprn).strip()
|
31
|
+
|
32
|
+
if not premises:
|
33
|
+
raise ValueError("No premises ID supplied. Pass 'premisesid' in kwargs or a numeric 'uprn'.")
|
34
|
+
|
35
|
+
api_url = "https://bins.azurewebsites.net/api/getcollections"
|
36
|
+
params = {
|
37
|
+
"premisesid": str(premises),
|
38
|
+
"localauthority": kwargs.get("localauthority", "Rotherham"),
|
39
|
+
}
|
18
40
|
headers = {
|
19
|
-
"
|
20
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
41
|
+
"User-Agent": "UKBinCollectionData/1.0 (+https://github.com/robbrad/UKBinCollectionData)"
|
21
42
|
}
|
22
|
-
response = requests.post(
|
23
|
-
"https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format(
|
24
|
-
user_uprn
|
25
|
-
),
|
26
|
-
headers=headers
|
27
|
-
)
|
28
|
-
# Make a BS4 object
|
29
|
-
soup = BeautifulSoup(response.text, features="html.parser")
|
30
|
-
soup.prettify()
|
31
43
|
|
32
|
-
|
44
|
+
try:
|
45
|
+
resp = requests.get(api_url, params=params, headers=headers, timeout=10)
|
46
|
+
except Exception as exc:
|
47
|
+
print(f"Error contacting Rotherham API: {exc}")
|
48
|
+
return {"bins": []}
|
49
|
+
|
50
|
+
if resp.status_code != 200:
|
51
|
+
print(f"Rotherham API request failed ({resp.status_code}). URL: {resp.url}")
|
52
|
+
return {"bins": []}
|
33
53
|
|
34
|
-
|
54
|
+
try:
|
55
|
+
collections = resp.json()
|
56
|
+
except ValueError:
|
57
|
+
print("Rotherham API returned non-JSON response.")
|
58
|
+
return {"bins": []}
|
59
|
+
|
60
|
+
data = {"bins": []}
|
61
|
+
seen = set() # dedupe identical (type, date) pairs
|
62
|
+
for item in collections:
|
63
|
+
bin_type = item.get("BinType") or item.get("bintype") or "Unknown"
|
64
|
+
date_str = item.get("CollectionDate") or item.get("collectionDate")
|
65
|
+
if not date_str:
|
66
|
+
continue
|
35
67
|
|
36
|
-
|
37
|
-
|
68
|
+
# API gives ISO date like '2025-09-29' (or possibly '2025-09-29T00:00:00').
|
69
|
+
try:
|
70
|
+
iso_date = date_str.split("T")[0]
|
71
|
+
parsed = datetime.strptime(iso_date, "%Y-%m-%d")
|
72
|
+
formatted = parsed.strftime(date_format)
|
73
|
+
except Exception:
|
74
|
+
# skip malformed dates
|
75
|
+
continue
|
38
76
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
bin_collection = bin_info_cell[1]
|
77
|
+
key = (bin_type.strip().lower(), formatted)
|
78
|
+
if key in seen:
|
79
|
+
continue
|
80
|
+
seen.add(key)
|
44
81
|
|
45
|
-
|
46
|
-
|
47
|
-
"type": bin_type.title(),
|
48
|
-
"collectionDate": datetime.strptime(
|
49
|
-
bin_collection.get_text(strip=True), "%A, %d %B %Y"
|
50
|
-
).strftime(date_format),
|
51
|
-
}
|
82
|
+
dict_data = {"type": bin_type.title(), "collectionDate": formatted}
|
83
|
+
data["bins"].append(dict_data)
|
52
84
|
|
53
|
-
|
54
|
-
|
55
|
-
print("
|
85
|
+
if not data["bins"]:
|
86
|
+
# helpful debugging note
|
87
|
+
print(f"Rotherham API returned no collection entries for premisesid={premises}")
|
56
88
|
|
57
|
-
return data
|
89
|
+
return data
|
@@ -1,7 +1,8 @@
|
|
1
1
|
from bs4 import BeautifulSoup
|
2
|
+
from lxml import etree
|
3
|
+
|
2
4
|
from uk_bin_collection.uk_bin_collection.common import *
|
3
5
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
-
from lxml import etree
|
5
6
|
|
6
7
|
|
7
8
|
# import the wonderful Beautiful Soup and the URL grabber
|
@@ -20,7 +21,8 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
20
21
|
collections = []
|
21
22
|
|
22
23
|
# Convert the XML to JSON and load the next collection data
|
23
|
-
result = soup.find("p").contents[0]
|
24
|
+
result = soup.find("p").contents[0]
|
25
|
+
|
24
26
|
json_data = json.loads(result)["NextCollection"]
|
25
27
|
|
26
28
|
# Get general waste data
|
@@ -28,17 +28,10 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
28
28
|
"Referer": "https://my.sandwell.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
29
29
|
}
|
30
30
|
LOOKUPS = [
|
31
|
-
(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
"Recycling (Blue)",
|
36
|
-
"Household Waste (Grey)",
|
37
|
-
"Food Waste (Brown)",
|
38
|
-
"Batteries",
|
39
|
-
],
|
40
|
-
),
|
41
|
-
("56b1cdaf6bb43", "GWDate", ["Garden Waste (Green)"]),
|
31
|
+
("686295a88a750", "GWDate", ["Garden Waste (Green)"]),
|
32
|
+
("686294de50729", "DWDate", ["Household Waste (Grey)"]),
|
33
|
+
("6863a78a1dd8e", "FWDate", ["Food Waste (Brown)"]),
|
34
|
+
("68629dd642423", "MDRDate", ["Recycling (Blue)"]),
|
42
35
|
]
|
43
36
|
|
44
37
|
def parse_data(self, page: str, **kwargs) -> dict:
|
@@ -1,15 +1,18 @@
|
|
1
|
-
import time
|
2
1
|
import re
|
3
|
-
import
|
2
|
+
import time
|
4
3
|
from datetime import datetime
|
4
|
+
|
5
|
+
import requests
|
5
6
|
from bs4 import BeautifulSoup
|
6
7
|
from selenium.webdriver.common.by import By
|
7
8
|
from selenium.webdriver.common.keys import Keys
|
8
9
|
from selenium.webdriver.support import expected_conditions as EC
|
9
10
|
from selenium.webdriver.support.ui import WebDriverWait
|
11
|
+
|
10
12
|
from uk_bin_collection.uk_bin_collection.common import *
|
11
13
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
14
|
|
15
|
+
|
13
16
|
def get_street_from_postcode(postcode: str, api_key: str) -> str:
|
14
17
|
url = "https://maps.googleapis.com/maps/api/geocode/json"
|
15
18
|
params = {"address": postcode, "key": api_key}
|
@@ -25,6 +28,7 @@ def get_street_from_postcode(postcode: str, api_key: str) -> str:
|
|
25
28
|
|
26
29
|
raise ValueError("No street (route) found in the response.")
|
27
30
|
|
31
|
+
|
28
32
|
class CouncilClass(AbstractGetBinDataClass):
|
29
33
|
def parse_data(self, page: str, **kwargs) -> dict:
|
30
34
|
driver = None
|
@@ -37,10 +41,10 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
37
41
|
|
38
42
|
headless = kwargs.get("headless")
|
39
43
|
web_driver = kwargs.get("web_driver")
|
40
|
-
|
44
|
+
UserAgent = "Mozilla/5.0"
|
45
|
+
driver = create_webdriver(web_driver, headless, UserAgent, __name__)
|
41
46
|
page = "https://www.slough.gov.uk/bin-collections"
|
42
47
|
driver.get(page)
|
43
|
-
|
44
48
|
# Accept cookies
|
45
49
|
WebDriverWait(driver, 10).until(
|
46
50
|
EC.element_to_be_clickable((By.ID, "ccc-recommended-settings"))
|
@@ -50,14 +54,20 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
50
54
|
address_input = WebDriverWait(driver, 10).until(
|
51
55
|
EC.presence_of_element_located((By.ID, "keyword_directory25"))
|
52
56
|
)
|
53
|
-
user_address = get_street_from_postcode(
|
57
|
+
user_address = get_street_from_postcode(
|
58
|
+
user_postcode, "AIzaSyBDLULT7EIlNtHerswPtfmL15Tt3Oc0bV8"
|
59
|
+
)
|
54
60
|
address_input.send_keys(user_address + Keys.ENTER)
|
55
61
|
|
56
62
|
# Wait for address results to load
|
57
63
|
WebDriverWait(driver, 10).until(
|
58
|
-
EC.presence_of_all_elements_located(
|
64
|
+
EC.presence_of_all_elements_located(
|
65
|
+
(By.CSS_SELECTOR, "span.list__link-text")
|
66
|
+
)
|
67
|
+
)
|
68
|
+
span_elements = driver.find_elements(
|
69
|
+
By.CSS_SELECTOR, "span.list__link-text"
|
59
70
|
)
|
60
|
-
span_elements = driver.find_elements(By.CSS_SELECTOR, "span.list__link-text")
|
61
71
|
|
62
72
|
for span in span_elements:
|
63
73
|
if user_address.lower() in span.text.lower():
|
@@ -68,7 +78,9 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
68
78
|
|
69
79
|
# Wait for address detail page
|
70
80
|
WebDriverWait(driver, 10).until(
|
71
|
-
EC.presence_of_element_located(
|
81
|
+
EC.presence_of_element_located(
|
82
|
+
(By.CSS_SELECTOR, "section.site-content")
|
83
|
+
)
|
72
84
|
)
|
73
85
|
soup = BeautifulSoup(driver.page_source, "html.parser")
|
74
86
|
|
@@ -86,28 +98,33 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
86
98
|
bin_url = "https://www.slough.gov.uk" + bin_url
|
87
99
|
|
88
100
|
# Visit the child page
|
89
|
-
print(f"Navigating to {bin_url}")
|
101
|
+
# print(f"Navigating to {bin_url}")
|
90
102
|
driver.get(bin_url)
|
91
103
|
WebDriverWait(driver, 10).until(
|
92
|
-
EC.presence_of_element_located(
|
104
|
+
EC.presence_of_element_located(
|
105
|
+
(By.CSS_SELECTOR, "div.page-content")
|
106
|
+
)
|
93
107
|
)
|
94
108
|
child_soup = BeautifulSoup(driver.page_source, "html.parser")
|
95
109
|
|
96
110
|
editor_div = child_soup.find("div", class_="editor")
|
97
111
|
if not editor_div:
|
98
|
-
print("No editor div found on bin detail page.")
|
112
|
+
# print("No editor div found on bin detail page.")
|
99
113
|
continue
|
100
114
|
|
101
115
|
ul = editor_div.find("ul")
|
102
116
|
if not ul:
|
103
|
-
print("No <ul> with dates found in editor div.")
|
117
|
+
# print("No <ul> with dates found in editor div.")
|
104
118
|
continue
|
105
119
|
|
106
120
|
for li in ul.find_all("li"):
|
107
121
|
raw_text = li.get_text(strip=True).replace(".", "")
|
108
122
|
|
109
|
-
if
|
110
|
-
|
123
|
+
if (
|
124
|
+
"no collection" in raw_text.lower()
|
125
|
+
or "no collections" in raw_text.lower()
|
126
|
+
):
|
127
|
+
# print(f"Ignoring non-collection note: {raw_text}")
|
111
128
|
continue
|
112
129
|
|
113
130
|
raw_date = raw_text
|
@@ -117,19 +134,20 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
117
134
|
except ValueError:
|
118
135
|
raw_date_cleaned = raw_date.split("(")[0].strip()
|
119
136
|
try:
|
120
|
-
parsed_date = datetime.strptime(
|
137
|
+
parsed_date = datetime.strptime(
|
138
|
+
raw_date_cleaned, "%d %B %Y"
|
139
|
+
)
|
121
140
|
except Exception:
|
122
141
|
print(f"Could not parse date: {raw_text}")
|
123
142
|
continue
|
124
143
|
|
125
144
|
formatted_date = parsed_date.strftime("%d/%m/%Y")
|
126
145
|
contains_date(formatted_date)
|
127
|
-
bin_data["bins"].append(
|
128
|
-
"type": bin_type,
|
129
|
-
|
130
|
-
})
|
146
|
+
bin_data["bins"].append(
|
147
|
+
{"type": bin_type, "collectionDate": formatted_date}
|
148
|
+
)
|
131
149
|
|
132
|
-
print(f"Type: {bin_type}, Date: {formatted_date}")
|
150
|
+
# print(f"Type: {bin_type}, Date: {formatted_date}")
|
133
151
|
|
134
152
|
except Exception as e:
|
135
153
|
print(f"An error occurred: {e}")
|
@@ -137,4 +155,4 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
137
155
|
finally:
|
138
156
|
if driver:
|
139
157
|
driver.quit()
|
140
|
-
return bin_data
|
158
|
+
return bin_data
|
@@ -1,4 +1,9 @@
|
|
1
|
+
import datetime
|
2
|
+
|
1
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.wait import WebDriverWait
|
2
7
|
|
3
8
|
from uk_bin_collection.uk_bin_collection.common import *
|
4
9
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
@@ -13,6 +18,7 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
13
18
|
"""
|
14
19
|
|
15
20
|
def parse_data(self, page: str, **kwargs) -> dict:
|
21
|
+
<<<<<<< HEAD
|
16
22
|
user_postcode = kwargs.get("postcode")
|
17
23
|
check_postcode(user_postcode)
|
18
24
|
user_uprn = kwargs.get("uprn")
|
@@ -43,10 +49,16 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
43
49
|
i["data-for"]: i.get("value", "")
|
44
50
|
for i in soup.select("input[data-for]")
|
45
51
|
}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
)
|
52
|
+
|
53
|
+
# Check if required form elements exist
|
54
|
+
salt_element = soup.select_one('input[id="pSalt"]')
|
55
|
+
protected_element = soup.select_one('input[id="pPageItemsProtected"]')
|
56
|
+
|
57
|
+
if not salt_element or not protected_element:
|
58
|
+
raise Exception("Required form elements not found. The council website may have changed or be unavailable.")
|
59
|
+
|
60
|
+
payload_salt = salt_element.get("value")
|
61
|
+
payload_protected = protected_element.get("value")
|
50
62
|
|
51
63
|
# Add the PostCode and 'SEARCH' to the payload
|
52
64
|
payload["p_request"] = "SEARCH"
|
@@ -123,10 +135,16 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
123
135
|
i["data-for"]: i.get("value", "")
|
124
136
|
for i in soup.select("input[data-for]")
|
125
137
|
}
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
)
|
138
|
+
|
139
|
+
# Check if required form elements exist
|
140
|
+
salt_element = soup.select_one('input[id="pSalt"]')
|
141
|
+
protected_element = soup.select_one('input[id="pPageItemsProtected"]')
|
142
|
+
|
143
|
+
if not salt_element or not protected_element:
|
144
|
+
raise Exception("Required form elements not found. The council website may have changed or be unavailable.")
|
145
|
+
|
146
|
+
payload_salt = salt_element.get("value")
|
147
|
+
payload_protected = protected_element.get("value")
|
130
148
|
|
131
149
|
# Add the UPRN and 'SUBMIT' to the payload
|
132
150
|
payload["p_request"] = "SUBMIT"
|
@@ -187,18 +205,115 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
187
205
|
|
188
206
|
# Create a BeautifulSoup object from the page's HTML
|
189
207
|
soup = BeautifulSoup(resource.text, "html.parser")
|
208
|
+
=======
|
209
|
+
driver = None
|
210
|
+
try:
|
211
|
+
>>>>>>> master
|
190
212
|
data = {"bins": []}
|
213
|
+
url = kwargs.get("url")
|
214
|
+
user_paon = kwargs.get("paon")
|
215
|
+
user_postcode = kwargs.get("postcode")
|
216
|
+
web_driver = kwargs.get("web_driver")
|
217
|
+
headless = kwargs.get("headless")
|
218
|
+
check_paon(user_paon)
|
219
|
+
check_postcode(user_postcode)
|
220
|
+
|
221
|
+
# Use a realistic user agent to help bypass Cloudflare
|
222
|
+
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"
|
223
|
+
driver = create_webdriver(web_driver, headless, user_agent, __name__)
|
224
|
+
driver.get("https://www.somerset.gov.uk/collection-days")
|
225
|
+
|
226
|
+
# Wait for the postcode field to appear then populate it
|
227
|
+
inputElement_postcode = WebDriverWait(driver, 30).until(
|
228
|
+
EC.presence_of_element_located((By.ID, "postcodeSearch"))
|
229
|
+
)
|
230
|
+
inputElement_postcode.send_keys(user_postcode)
|
231
|
+
|
232
|
+
# Click search button
|
233
|
+
findAddress = WebDriverWait(driver, 10).until(
|
234
|
+
EC.presence_of_element_located((By.CLASS_NAME, "govuk-button"))
|
235
|
+
)
|
236
|
+
findAddress.click()
|
237
|
+
|
238
|
+
# Wait for the 'Select address' dropdown to appear and select option matching the house name/number
|
239
|
+
WebDriverWait(driver, 10).until(
|
240
|
+
EC.element_to_be_clickable(
|
241
|
+
(
|
242
|
+
By.XPATH,
|
243
|
+
"//select[@id='addressSelect']//option[contains(., '"
|
244
|
+
+ user_paon
|
245
|
+
+ "')]",
|
246
|
+
)
|
247
|
+
)
|
248
|
+
).click()
|
249
|
+
|
250
|
+
# Wait for the collections table to appear
|
251
|
+
WebDriverWait(driver, 20).until(
|
252
|
+
EC.presence_of_element_located(
|
253
|
+
(
|
254
|
+
By.XPATH,
|
255
|
+
"//h2[contains(@class,'mt-4') and contains(@class,'govuk-heading-s') and normalize-space(.)='Your next collections']",
|
256
|
+
)
|
257
|
+
)
|
258
|
+
)
|
259
|
+
|
260
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
261
|
+
|
262
|
+
collections = soup.find_all("div", {"class": "p-2"})
|
263
|
+
|
264
|
+
for collection in collections:
|
265
|
+
bin_type = collection.find("h3").get_text()
|
266
|
+
|
267
|
+
next_collection = soup.find("div", {"class": "fw-bold"}).get_text()
|
268
|
+
|
269
|
+
following_collection = soup.find(
|
270
|
+
lambda t: (
|
271
|
+
t.name == "div"
|
272
|
+
and t.get_text(strip=True).lower().startswith("followed by")
|
273
|
+
)
|
274
|
+
).get_text()
|
275
|
+
|
276
|
+
next_collection_date = datetime.strptime(next_collection, "%A %d %B")
|
277
|
+
|
278
|
+
following_collection_date = datetime.strptime(
|
279
|
+
following_collection, "followed by %A %d %B"
|
280
|
+
)
|
281
|
+
|
282
|
+
current_date = datetime.now()
|
283
|
+
next_collection_date = next_collection_date.replace(
|
284
|
+
year=current_date.year
|
285
|
+
)
|
286
|
+
following_collection_date = following_collection_date.replace(
|
287
|
+
year=current_date.year
|
288
|
+
)
|
289
|
+
|
290
|
+
next_collection_date = get_next_occurrence_from_day_month(
|
291
|
+
next_collection_date
|
292
|
+
)
|
293
|
+
|
294
|
+
following_collection_date = get_next_occurrence_from_day_month(
|
295
|
+
following_collection_date
|
296
|
+
)
|
297
|
+
|
298
|
+
dict_data = {
|
299
|
+
"type": bin_type,
|
300
|
+
"collectionDate": next_collection_date.strftime(date_format),
|
301
|
+
}
|
302
|
+
data["bins"].append(dict_data)
|
303
|
+
|
304
|
+
dict_data = {
|
305
|
+
"type": bin_type,
|
306
|
+
"collectionDate": following_collection_date.strftime(date_format),
|
307
|
+
}
|
308
|
+
data["bins"].append(dict_data)
|
191
309
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
data["bins"].append(dict_data)
|
203
|
-
|
204
|
-
return data
|
310
|
+
except Exception as e:
|
311
|
+
# Here you can log the exception if needed
|
312
|
+
print(f"An error occurred: {e}")
|
313
|
+
# Optionally, re-raise the exception if you want it to propagate
|
314
|
+
raise
|
315
|
+
finally:
|
316
|
+
# This block ensures that the driver is closed regardless of an exception
|
317
|
+
if driver:
|
318
|
+
driver.quit()
|
319
|
+
return data
|
@@ -6,17 +6,16 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataC
|
|
6
6
|
|
7
7
|
def format_bin_data(key: str, date: datetime):
|
8
8
|
formatted_date = date.strftime(date_format)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
return [
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
elif re.match(r"^G\d+$", key) is not None:
|
9
|
+
servicename = key.get("hso_servicename")
|
10
|
+
print(servicename)
|
11
|
+
if re.match(r"^Recycl", servicename) is not None:
|
12
|
+
return [ ("Recycling", formatted_date) ]
|
13
|
+
elif re.match(r"^Refuse", servicename) is not None:
|
14
|
+
return [("General Waste (Black Bin)", formatted_date)]
|
15
|
+
elif re.match(r"^Garden", servicename) is not None:
|
17
16
|
return [("Garden Waste (Green Bin)", formatted_date)]
|
18
|
-
elif re.match(r"^
|
19
|
-
return [("
|
17
|
+
elif re.match(r"^Food", servicename) is not None:
|
18
|
+
return [("Food Waste", formatted_date)]
|
20
19
|
else:
|
21
20
|
return None
|
22
21
|
|
@@ -27,37 +26,34 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
27
26
|
check_uprn(uprn)
|
28
27
|
|
29
28
|
api_url = (
|
30
|
-
f"https://
|
31
|
-
f"
|
29
|
+
f"https://api.southglos.gov.uk/wastecomp/GetCollectionDetails"
|
30
|
+
f"?uprn={uprn}"
|
32
31
|
)
|
33
32
|
|
34
33
|
headers = {"content-type": "application/json"}
|
35
34
|
|
36
35
|
response = requests.get(api_url, headers=headers)
|
37
36
|
|
38
|
-
json_response = json
|
37
|
+
json_response = response.json()
|
39
38
|
if not json_response:
|
40
39
|
raise ValueError("No collection data found for provided UPRN.")
|
41
40
|
|
42
|
-
collection_data = json_response
|
41
|
+
collection_data = json_response.get('value')
|
43
42
|
|
44
43
|
today = datetime.today()
|
45
44
|
eight_weeks = datetime.today() + timedelta(days=8 * 7)
|
46
45
|
data = {"bins": []}
|
47
46
|
collection_tuple = []
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
continue
|
52
|
-
|
53
|
-
item = collection_data[key]
|
47
|
+
for collection in collection_data:
|
48
|
+
print(collection)
|
49
|
+
item = collection.get('hso_nextcollection')
|
54
50
|
|
55
51
|
if item == "":
|
56
52
|
continue
|
57
53
|
|
58
|
-
collection_date = datetime.
|
54
|
+
collection_date = datetime.fromisoformat(item)
|
59
55
|
if today.date() <= collection_date.date() <= eight_weeks.date():
|
60
|
-
bin_data = format_bin_data(
|
56
|
+
bin_data = format_bin_data(collection, collection_date)
|
61
57
|
if bin_data is not None:
|
62
58
|
for bin_date in bin_data:
|
63
59
|
collection_tuple.append(bin_date)
|