uk_bin_collection 0.84.2__py3-none-any.whl → 0.85.1__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 +15 -0
- uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py +156 -0
- {uk_bin_collection-0.84.2.dist-info → uk_bin_collection-0.85.1.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.84.2.dist-info → uk_bin_collection-0.85.1.dist-info}/RECORD +8 -6
- {uk_bin_collection-0.84.2.dist-info → uk_bin_collection-0.85.1.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.84.2.dist-info → uk_bin_collection-0.85.1.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.84.2.dist-info → uk_bin_collection-0.85.1.dist-info}/entry_points.txt +0 -0
@@ -68,6 +68,13 @@
|
|
68
68
|
"wiki_name": "Bedfordshire Council",
|
69
69
|
"wiki_note": "In order to use this parser, you must provide a valid postcode and a uprn retrieved from the councils website for your specific address"
|
70
70
|
},
|
71
|
+
"BelfastCityCouncil": {
|
72
|
+
"skip_get_url": true,
|
73
|
+
"url": "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx",
|
74
|
+
"wiki_name": "BelfastCityCouncil",
|
75
|
+
"postcode": "BT10 0GY",
|
76
|
+
"uprn": "185086469"
|
77
|
+
},
|
71
78
|
"BexleyCouncil": {
|
72
79
|
"house_number": "1 Dorchester Avenue, Bexley",
|
73
80
|
"postcode": "DA5 3AH",
|
@@ -352,6 +359,14 @@
|
|
352
359
|
"url": "https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn=",
|
353
360
|
"wiki_name": "Eastleigh Borough Council"
|
354
361
|
},
|
362
|
+
"EnfieldCouncil": {
|
363
|
+
"url": "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day",
|
364
|
+
"wiki_name": "Enfield Council",
|
365
|
+
"skip_get_url": true,
|
366
|
+
"postcode": "N13 5AJ",
|
367
|
+
"house_number": "111",
|
368
|
+
"web_driver": "http://selenium:4444"
|
369
|
+
},
|
355
370
|
"EnvironmentFirst": {
|
356
371
|
"url": "https://environmentfirst.co.uk/house.php?uprn=100060055444",
|
357
372
|
"wiki_command_url_override": "https://environmentfirst.co.uk/house.php?uprn=XXXXXXXXXX",
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import logging
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
import requests
|
5
|
+
import urllib
|
6
|
+
|
7
|
+
from bs4 import BeautifulSoup
|
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
|
+
|
13
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
14
|
+
class CouncilClass(AbstractGetBinDataClass):
|
15
|
+
"""
|
16
|
+
Concrete classes have to implement all abstract operations of the
|
17
|
+
base class. They can also override some operations with a default
|
18
|
+
implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def get_session_variable(self, soup, id) -> str:
|
22
|
+
"""Extract ASP.NET variable from the HTML."""
|
23
|
+
element = soup.find("input", {"id": id})
|
24
|
+
if element:
|
25
|
+
return element.get("value")
|
26
|
+
else:
|
27
|
+
raise ValueError(f"Unable to find element with id: {id}")
|
28
|
+
|
29
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
30
|
+
bin_data = {"bins": []}
|
31
|
+
headers = {
|
32
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/119.0"
|
33
|
+
}
|
34
|
+
|
35
|
+
session = requests.Session()
|
36
|
+
session.headers.update(headers)
|
37
|
+
|
38
|
+
user_uprn = kwargs.get("uprn")
|
39
|
+
user_postcode = kwargs.get("postcode")
|
40
|
+
URL = "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx"
|
41
|
+
|
42
|
+
# Build initial ASP.NET variables for Postcode Find address
|
43
|
+
response = session.get(URL)
|
44
|
+
response.raise_for_status()
|
45
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
46
|
+
form_data = {
|
47
|
+
"__EVENTTARGET": "",
|
48
|
+
"__EVENTARGUMENT": "",
|
49
|
+
"__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
|
50
|
+
"__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
|
51
|
+
"__SCROLLPOSITIONX": "0",
|
52
|
+
"__SCROLLPOSITIONY": "0",
|
53
|
+
"__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
|
54
|
+
"ctl00$MainContent$searchBy_radio": "P",
|
55
|
+
"ctl00$MainContent$Street_textbox": "",
|
56
|
+
"ctl00$MainContent$Postcode_textbox": user_postcode,
|
57
|
+
"ctl00$MainContent$AddressLookup_button": "Find address"
|
58
|
+
}
|
59
|
+
|
60
|
+
# Build intermediate ASP.NET variables for uprn Select address
|
61
|
+
response = session.post(URL, data=form_data)
|
62
|
+
response.raise_for_status()
|
63
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
64
|
+
form_data = {
|
65
|
+
"__EVENTTARGET": "",
|
66
|
+
"__EVENTARGUMENT": "",
|
67
|
+
"__VIEWSTATE": self.get_session_variable(soup, "__VIEWSTATE"),
|
68
|
+
"__VIEWSTATEGENERATOR": self.get_session_variable(soup, "__VIEWSTATEGENERATOR"),
|
69
|
+
"__SCROLLPOSITIONX": "0",
|
70
|
+
"__SCROLLPOSITIONY": "0",
|
71
|
+
"__EVENTVALIDATION": self.get_session_variable(soup, "__EVENTVALIDATION"),
|
72
|
+
"ctl00$MainContent$searchBy_radio": "P",
|
73
|
+
"ctl00$MainContent$Street_textbox": "",
|
74
|
+
"ctl00$MainContent$Postcode_textbox": user_postcode,
|
75
|
+
"ctl00$MainContent$lstAddresses": user_uprn,
|
76
|
+
"ctl00$MainContent$SelectAddress_button": "Select address"
|
77
|
+
}
|
78
|
+
|
79
|
+
# Actual http call to get Bins Data
|
80
|
+
response = session.post(URL, data=form_data)
|
81
|
+
response.raise_for_status()
|
82
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
83
|
+
|
84
|
+
# Find Bins table and data
|
85
|
+
table = soup.find("div", {"id": "binsGrid"})
|
86
|
+
if table:
|
87
|
+
rows = table.find_all("tr")
|
88
|
+
for row in rows:
|
89
|
+
columns = row.find_all("td")
|
90
|
+
if len(columns) >= 4:
|
91
|
+
collection_type = columns[0].get_text(strip=True)
|
92
|
+
collection_date_raw = columns[3].get_text(strip=True)
|
93
|
+
# if the month number is a single digit there are 2 spaces, stripping all spaces to make it consistent
|
94
|
+
collection_date = datetime.strptime(collection_date_raw.replace(" ", ""),'%a%b%d%Y')
|
95
|
+
bin_entry = {
|
96
|
+
"type": collection_type,
|
97
|
+
"collectionDate": collection_date.strftime(date_format),
|
98
|
+
}
|
99
|
+
bin_data["bins"].append(bin_entry)
|
100
|
+
return bin_data
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import time
|
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
|
+
import pdb
|
8
|
+
|
9
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
10
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
11
|
+
|
12
|
+
|
13
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
14
|
+
class CouncilClass(AbstractGetBinDataClass):
|
15
|
+
"""
|
16
|
+
Concrete classes have to implement all abstract operations of the
|
17
|
+
base class. They can also override some operations with a default
|
18
|
+
implementation.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
22
|
+
driver = None
|
23
|
+
try:
|
24
|
+
user_postcode = kwargs.get("postcode")
|
25
|
+
if not user_postcode:
|
26
|
+
raise ValueError("No postcode provided.")
|
27
|
+
check_postcode(user_postcode)
|
28
|
+
|
29
|
+
user_paon = kwargs.get("paon")
|
30
|
+
check_paon(user_paon)
|
31
|
+
headless = kwargs.get("headless")
|
32
|
+
web_driver = kwargs.get("web_driver")
|
33
|
+
driver = create_webdriver(web_driver, headless, None, __name__)
|
34
|
+
page = "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day"
|
35
|
+
driver.get(page)
|
36
|
+
|
37
|
+
time.sleep(5)
|
38
|
+
|
39
|
+
try:
|
40
|
+
accept_cookies = WebDriverWait(driver, timeout=10).until(
|
41
|
+
EC.presence_of_element_located((By.ID, "ccc-notify-reject"))
|
42
|
+
)
|
43
|
+
accept_cookies.click()
|
44
|
+
except:
|
45
|
+
print(
|
46
|
+
"Accept cookies banner not found or clickable within the specified time."
|
47
|
+
)
|
48
|
+
pass
|
49
|
+
|
50
|
+
postcode_input = WebDriverWait(driver, 10).until(
|
51
|
+
EC.presence_of_element_located(
|
52
|
+
(By.CSS_SELECTOR, '[aria-label="Enter your address"]')
|
53
|
+
)
|
54
|
+
)
|
55
|
+
|
56
|
+
postcode_input.send_keys(user_postcode)
|
57
|
+
|
58
|
+
find_address_button = WebDriverWait(driver, 10).until(
|
59
|
+
EC.presence_of_element_located(
|
60
|
+
(By.ID, 'submitButton0')
|
61
|
+
)
|
62
|
+
)
|
63
|
+
find_address_button.click()
|
64
|
+
|
65
|
+
time.sleep(15)
|
66
|
+
# Wait for address box to be visible
|
67
|
+
select_address_input = WebDriverWait(driver, 10).until(
|
68
|
+
EC.presence_of_element_located(
|
69
|
+
(
|
70
|
+
By.CSS_SELECTOR,
|
71
|
+
'[aria-label="Select full address"]',
|
72
|
+
)
|
73
|
+
)
|
74
|
+
)
|
75
|
+
|
76
|
+
# Select address based
|
77
|
+
select = Select(select_address_input)
|
78
|
+
# Grab the first option as a template
|
79
|
+
first_option = select.options[0].accessible_name
|
80
|
+
template_parts = first_option.split(", ")
|
81
|
+
template_parts[0] = user_paon # Replace the first part with user_paon
|
82
|
+
|
83
|
+
addr_label = ", ".join(template_parts)
|
84
|
+
for addr_option in select.options:
|
85
|
+
option_name = addr_option.accessible_name[0 : len(addr_label)]
|
86
|
+
if option_name == addr_label:
|
87
|
+
break
|
88
|
+
select.select_by_value(addr_option.text)
|
89
|
+
|
90
|
+
time.sleep(10)
|
91
|
+
# Wait for the specified div to be present
|
92
|
+
target_div_id = "FinalResults"
|
93
|
+
target_div = WebDriverWait(driver, 10).until(
|
94
|
+
EC.presence_of_element_located((By.ID, target_div_id))
|
95
|
+
)
|
96
|
+
|
97
|
+
time.sleep(5)
|
98
|
+
soup = BeautifulSoup(driver.page_source, "html.parser")
|
99
|
+
|
100
|
+
# Find the div with the specified id
|
101
|
+
target_div = soup.find("div", {"id": target_div_id})
|
102
|
+
|
103
|
+
|
104
|
+
# Check if the div is found
|
105
|
+
if target_div:
|
106
|
+
bin_data = {"bins": []}
|
107
|
+
|
108
|
+
for bin_div in target_div.find_all(
|
109
|
+
"div"
|
110
|
+
):
|
111
|
+
# Extract the collection date from the message
|
112
|
+
try:
|
113
|
+
bin_collection_message = bin_div.find("p").text.strip()
|
114
|
+
date_pattern = r"\b\d{2}/\d{2}/\d{4}\b"
|
115
|
+
|
116
|
+
collection_date_string = (
|
117
|
+
re.search(date_pattern, bin_div.text)
|
118
|
+
.group(0)
|
119
|
+
.strip()
|
120
|
+
.replace(",", "")
|
121
|
+
)
|
122
|
+
except AttributeError:
|
123
|
+
continue
|
124
|
+
|
125
|
+
current_date = datetime.now()
|
126
|
+
parsed_date = datetime.strptime(
|
127
|
+
collection_date_string, "%d/%m/%Y"
|
128
|
+
)
|
129
|
+
# Check if the parsed date is in the past and not today
|
130
|
+
if parsed_date.date() < current_date.date():
|
131
|
+
# If so, set the year to the next year
|
132
|
+
parsed_date = parsed_date.replace(year=current_date.year + 1)
|
133
|
+
else:
|
134
|
+
# If not, set the year to the current year
|
135
|
+
parsed_date = parsed_date.replace(year=current_date.year)
|
136
|
+
formatted_date = parsed_date.strftime("%d/%m/%Y")
|
137
|
+
contains_date(formatted_date)
|
138
|
+
|
139
|
+
# Extract the bin type from the message
|
140
|
+
bin_type_match = re.search(r"Your next (.*?) collection", bin_collection_message)
|
141
|
+
if bin_type_match:
|
142
|
+
bin_info = {"type": bin_type_match.group(1), "collectionDate": formatted_date}
|
143
|
+
bin_data["bins"].append(bin_info)
|
144
|
+
else:
|
145
|
+
raise ValueError("Collection data not found.")
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
# Here you can log the exception if needed
|
149
|
+
print(f"An error occurred: {e}")
|
150
|
+
# Optionally, re-raise the exception if you want it to propagate
|
151
|
+
raise
|
152
|
+
finally:
|
153
|
+
# This block ensures that the driver is closed regardless of an exception
|
154
|
+
if driver:
|
155
|
+
driver.quit()
|
156
|
+
return bin_data
|
@@ -2,7 +2,7 @@ uk_bin_collection/README.rst,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
2
2
|
uk_bin_collection/tests/council_feature_input_parity.py,sha256=DO6Mk4ImYgM5ZCZ-cutwz5RoYYWZRLYx2tr6zIs_9Rc,3843
|
3
3
|
uk_bin_collection/tests/features/environment.py,sha256=VQZjJdJI_kZn08M0j5cUgvKT4k3iTw8icJge1DGOkoA,127
|
4
4
|
uk_bin_collection/tests/features/validate_council_outputs.feature,sha256=SJK-Vc737hrf03tssxxbeg_JIvAH-ddB8f6gU1LTbuQ,251
|
5
|
-
uk_bin_collection/tests/input.json,sha256=
|
5
|
+
uk_bin_collection/tests/input.json,sha256=zEy759soyIGIG4vmYmIbKloY_kH4R0CDeEFT7ToWRRs,59654
|
6
6
|
uk_bin_collection/tests/output.schema,sha256=ZwKQBwYyTDEM4G2hJwfLUVM-5v1vKRvRK9W9SS1sd18,1086
|
7
7
|
uk_bin_collection/tests/step_defs/step_helpers/file_handler.py,sha256=Ygzi4V0S1MIHqbdstUlIqtRIwnynvhu4UtpweJ6-5N8,1474
|
8
8
|
uk_bin_collection/tests/step_defs/test_validate_council.py,sha256=LrOSt_loA1Mw3vTqaO2LpaDMu7rYJy6k5Kr-EOBln7s,3424
|
@@ -20,6 +20,7 @@ uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py,sha256=VPWGlj
|
|
20
20
|
uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py,sha256=N_TPiIv8VBzN3rY0p3JtLlxSEru-6k1wW4UNIhN5X1M,3709
|
21
21
|
uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py,sha256=CvGB7w9HMn7XyEtwfd9MWZE_HlZ75pDcaKMsQJz0xhk,1669
|
22
22
|
uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py,sha256=U1HOr9YLMAlFoZysfw5n04E0bVuCliO5Yj1FMiiwcHA,2549
|
23
|
+
uk_bin_collection/uk_bin_collection/councils/BelfastCityCouncil.py,sha256=mspYVHO8fgoVIwogT6V2Go1tbf3PDbEmr8kZMJug5Gs,4235
|
23
24
|
uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py,sha256=9MrbpfR17R6DYjDrItQL3CPF7ie1SJkaoNbwQumPgIQ,5399
|
24
25
|
uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py,sha256=yrO9dnSMdqVTcrzqXXfexFxSuESW01VPxXNAl0_lNOU,4669
|
25
26
|
uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py,sha256=jHbCK8sL09vdmdP7Xnh8lIrU5AHTnJLEZfOLephPvWg,4090
|
@@ -58,6 +59,7 @@ uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py,sha25
|
|
58
59
|
uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py,sha256=CsYdkmL-8Ty-Kz7uNdlnJnhiDMgOPah_swYgSKbaFqA,5218
|
59
60
|
uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py,sha256=qQ0oOfGd0sWcczse_B22YoeL9uj3og8v3UJLt_Sx29c,4353
|
60
61
|
uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py,sha256=V4Vso4DvawFiezKlmXbTlJEK9Sjhz9nA8WeYjwtO2e4,2310
|
62
|
+
uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py,sha256=HhKHlLciZKXViqcgkWme-wBUKlGhAs5LIpkKuRETvXM,6119
|
61
63
|
uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py,sha256=_9QJYDHpdnYK5R6znvZk1w0F9GnPnI8G4b6I_p26h4U,1695
|
62
64
|
uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py,sha256=cKFllQ4zt6MGkwiz_HedZvw3iL1kRMLA6Ct2spUE5og,2085
|
63
65
|
uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py,sha256=QTQA6NjZtTL2baDeerIQW1SQpawwu6kGDMGdVvYQRRo,2501
|
@@ -182,8 +184,8 @@ uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py,sha256=I2kBYMlsD4bId
|
|
182
184
|
uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py,sha256=4s9ODGPAwPqwXc8SrTX5Wlfmizs3_58iXUtHc4Ir86o,1162
|
183
185
|
uk_bin_collection/uk_bin_collection/create_new_council.py,sha256=m-IhmWmeWQlFsTZC4OxuFvtw5ZtB8EAJHxJTH4O59lQ,1536
|
184
186
|
uk_bin_collection/uk_bin_collection/get_bin_data.py,sha256=YvmHfZqanwrJ8ToGch34x-L-7yPe31nB_x77_Mgl_vo,4545
|
185
|
-
uk_bin_collection-0.
|
186
|
-
uk_bin_collection-0.
|
187
|
-
uk_bin_collection-0.
|
188
|
-
uk_bin_collection-0.
|
189
|
-
uk_bin_collection-0.
|
187
|
+
uk_bin_collection-0.85.1.dist-info/LICENSE,sha256=vABBUOzcrgfaTKpzeo-si9YVEun6juDkndqA8RKdKGs,1071
|
188
|
+
uk_bin_collection-0.85.1.dist-info/METADATA,sha256=SdozpBmBLib8V-KxdrKhdkBe-FH3-mkckz0woby8UCg,16231
|
189
|
+
uk_bin_collection-0.85.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
190
|
+
uk_bin_collection-0.85.1.dist-info/entry_points.txt,sha256=36WCSGMWSc916S3Hi1ZkazzDKHaJ6CD-4fCEFm5MIao,90
|
191
|
+
uk_bin_collection-0.85.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|