uk_bin_collection 0.74.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/README.rst +0 -0
- uk_bin_collection/tests/council_feature_input_parity.py +79 -0
- uk_bin_collection/tests/features/environment.py +7 -0
- uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
- uk_bin_collection/tests/input.json +1077 -0
- uk_bin_collection/tests/output.schema +41 -0
- uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
- uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
- uk_bin_collection/tests/test_collect_data.py +104 -0
- uk_bin_collection/tests/test_common_functions.py +342 -0
- uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
- uk_bin_collection/uk_bin_collection/common.py +292 -0
- uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
- uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
- uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
- uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
- uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
- uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
- uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
- uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
- uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
- uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
- uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
- uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
- uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
- uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
- uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
- uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
- uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
- uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
- uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
- uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
- uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
- uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
- uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
- uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
- uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
- uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
- uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
- uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
- uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
- uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
- uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
- uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
- uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
- uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
- uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
- uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
- uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
- uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
- uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
- uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
- uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
- uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
- uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
- uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
- uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
- uk_bin_collection-0.74.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
from bs4 import BeautifulSoup
|
6
|
+
from datetime import datetime
|
7
|
+
from selenium.webdriver.common.by import By
|
8
|
+
from selenium.webdriver.support import expected_conditions as EC
|
9
|
+
from selenium.webdriver.support.ui import Select
|
10
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
11
|
+
from selenium.webdriver.common.keys import Keys
|
12
|
+
|
13
|
+
import pandas as pd
|
14
|
+
import urllib.request
|
15
|
+
|
16
|
+
|
17
|
+
class CouncilClass(AbstractGetBinDataClass):
|
18
|
+
"""
|
19
|
+
Concrete classes have to implement all abstract operations of the base
|
20
|
+
class. They can also override some operations with a default
|
21
|
+
implementation.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
25
|
+
driver = None
|
26
|
+
try:
|
27
|
+
user_paon = kwargs.get("paon")
|
28
|
+
user_postcode = kwargs.get("postcode")
|
29
|
+
web_driver = kwargs.get("web_driver")
|
30
|
+
headless = kwargs.get("headless")
|
31
|
+
|
32
|
+
# Create Selenium webdriver
|
33
|
+
page = f"https://www.eastriding.gov.uk/environment/bins-rubbish-recycling/bins-and-collections/bin-collection-dates/"
|
34
|
+
|
35
|
+
driver = create_webdriver(web_driver, headless)
|
36
|
+
driver.get(page)
|
37
|
+
|
38
|
+
wait = WebDriverWait(driver, 60)
|
39
|
+
|
40
|
+
try:
|
41
|
+
accept_cookies = wait.until(
|
42
|
+
EC.element_to_be_clickable(
|
43
|
+
(By.XPATH, '//*[@id="er-cookie-placeholder-settings"]/div/a[1]')
|
44
|
+
)
|
45
|
+
)
|
46
|
+
|
47
|
+
accept_cookies.click()
|
48
|
+
except:
|
49
|
+
print(
|
50
|
+
"Cookies acceptance element not found or clickable within the specified time."
|
51
|
+
)
|
52
|
+
pass
|
53
|
+
|
54
|
+
expand_postcode_box = wait.until(
|
55
|
+
EC.element_to_be_clickable(
|
56
|
+
(By.ID, "when-and-where-should-i-put-my-bin-out")
|
57
|
+
)
|
58
|
+
)
|
59
|
+
|
60
|
+
expand_postcode_box.click()
|
61
|
+
|
62
|
+
postcode_box = wait.until(
|
63
|
+
EC.element_to_be_clickable(
|
64
|
+
(By.XPATH, "//input[@placeholder='Enter your postcode']")
|
65
|
+
)
|
66
|
+
)
|
67
|
+
postcode_box.send_keys(user_postcode)
|
68
|
+
|
69
|
+
postcode_search_btn = wait.until(
|
70
|
+
EC.element_to_be_clickable((By.XPATH, "//button[text()='Search']"))
|
71
|
+
)
|
72
|
+
postcode_search_btn.send_keys(Keys.ENTER)
|
73
|
+
|
74
|
+
dropdown = wait.until(
|
75
|
+
EC.presence_of_element_located(
|
76
|
+
(
|
77
|
+
By.XPATH,
|
78
|
+
"//div[@class='er-select-wrapper']/select[@class='dropdown']",
|
79
|
+
)
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
options_present = wait.until(
|
84
|
+
EC.presence_of_all_elements_located(
|
85
|
+
(By.CSS_SELECTOR, "select.dropdown option")
|
86
|
+
)
|
87
|
+
)
|
88
|
+
drop = Select(dropdown)
|
89
|
+
drop.select_by_visible_text(str(user_paon))
|
90
|
+
|
91
|
+
results_present = wait.until(
|
92
|
+
EC.presence_of_element_located(
|
93
|
+
(
|
94
|
+
By.CLASS_NAME,
|
95
|
+
"results",
|
96
|
+
)
|
97
|
+
)
|
98
|
+
)
|
99
|
+
|
100
|
+
data = {"bins": []} # dictionary for data
|
101
|
+
soup = BeautifulSoup(driver.page_source, "html.parser")
|
102
|
+
|
103
|
+
bin_types = {} # Dictionary to store bin types
|
104
|
+
|
105
|
+
# Extract bin types from header elements
|
106
|
+
header_elements = soup.find_all(
|
107
|
+
"li", class_=lambda x: x and x.startswith("header")
|
108
|
+
)
|
109
|
+
for header_element in header_elements:
|
110
|
+
bin_type = header_element.get_text(strip=True)
|
111
|
+
bin_class = [
|
112
|
+
cls for cls in header_element.get("class") if cls != "header"
|
113
|
+
]
|
114
|
+
if bin_class:
|
115
|
+
bin_types[bin_class[0]] = bin_type
|
116
|
+
|
117
|
+
# Extract collection dates and associate them with respective bin types
|
118
|
+
date_elements = soup.find_all(
|
119
|
+
"li", class_=lambda x: x and x.startswith("date")
|
120
|
+
)
|
121
|
+
for date_element in date_elements:
|
122
|
+
bin_class = [cls for cls in date_element.get("class") if cls != "date"]
|
123
|
+
if bin_class:
|
124
|
+
bin_type = bin_types.get(bin_class[0])
|
125
|
+
span_text = date_element.find("span").get_text(strip=True)
|
126
|
+
collection_date = datetime.strptime(span_text, "%a, %d %b %Y")
|
127
|
+
data["bins"].append(
|
128
|
+
{
|
129
|
+
"type": bin_type,
|
130
|
+
"collectionDate": collection_date.strftime(date_format),
|
131
|
+
}
|
132
|
+
)
|
133
|
+
except Exception as e:
|
134
|
+
# Here you can log the exception if needed
|
135
|
+
print(f"An error occurred: {e}")
|
136
|
+
# Optionally, re-raise the exception if you want it to propagate
|
137
|
+
raise
|
138
|
+
finally:
|
139
|
+
# This block ensures that the driver is closed regardless of an exception
|
140
|
+
if driver:
|
141
|
+
driver.quit()
|
142
|
+
return data
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from time import sleep
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from selenium.webdriver.common.by import By
|
4
|
+
from selenium.webdriver.support import expected_conditions as EC
|
5
|
+
from selenium.webdriver.support.ui import WebDriverWait, Select
|
6
|
+
|
7
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
8
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
9
|
+
|
10
|
+
|
11
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
12
|
+
class CouncilClass(AbstractGetBinDataClass):
|
13
|
+
"""
|
14
|
+
Concrete classes have to implement all abstract operations of the base
|
15
|
+
class. They can also override some operations with a default
|
16
|
+
implementation.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
20
|
+
driver = None
|
21
|
+
try:
|
22
|
+
user_uprn = kwargs.get("uprn")
|
23
|
+
user_postcode = kwargs.get("postcode")
|
24
|
+
web_driver = kwargs.get("web_driver")
|
25
|
+
headless = kwargs.get("headless")
|
26
|
+
check_uprn(user_uprn)
|
27
|
+
check_postcode(user_postcode)
|
28
|
+
|
29
|
+
# Create Selenium webdriver
|
30
|
+
driver = create_webdriver(web_driver, headless)
|
31
|
+
driver.get(
|
32
|
+
"https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder"
|
33
|
+
)
|
34
|
+
|
35
|
+
# Wait for iframe to load and switch to it
|
36
|
+
WebDriverWait(driver, 30).until(
|
37
|
+
EC.frame_to_be_available_and_switch_to_it((By.ID, "fillform-frame-1"))
|
38
|
+
)
|
39
|
+
|
40
|
+
# Wait for postcode entry box
|
41
|
+
postcode = WebDriverWait(driver, 10).until(
|
42
|
+
EC.presence_of_element_located((By.ID, "alt_postcode_search"))
|
43
|
+
)
|
44
|
+
# Enter postcode
|
45
|
+
postcode.send_keys(user_postcode.replace(" ", ""))
|
46
|
+
|
47
|
+
# Wait for address selection dropdown to appear
|
48
|
+
address = Select(
|
49
|
+
WebDriverWait(driver, 10).until(
|
50
|
+
EC.visibility_of_element_located((By.ID, "alt_choose_address"))
|
51
|
+
)
|
52
|
+
)
|
53
|
+
|
54
|
+
# Wait for spinner to disappear (signifies options are loaded for select)
|
55
|
+
WebDriverWait(driver, 10).until(
|
56
|
+
EC.invisibility_of_element_located((By.CLASS_NAME, "spinner-outer"))
|
57
|
+
)
|
58
|
+
|
59
|
+
# Sometimes the options aren't fully there despite the spinner being gone, wait another 2 seconds.
|
60
|
+
sleep(2)
|
61
|
+
|
62
|
+
# Select address by UPRN
|
63
|
+
address.select_by_value(user_uprn)
|
64
|
+
|
65
|
+
# Wait for spinner to disappear (signifies data is loaded)
|
66
|
+
WebDriverWait(driver, 10).until(
|
67
|
+
EC.invisibility_of_element_located((By.CLASS_NAME, "spinner-outer"))
|
68
|
+
)
|
69
|
+
|
70
|
+
sleep(2)
|
71
|
+
|
72
|
+
# Find data table
|
73
|
+
data_table = WebDriverWait(driver, 10).until(
|
74
|
+
EC.presence_of_element_located(
|
75
|
+
(
|
76
|
+
By.XPATH,
|
77
|
+
'//div[@data-field-name="collection_details"]/div[contains(@class, "fieldContent")]/div[contains(@class, "repeatable-table-wrapper")]',
|
78
|
+
)
|
79
|
+
)
|
80
|
+
)
|
81
|
+
|
82
|
+
# Make a BS4 object
|
83
|
+
soup = BeautifulSoup(
|
84
|
+
data_table.get_attribute("innerHTML"), features="html.parser"
|
85
|
+
)
|
86
|
+
|
87
|
+
data = {"bins": []}
|
88
|
+
|
89
|
+
rows = soup.find("table").find("tbody").find_all("tr")
|
90
|
+
for row in rows:
|
91
|
+
cols = row.find_all("td")
|
92
|
+
bin_type = cols[2].find_all("span")[1].text.title()
|
93
|
+
collection_date = cols[3].find_all("span")[1].text
|
94
|
+
collection_date = datetime.strptime(
|
95
|
+
collection_date, "%d/%m/%Y"
|
96
|
+
).strftime(date_format)
|
97
|
+
dict_data = {"type": bin_type, "collectionDate": collection_date}
|
98
|
+
data["bins"].append(dict_data)
|
99
|
+
|
100
|
+
data["bins"].sort(
|
101
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
102
|
+
)
|
103
|
+
except Exception as e:
|
104
|
+
# Here you can log the exception if needed
|
105
|
+
print(f"An error occurred: {e}")
|
106
|
+
# Optionally, re-raise the exception if you want it to propagate
|
107
|
+
raise
|
108
|
+
finally:
|
109
|
+
# This block ensures that the driver is closed regardless of an exception
|
110
|
+
if driver:
|
111
|
+
driver.quit()
|
112
|
+
return data
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
uprn = kwargs.get("uprn")
|
16
|
+
# Check the UPRN is valid
|
17
|
+
check_uprn(uprn)
|
18
|
+
|
19
|
+
# Request URL
|
20
|
+
url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}"
|
21
|
+
|
22
|
+
# Make Request
|
23
|
+
requests.packages.urllib3.disable_warnings()
|
24
|
+
page = requests.get(url)
|
25
|
+
|
26
|
+
# Make a BS4 object
|
27
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
28
|
+
soup.prettify()
|
29
|
+
|
30
|
+
# Data to return
|
31
|
+
data = {"bins": []}
|
32
|
+
|
33
|
+
# Valid bin types
|
34
|
+
binTypes = [
|
35
|
+
"Household Waste Bin",
|
36
|
+
"Recycling Bin",
|
37
|
+
"Food Waste Bin",
|
38
|
+
"Glass Box and Batteries",
|
39
|
+
"Garden Waste Bin",
|
40
|
+
]
|
41
|
+
|
42
|
+
# Value to create dict for DL values
|
43
|
+
keys, values = [], []
|
44
|
+
|
45
|
+
# Loop though DT and DD for DL containing bins
|
46
|
+
dl = soup.find("dl", {"class": "dl-horizontal"})
|
47
|
+
for dt in dl.find_all("dt"):
|
48
|
+
keys.append(dt.text.strip())
|
49
|
+
for dd in dl.find_all("dd"):
|
50
|
+
values.append(dd.text.strip())
|
51
|
+
|
52
|
+
# Create dict for bin name and string dates
|
53
|
+
binDict = dict(zip(keys, values))
|
54
|
+
|
55
|
+
# Process dict for valid bin types
|
56
|
+
for bin in list(binDict):
|
57
|
+
if bin in binTypes:
|
58
|
+
if not binDict[bin].startswith("You haven't yet signed up for"):
|
59
|
+
# Convert date
|
60
|
+
date = datetime.strptime(binDict[bin], "%a, %d %b %Y")
|
61
|
+
|
62
|
+
# Set bin data
|
63
|
+
dict_data = {
|
64
|
+
"type": bin,
|
65
|
+
"collectionDate": date.strftime(date_format),
|
66
|
+
}
|
67
|
+
data["bins"].append(dict_data)
|
68
|
+
|
69
|
+
# Return bin data
|
70
|
+
return data
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
# Make a BS4 object
|
16
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
17
|
+
soup.prettify()
|
18
|
+
|
19
|
+
# Get the paragraph lines from the page
|
20
|
+
data = {"bins": []}
|
21
|
+
page_text = soup.find("div", {"class": "collect"}).find_all("p")
|
22
|
+
|
23
|
+
# Parse the correct lines (find them, remove the ordinal indicator and make them the correct format date) and
|
24
|
+
# then add them to the dictionary
|
25
|
+
rubbish_day = datetime.strptime(
|
26
|
+
remove_ordinal_indicator_from_date_string(
|
27
|
+
page_text[2].find_next("strong").text
|
28
|
+
),
|
29
|
+
"%d %B %Y",
|
30
|
+
).strftime(date_format)
|
31
|
+
dict_data = {
|
32
|
+
"type": "Rubbish",
|
33
|
+
"collectionDate": rubbish_day,
|
34
|
+
}
|
35
|
+
data["bins"].append(dict_data)
|
36
|
+
recycling_day = datetime.strptime(
|
37
|
+
remove_ordinal_indicator_from_date_string(
|
38
|
+
page_text[4].find_next("strong").text
|
39
|
+
),
|
40
|
+
"%d %B %Y",
|
41
|
+
).strftime(date_format)
|
42
|
+
dict_data = {
|
43
|
+
"type": "Recycling",
|
44
|
+
"collectionDate": recycling_day,
|
45
|
+
}
|
46
|
+
data["bins"].append(dict_data)
|
47
|
+
|
48
|
+
return data
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
data = {"bins": []}
|
16
|
+
uprn = kwargs.get("uprn")
|
17
|
+
check_uprn(uprn)
|
18
|
+
|
19
|
+
requests.packages.urllib3.disable_warnings()
|
20
|
+
response = requests.get(
|
21
|
+
f"https://map.erewash.gov.uk/isharelive.web/myerewash.aspx?action=SetAddress&UniqueId={uprn}",
|
22
|
+
headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"},
|
23
|
+
)
|
24
|
+
|
25
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
26
|
+
collections = soup.find("div", {"aria-label": "Waste Collection"}).find_all(
|
27
|
+
"div", {"class": "atPanelContent"}
|
28
|
+
)
|
29
|
+
for c in collections:
|
30
|
+
bin_type = c.find("h4").get_text(strip=True)
|
31
|
+
if "my next" in bin_type.lower():
|
32
|
+
collection_info = c.find("div", {"class": "atPanelData"}).get_text(
|
33
|
+
strip=True
|
34
|
+
)
|
35
|
+
results = re.search(
|
36
|
+
"([A-Za-z]+ \\d+[A-Za-z]+ [A-Za-z]+ \\d*)", collection_info
|
37
|
+
)
|
38
|
+
if results:
|
39
|
+
collection_date = datetime.strptime(
|
40
|
+
remove_ordinal_indicator_from_date_string(results[1]).strip(),
|
41
|
+
"%A %d %B %Y",
|
42
|
+
).strftime(date_format)
|
43
|
+
dict_data = {
|
44
|
+
"type": bin_type.replace("My Next ", "").replace(
|
45
|
+
" Collection", ""
|
46
|
+
),
|
47
|
+
"collectionDate": collection_date,
|
48
|
+
}
|
49
|
+
data["bins"].append(dict_data)
|
50
|
+
if "garden waste" in collection_info.lower():
|
51
|
+
dict_data = {
|
52
|
+
"type": "Garden Waste",
|
53
|
+
"collectionDate": collection_date,
|
54
|
+
}
|
55
|
+
data["bins"].append(dict_data)
|
56
|
+
|
57
|
+
data["bins"].sort(
|
58
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
59
|
+
)
|
60
|
+
|
61
|
+
return data
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
5
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
6
|
+
|
7
|
+
|
8
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
9
|
+
class CouncilClass(AbstractGetBinDataClass):
|
10
|
+
"""
|
11
|
+
Concrete classes have to implement all abstract operations of the
|
12
|
+
base class. They can also override some operations with a default
|
13
|
+
implementation.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
17
|
+
user_uprn = kwargs.get("uprn")
|
18
|
+
check_uprn(user_uprn)
|
19
|
+
|
20
|
+
headers = {
|
21
|
+
"Accept": "application/json, text/javascript, */*; q=0.01",
|
22
|
+
"Accept-Language": "en-GB,en;q=0.7",
|
23
|
+
"Connection": "keep-alive",
|
24
|
+
"Content-Type": "application/json; charset=utf-8",
|
25
|
+
"Referer": "https://www.fenland.gov.uk/article/13114/?uprn=200002981143&lat=52.665569590474&lng=0.177905443639&postcode=PE13+3SL&line1=20+Felsted+Avenue&rad=5m&layers=2%2C3%2C1",
|
26
|
+
"Sec-Fetch-Dest": "empty",
|
27
|
+
"Sec-Fetch-Mode": "cors",
|
28
|
+
"Sec-Fetch-Site": "same-origin",
|
29
|
+
"Sec-GPC": "1",
|
30
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
|
31
|
+
"X-Requested-With": "XMLHttpRequest",
|
32
|
+
}
|
33
|
+
|
34
|
+
# It needs lat and lng for point data, but we don't need it >:)
|
35
|
+
params = {
|
36
|
+
"type": "loadlayer",
|
37
|
+
"layerId": "2",
|
38
|
+
"uprn": user_uprn,
|
39
|
+
"lat": "0.000000000001",
|
40
|
+
"lng": "0.000000000001",
|
41
|
+
}
|
42
|
+
|
43
|
+
requests.packages.urllib3.disable_warnings()
|
44
|
+
response = requests.get(
|
45
|
+
"https://www.fenland.gov.uk/article/13114/", params=params, headers=headers
|
46
|
+
)
|
47
|
+
|
48
|
+
# Returned data is just json, so we can get what we need
|
49
|
+
json_data = json.loads(response.text)["features"][0]["properties"]["upcoming"]
|
50
|
+
data = {"bins": []}
|
51
|
+
|
52
|
+
for item in json_data:
|
53
|
+
collections_list = item["collections"]
|
54
|
+
for bin in collections_list:
|
55
|
+
bin_type = bin["desc"]
|
56
|
+
bin_date = datetime.strptime(
|
57
|
+
bin["collectionDate"], "%Y-%m-%dT%H:%M:%SZ"
|
58
|
+
).strftime(date_format)
|
59
|
+
dict_data = {
|
60
|
+
"type": bin_type,
|
61
|
+
"collectionDate": bin_date,
|
62
|
+
}
|
63
|
+
data["bins"].append(dict_data)
|
64
|
+
|
65
|
+
return data
|
@@ -0,0 +1,113 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from datetime import datetime
|
3
|
+
from selenium.webdriver.common.by import By
|
4
|
+
from selenium.webdriver.support import expected_conditions as EC
|
5
|
+
from selenium.webdriver.support.ui import Select
|
6
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
7
|
+
from selenium.webdriver.common.keys import Keys
|
8
|
+
import time
|
9
|
+
|
10
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
11
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
12
|
+
|
13
|
+
|
14
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
15
|
+
|
16
|
+
|
17
|
+
class CouncilClass(AbstractGetBinDataClass):
|
18
|
+
"""
|
19
|
+
Concrete classes have to implement all abstract operations of the
|
20
|
+
base class. They can also override some operations with a default
|
21
|
+
implementation.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
25
|
+
driver = None
|
26
|
+
try:
|
27
|
+
page = "https://community.fdean.gov.uk/s/waste-collection-enquiry"
|
28
|
+
|
29
|
+
data = {"bins": []}
|
30
|
+
|
31
|
+
house_number = kwargs.get("paon")
|
32
|
+
postcode = kwargs.get("postcode")
|
33
|
+
full_address = f"{house_number}, {postcode}"
|
34
|
+
web_driver = kwargs.get("web_driver")
|
35
|
+
headless = kwargs.get("headless")
|
36
|
+
|
37
|
+
# Create Selenium webdriver
|
38
|
+
driver = create_webdriver(web_driver, headless)
|
39
|
+
driver.get(page)
|
40
|
+
|
41
|
+
# If you bang in the house number (or property name) and postcode in the box it should find your property
|
42
|
+
wait = WebDriverWait(driver, 60)
|
43
|
+
address_entry_field = wait.until(
|
44
|
+
EC.presence_of_element_located(
|
45
|
+
(By.XPATH, '//*[@id="combobox-input-19"]')
|
46
|
+
)
|
47
|
+
)
|
48
|
+
|
49
|
+
address_entry_field.send_keys(str(full_address))
|
50
|
+
|
51
|
+
address_entry_field = wait.until(
|
52
|
+
EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-19"]'))
|
53
|
+
)
|
54
|
+
address_entry_field.click()
|
55
|
+
address_entry_field.send_keys(Keys.BACKSPACE)
|
56
|
+
address_entry_field.send_keys(str(full_address[len(full_address) - 1]))
|
57
|
+
|
58
|
+
first_found_address = wait.until(
|
59
|
+
EC.element_to_be_clickable(
|
60
|
+
(By.XPATH, '//*[@id="dropdown-element-19"]/ul')
|
61
|
+
)
|
62
|
+
)
|
63
|
+
|
64
|
+
first_found_address.click()
|
65
|
+
# Wait for the 'Select your property' dropdown to appear and select the first result
|
66
|
+
next_btn = wait.until(
|
67
|
+
EC.element_to_be_clickable((By.XPATH, "//lightning-button/button"))
|
68
|
+
)
|
69
|
+
next_btn.click()
|
70
|
+
bin_data = wait.until(
|
71
|
+
EC.presence_of_element_located(
|
72
|
+
(By.XPATH, "//span[contains(text(), 'Container')]")
|
73
|
+
)
|
74
|
+
)
|
75
|
+
|
76
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
77
|
+
|
78
|
+
rows = soup.find_all("tr", class_="slds-hint-parent")
|
79
|
+
current_year = datetime.now().year
|
80
|
+
|
81
|
+
for row in rows:
|
82
|
+
columns = row.find_all("td")
|
83
|
+
if columns:
|
84
|
+
container_type = row.find("th").text.strip()
|
85
|
+
collection_day = re.sub(
|
86
|
+
r"[^a-zA-Z0-9,\s]", "", columns[0].get_text()
|
87
|
+
).strip()
|
88
|
+
|
89
|
+
# Parse the date from the string
|
90
|
+
parsed_date = datetime.strptime(collection_day, "%a, %d %B")
|
91
|
+
if parsed_date < datetime(
|
92
|
+
parsed_date.year, parsed_date.month, parsed_date.day
|
93
|
+
):
|
94
|
+
parsed_date = parsed_date.replace(year=current_year + 1)
|
95
|
+
else:
|
96
|
+
parsed_date = parsed_date.replace(year=current_year)
|
97
|
+
# Format the date as %d/%m/%Y
|
98
|
+
formatted_date = parsed_date.strftime("%d/%m/%Y")
|
99
|
+
|
100
|
+
# Add the bin type and collection date to the 'data' dictionary
|
101
|
+
data["bins"].append(
|
102
|
+
{"type": container_type, "collectionDate": formatted_date}
|
103
|
+
)
|
104
|
+
except Exception as e:
|
105
|
+
# Here you can log the exception if needed
|
106
|
+
print(f"An error occurred: {e}")
|
107
|
+
# Optionally, re-raise the exception if you want it to propagate
|
108
|
+
raise
|
109
|
+
finally:
|
110
|
+
# This block ensures that the driver is closed regardless of an exception
|
111
|
+
if driver:
|
112
|
+
driver.quit()
|
113
|
+
return data
|