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,103 @@
|
|
1
|
+
import requests
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
4
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
5
|
+
|
6
|
+
|
7
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
8
|
+
class CouncilClass(AbstractGetBinDataClass):
|
9
|
+
"""
|
10
|
+
Concrete classes have to implement all abstract operations of the
|
11
|
+
base class. They can also override some operations with a default
|
12
|
+
implementation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
16
|
+
user_uprn = kwargs.get("uprn")
|
17
|
+
check_uprn(user_uprn)
|
18
|
+
|
19
|
+
# UPRN is passed in via a cookie. Set cookies/params and GET the page
|
20
|
+
cookies = {
|
21
|
+
"SVBINZONE": f"VALE%3AUPRN%40{user_uprn}",
|
22
|
+
}
|
23
|
+
headers = {
|
24
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
25
|
+
"Accept-Language": "en-GB,en;q=0.7",
|
26
|
+
"Cache-Control": "max-age=0",
|
27
|
+
"Connection": "keep-alive",
|
28
|
+
"Referer": "https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb?SOVA_TAG=VALE&ebd=0&ebz=1_1704201201813",
|
29
|
+
"Sec-Fetch-Dest": "document",
|
30
|
+
"Sec-Fetch-Mode": "navigate",
|
31
|
+
"Sec-Fetch-Site": "same-origin",
|
32
|
+
"Sec-Fetch-User": "?1",
|
33
|
+
"Sec-GPC": "1",
|
34
|
+
"Upgrade-Insecure-Requests": "1",
|
35
|
+
"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",
|
36
|
+
}
|
37
|
+
params = {
|
38
|
+
"SOVA_TAG": "VALE",
|
39
|
+
"ebd": "0",
|
40
|
+
}
|
41
|
+
requests.packages.urllib3.disable_warnings()
|
42
|
+
response = requests.get(
|
43
|
+
"https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb",
|
44
|
+
params=params,
|
45
|
+
headers=headers,
|
46
|
+
cookies=cookies,
|
47
|
+
)
|
48
|
+
|
49
|
+
# Parse response text for super speedy finding
|
50
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
51
|
+
soup.prettify()
|
52
|
+
|
53
|
+
data = {"bins": []}
|
54
|
+
|
55
|
+
# Page has slider info side by side, which are two instances of this class
|
56
|
+
for bin in soup.find_all("div", {"class": "bintxt"}):
|
57
|
+
try:
|
58
|
+
# Check bin type heading and make that bin type and colour
|
59
|
+
bin_type_info = list(bin.stripped_strings)
|
60
|
+
if "rubbish" in bin_type_info[0]:
|
61
|
+
bin_type = "Rubbish"
|
62
|
+
bin_colour = "Black"
|
63
|
+
elif "recycling" in bin_type_info[0]:
|
64
|
+
bin_type = "Recycling"
|
65
|
+
bin_colour = "Green"
|
66
|
+
else:
|
67
|
+
raise ValueError(f"No bin info found in {bin_type_info[0]}")
|
68
|
+
|
69
|
+
bin_date_info = list(
|
70
|
+
bin.find_next("div", {"class": "binextra"}).stripped_strings
|
71
|
+
)
|
72
|
+
# On standard collection schedule, date will be contained in the first string
|
73
|
+
if contains_date(bin_date_info[0]):
|
74
|
+
bin_date = get_next_occurrence_from_day_month(
|
75
|
+
datetime.strptime(
|
76
|
+
bin_date_info[0] + " " + datetime.today().strftime("%Y"),
|
77
|
+
"%A %d %B - %Y",
|
78
|
+
)
|
79
|
+
).strftime(date_format)
|
80
|
+
# On exceptional collection schedule (e.g. around English Bank Holidays), date will be contained in the second stripped string
|
81
|
+
else:
|
82
|
+
bin_date = get_next_occurrence_from_day_month(
|
83
|
+
datetime.strptime(
|
84
|
+
bin_date_info[1] + " " + datetime.today().strftime("%Y"),
|
85
|
+
"%A %d %B - %Y",
|
86
|
+
)
|
87
|
+
).strftime(date_format)
|
88
|
+
except Exception as ex:
|
89
|
+
raise ValueError(f"Error parsing bin data: {ex}")
|
90
|
+
|
91
|
+
# Build data dict for each entry
|
92
|
+
dict_data = {
|
93
|
+
"type": bin_type,
|
94
|
+
"collectionDate": bin_date,
|
95
|
+
"colour": bin_colour,
|
96
|
+
}
|
97
|
+
data["bins"].append(dict_data)
|
98
|
+
|
99
|
+
data["bins"].sort(
|
100
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
101
|
+
)
|
102
|
+
|
103
|
+
return data
|
@@ -0,0 +1,89 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
|
3
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
4
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
5
|
+
|
6
|
+
|
7
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
8
|
+
class CouncilClass(AbstractGetBinDataClass):
|
9
|
+
"""
|
10
|
+
Concrete classes have to implement all abstract operations of the base
|
11
|
+
class. They can also override some operations with a default
|
12
|
+
implementation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
16
|
+
driver = None
|
17
|
+
try:
|
18
|
+
# Create Selenium webdriver
|
19
|
+
headless = kwargs.get("headless")
|
20
|
+
driver = create_webdriver(kwargs.get("web_driver"), headless)
|
21
|
+
driver.get(kwargs.get("url"))
|
22
|
+
|
23
|
+
# Make a BS4 object
|
24
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
25
|
+
soup.prettify()
|
26
|
+
|
27
|
+
data = {"bins": []}
|
28
|
+
sections = soup.find_all("div", {"class": "wil_c-content-section_heading"})
|
29
|
+
for s in sections:
|
30
|
+
if s.get_text(strip=True).lower() == "bin collections":
|
31
|
+
rows = s.find_next_sibling(
|
32
|
+
"div", {"class": "c-content-section_body"}
|
33
|
+
).find_all("div", {"class": "u-mb-8"})
|
34
|
+
for row in rows:
|
35
|
+
title = row.find("div", {"class": "u-mb-4"})
|
36
|
+
collections = row.find_all("div", {"class": "u-mb-2"})
|
37
|
+
if title and collections:
|
38
|
+
for c in collections:
|
39
|
+
if (
|
40
|
+
c.get_text(strip=True)
|
41
|
+
.lower()
|
42
|
+
.startswith("next collection")
|
43
|
+
):
|
44
|
+
# add next collection
|
45
|
+
next_collection_date = datetime.strptime(
|
46
|
+
c.get_text(strip=True).replace(
|
47
|
+
"Next collection - ", ""
|
48
|
+
),
|
49
|
+
"%A, %d %B %Y",
|
50
|
+
).strftime(date_format)
|
51
|
+
dict_data = {
|
52
|
+
"type": title.get_text(strip=True).capitalize(),
|
53
|
+
"collectionDate": next_collection_date,
|
54
|
+
}
|
55
|
+
data["bins"].append(dict_data)
|
56
|
+
# add future collections without duplicating next collection
|
57
|
+
future_collections = row.find(
|
58
|
+
"ul", {"class": "u-mt-4"}
|
59
|
+
).find_all("li")
|
60
|
+
for c in future_collections:
|
61
|
+
future_collection_date = datetime.strptime(
|
62
|
+
c.get_text(strip=True),
|
63
|
+
"%A, %d %B %Y",
|
64
|
+
).strftime(date_format)
|
65
|
+
if (
|
66
|
+
future_collection_date
|
67
|
+
!= next_collection_date
|
68
|
+
):
|
69
|
+
dict_data = {
|
70
|
+
"type": title.get_text(
|
71
|
+
strip=True
|
72
|
+
).capitalize(),
|
73
|
+
"collectionDate": future_collection_date,
|
74
|
+
}
|
75
|
+
data["bins"].append(dict_data)
|
76
|
+
|
77
|
+
data["bins"].sort(
|
78
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
79
|
+
)
|
80
|
+
except Exception as e:
|
81
|
+
# Here you can log the exception if needed
|
82
|
+
print(f"An error occurred: {e}")
|
83
|
+
# Optionally, re-raise the exception if you want it to propagate
|
84
|
+
raise
|
85
|
+
finally:
|
86
|
+
# This block ensures that the driver is closed regardless of an exception
|
87
|
+
if driver:
|
88
|
+
driver.quit()
|
89
|
+
return data
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This script pulls (in one hit) the data
|
2
|
+
# from Warick District Council Bins Data
|
3
|
+
|
4
|
+
from bs4 import BeautifulSoup
|
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
|
+
baseclass. They can also override some
|
13
|
+
operations with a default implementation.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
17
|
+
# Make a BS4 object
|
18
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
19
|
+
soup.prettify()
|
20
|
+
|
21
|
+
data = {"bins": []}
|
22
|
+
|
23
|
+
for element in soup.find_all("strong"):
|
24
|
+
bin_type = element.next_element
|
25
|
+
bin_type = bin_type.lstrip()
|
26
|
+
collectionDateElement = element.next_sibling.next_element.next_element
|
27
|
+
collectionDate = collectionDateElement.getText()
|
28
|
+
dict_data = {
|
29
|
+
"type": bin_type,
|
30
|
+
"collectionDate": collectionDate,
|
31
|
+
}
|
32
|
+
data["bins"].append(dict_data)
|
33
|
+
|
34
|
+
return data
|
@@ -0,0 +1,119 @@
|
|
1
|
+
from datetime import date, datetime
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
10
|
+
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
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
# pindex isn't actually paon, it's a url parameter that I'm guessing the council use as a property id
|
19
|
+
data = {"bins": []}
|
20
|
+
pindex = kwargs.get("paon")
|
21
|
+
user_postcode = kwargs.get("postcode")
|
22
|
+
check_postcode(user_postcode)
|
23
|
+
|
24
|
+
# WBC use a url parameter called "Track" that's generated when you start a form session.
|
25
|
+
# So first off, open the page, find the page link and copy it with the Track
|
26
|
+
start_url = "https://wav-wrp.whitespacews.com/"
|
27
|
+
s = requests.session()
|
28
|
+
response = s.get(start_url)
|
29
|
+
soup = BeautifulSoup(response.content, features="html.parser")
|
30
|
+
soup.prettify()
|
31
|
+
collection_page_link = soup.find_all(
|
32
|
+
"p", {"class": "govuk-body govuk-!-margin-bottom-0 colorblue lineheight15"}
|
33
|
+
)[0].find("a")["href"]
|
34
|
+
track_id = collection_page_link[33:60]
|
35
|
+
|
36
|
+
# Next we need to search using the postcode, but this is actually an important POST request
|
37
|
+
pc_headers = {
|
38
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
|
39
|
+
"image/webp,image/apng,*/*;q=0.8",
|
40
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
41
|
+
"Cache-Control": "max-age=0",
|
42
|
+
"Connection": "keep-alive",
|
43
|
+
"Origin": "https://wav-wrp.whitespacews.com",
|
44
|
+
"Referer": "https://wav-wrp.whitespacews.com/"
|
45
|
+
+ track_id
|
46
|
+
+ "&serviceID=A&seq=2",
|
47
|
+
"Sec-Fetch-Dest": "document",
|
48
|
+
"Sec-Fetch-Mode": "navigate",
|
49
|
+
"Sec-Fetch-Site": "same-origin",
|
50
|
+
"Sec-Fetch-User": "?1",
|
51
|
+
"Sec-GPC": "1",
|
52
|
+
"Upgrade-Insecure-Requests": "1",
|
53
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
54
|
+
"like Gecko) Chrome/106.0.0.0 Safari/537.36",
|
55
|
+
}
|
56
|
+
form_data = {
|
57
|
+
"address_name_number": "",
|
58
|
+
"address_street": "",
|
59
|
+
"street_town": "",
|
60
|
+
"address_postcode": user_postcode,
|
61
|
+
}
|
62
|
+
response = s.post(
|
63
|
+
"https://wav-wrp.whitespacews.com/mop.php?serviceID=A&"
|
64
|
+
+ track_id
|
65
|
+
+ "&seq=2",
|
66
|
+
headers=pc_headers,
|
67
|
+
data=form_data,
|
68
|
+
)
|
69
|
+
|
70
|
+
# Finally, we can use pindex to find the address and get some data
|
71
|
+
request_url = (
|
72
|
+
"https://wav-wrp.whitespacews.com/mop.php?"
|
73
|
+
+ track_id
|
74
|
+
+ "&serviceID=A&seq=3&pIndex="
|
75
|
+
+ pindex
|
76
|
+
)
|
77
|
+
headers = {
|
78
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
|
79
|
+
"image/webp,image/apng,*/*;q=0.8",
|
80
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
81
|
+
"Connection": "keep-alive",
|
82
|
+
"Referer": "https://wav-wrp.whitespacews.com/mop.php?serviceID=A&"
|
83
|
+
+ track_id
|
84
|
+
+ "&seq=2",
|
85
|
+
"Sec-Fetch-Dest": "document",
|
86
|
+
"Sec-Fetch-Mode": "navigate",
|
87
|
+
"Sec-Fetch-Site": "same-origin",
|
88
|
+
"Sec-Fetch-User": "?1",
|
89
|
+
"Sec-GPC": "1",
|
90
|
+
"Upgrade-Insecure-Requests": "1",
|
91
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
|
92
|
+
"like Gecko) Chrome/106.0.0.0 Safari/537.36",
|
93
|
+
}
|
94
|
+
|
95
|
+
response = s.get(request_url, headers=headers)
|
96
|
+
soup = BeautifulSoup(response.content, features="html.parser")
|
97
|
+
soup.prettify()
|
98
|
+
|
99
|
+
# Find the list elements
|
100
|
+
u1_block = soup.find_all(
|
101
|
+
"u1",
|
102
|
+
{
|
103
|
+
"class": "displayinlineblock justifycontentleft alignitemscenter margin0 padding0"
|
104
|
+
},
|
105
|
+
)
|
106
|
+
|
107
|
+
for element in u1_block:
|
108
|
+
x = element.find_all_next(
|
109
|
+
"li", {"class": "displayinlineblock padding0px20px5px0px"}
|
110
|
+
)
|
111
|
+
dict_data = {
|
112
|
+
"type": x[2].text.strip(),
|
113
|
+
"collectionDate": datetime.strptime(
|
114
|
+
x[1].text.strip(), date_format
|
115
|
+
).strftime(date_format),
|
116
|
+
}
|
117
|
+
data["bins"].append(dict_data)
|
118
|
+
|
119
|
+
return data
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
10
|
+
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
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
user_uprn = kwargs.get("uprn")
|
19
|
+
check_uprn(user_uprn)
|
20
|
+
|
21
|
+
headers = {
|
22
|
+
"authority": "www.wealden.gov.uk",
|
23
|
+
"accept": "*/*",
|
24
|
+
"accept-language": "en-GB,en;q=0.7",
|
25
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
26
|
+
# Requests sorts cookies= alphabetically
|
27
|
+
# 'cookie': 'ARRAffinity=e45c20b343b490e3866d5d35c3dbda687e4a1357c2163c32922209862abb5872; ARRAffinitySameSite=e45c20b343b490e3866d5d35c3dbda687e4a1357c2163c32922209862abb5872',
|
28
|
+
"origin": "https://www.wealden.gov.uk",
|
29
|
+
"referer": "https://www.wealden.gov.uk/recycling-and-waste/bin-search/?uprn=10033413624",
|
30
|
+
"sec-fetch-dest": "empty",
|
31
|
+
"sec-fetch-mode": "cors",
|
32
|
+
"sec-fetch-site": "same-origin",
|
33
|
+
"sec-gpc": "1",
|
34
|
+
"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",
|
35
|
+
"x-requested-with": "XMLHttpRequest",
|
36
|
+
}
|
37
|
+
|
38
|
+
data = {
|
39
|
+
"action": "wealden_get_collections_for_uprn",
|
40
|
+
"uprn": user_uprn,
|
41
|
+
}
|
42
|
+
|
43
|
+
requests.packages.urllib3.disable_warnings()
|
44
|
+
response = requests.post(
|
45
|
+
"https://www.wealden.gov.uk/wp-admin/admin-ajax.php",
|
46
|
+
headers=headers,
|
47
|
+
data=data,
|
48
|
+
)
|
49
|
+
json_data = json.loads(response.text)
|
50
|
+
|
51
|
+
if json_data["status"] != "success":
|
52
|
+
raise ValueError("Error parsing data. Please open an issue on GitHub.")
|
53
|
+
|
54
|
+
property_data = json_data["collection"]
|
55
|
+
data = {"bins": []}
|
56
|
+
collections = []
|
57
|
+
|
58
|
+
if len(property_data["refuseCollectionDate"]) > 0:
|
59
|
+
bin_type = "Rubbish Bin"
|
60
|
+
bin_date = datetime.strptime(
|
61
|
+
property_data["refuseCollectionDate"], "%Y-%m-%dT%H:%M:%S"
|
62
|
+
)
|
63
|
+
collections.append((bin_type, bin_date))
|
64
|
+
if len(property_data["recyclingCollectionDate"]) > 0:
|
65
|
+
bin_type = "Recycling Bin"
|
66
|
+
bin_date = datetime.strptime(
|
67
|
+
property_data["recyclingCollectionDate"], "%Y-%m-%dT%H:%M:%S"
|
68
|
+
)
|
69
|
+
collections.append((bin_type, bin_date))
|
70
|
+
if len(property_data["gardenCollectionDate"]) > 0:
|
71
|
+
bin_type = "Garden Bin"
|
72
|
+
bin_date = datetime.strptime(
|
73
|
+
property_data["gardenCollectionDate"], "%Y-%m-%dT%H:%M:%S"
|
74
|
+
)
|
75
|
+
collections.append((bin_type, bin_date))
|
76
|
+
|
77
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
78
|
+
data = {"bins": []}
|
79
|
+
for item in ordered_data:
|
80
|
+
dict_data = {
|
81
|
+
"type": item[0],
|
82
|
+
"collectionDate": item[1].strftime(date_format),
|
83
|
+
}
|
84
|
+
data["bins"].append(dict_data)
|
85
|
+
|
86
|
+
return data
|
@@ -0,0 +1,73 @@
|
|
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
|
+
def get_token(page) -> str:
|
7
|
+
"""
|
8
|
+
Get a __token to include in the form data
|
9
|
+
:param page: Page html
|
10
|
+
:return: Form __token
|
11
|
+
"""
|
12
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
13
|
+
soup.prettify()
|
14
|
+
token = soup.find("input", {"name": "__token"}).get("value")
|
15
|
+
return token
|
16
|
+
|
17
|
+
|
18
|
+
class CouncilClass(AbstractGetBinDataClass):
|
19
|
+
"""
|
20
|
+
Concrete classes have to implement all abstract operations of the
|
21
|
+
base class. They can also override some operations with a default
|
22
|
+
implementation.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
26
|
+
uprn = kwargs.get("uprn")
|
27
|
+
postcode = kwargs.get("postcode")
|
28
|
+
check_uprn(uprn)
|
29
|
+
check_postcode(postcode)
|
30
|
+
|
31
|
+
values = {
|
32
|
+
"__token": get_token(page),
|
33
|
+
"page": "492",
|
34
|
+
"locale": "en_GB",
|
35
|
+
"q9f451fe0ca70775687eeedd1e54b359e55f7c10c_0_0": postcode,
|
36
|
+
"q9f451fe0ca70775687eeedd1e54b359e55f7c10c_1_0": uprn,
|
37
|
+
"next": "Next",
|
38
|
+
}
|
39
|
+
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
|
40
|
+
requests.packages.urllib3.disable_warnings()
|
41
|
+
response = requests.request(
|
42
|
+
"POST",
|
43
|
+
"https://www.welhat.gov.uk/xfp/form/214",
|
44
|
+
headers=headers,
|
45
|
+
data=values,
|
46
|
+
)
|
47
|
+
|
48
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
49
|
+
|
50
|
+
rows = soup.find("table").find_all("tr")
|
51
|
+
|
52
|
+
# Form a JSON wrapper
|
53
|
+
data = {"bins": []}
|
54
|
+
|
55
|
+
# Loops the Rows
|
56
|
+
for row in rows:
|
57
|
+
cells = row.find_all("td")
|
58
|
+
if cells:
|
59
|
+
binType = cells[0].get_text(strip=True)
|
60
|
+
collectionDate = datetime.strptime(
|
61
|
+
cells[1].get_text(strip=True), "%A %d %B %Y"
|
62
|
+
).strftime(date_format)
|
63
|
+
|
64
|
+
# Make each Bin element in the JSON
|
65
|
+
dict_data = {
|
66
|
+
"type": binType,
|
67
|
+
"collectionDate": collectionDate,
|
68
|
+
}
|
69
|
+
|
70
|
+
# Add data to the main JSON Wrapper
|
71
|
+
data["bins"].append(dict_data)
|
72
|
+
|
73
|
+
return data
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from dateutil.relativedelta import relativedelta
|
5
|
+
from selenium.webdriver.common.by import By
|
6
|
+
from selenium.webdriver.support import expected_conditions as EC
|
7
|
+
from selenium.webdriver.support.ui import Select
|
8
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
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
|
+
class CouncilClass(AbstractGetBinDataClass):
|
16
|
+
"""
|
17
|
+
Concrete classes have to implement all abstract operations of the
|
18
|
+
base class. They can also override some operations with a default
|
19
|
+
implementation.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
23
|
+
driver = None
|
24
|
+
try:
|
25
|
+
data = {"bins": []}
|
26
|
+
collections = []
|
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
|
+
check_paon(user_paon)
|
32
|
+
check_postcode(user_postcode)
|
33
|
+
|
34
|
+
# Create Selenium webdriver
|
35
|
+
# driver = create_webdriver(web_driver, headless)
|
36
|
+
driver = webdriver.Chrome(
|
37
|
+
service=ChromeService(ChromeDriverManager().install())
|
38
|
+
)
|
39
|
+
driver.get("https://www.westberks.gov.uk/binday")
|
40
|
+
|
41
|
+
# Wait for the postcode field to appear then populate it
|
42
|
+
inputElement_postcode = WebDriverWait(driver, 30).until(
|
43
|
+
EC.presence_of_element_located(
|
44
|
+
(By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPPOSTCODE")
|
45
|
+
)
|
46
|
+
)
|
47
|
+
inputElement_postcode.send_keys(user_postcode)
|
48
|
+
|
49
|
+
# Click search button
|
50
|
+
findAddress = WebDriverWait(driver, 10).until(
|
51
|
+
EC.presence_of_element_located(
|
52
|
+
(By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPSEARCH")
|
53
|
+
)
|
54
|
+
)
|
55
|
+
findAddress.click()
|
56
|
+
|
57
|
+
WebDriverWait(driver, 10).until(
|
58
|
+
EC.element_to_be_clickable(
|
59
|
+
(
|
60
|
+
By.XPATH,
|
61
|
+
""
|
62
|
+
"//*[@id='FINDYOURBINDAYS_ADDRESSLOOKUPADDRESS']//option[contains(., '"
|
63
|
+
+ user_paon
|
64
|
+
+ "')]",
|
65
|
+
)
|
66
|
+
)
|
67
|
+
).click()
|
68
|
+
|
69
|
+
# Wait for the submit button to appear, then click it to get the collection dates
|
70
|
+
WebDriverWait(driver, 30).until(
|
71
|
+
EC.presence_of_element_located(
|
72
|
+
(By.XPATH, '//*[@id="FINDYOURBINDAYS_RUBBISHDATE"]/div')
|
73
|
+
)
|
74
|
+
)
|
75
|
+
time.sleep(2)
|
76
|
+
|
77
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
78
|
+
soup.prettify()
|
79
|
+
|
80
|
+
rubbish_date = datetime.strptime(
|
81
|
+
" ".join(
|
82
|
+
soup.find("div", {"id": "FINDYOURBINDAYS_RUBBISHDATE_OUTERDIV"})
|
83
|
+
.get_text(strip=True)
|
84
|
+
.split()[6:8]
|
85
|
+
),
|
86
|
+
"%d %B",
|
87
|
+
).replace(year=datetime.now().year)
|
88
|
+
recycling_date = datetime.strptime(
|
89
|
+
" ".join(
|
90
|
+
soup.find("div", {"id": "FINDYOURBINDAYS_RECYCLINGDATE_OUTERDIV"})
|
91
|
+
.get_text(strip=True)
|
92
|
+
.split()[6:8]
|
93
|
+
),
|
94
|
+
"%d %B",
|
95
|
+
).replace(year=datetime.now().year)
|
96
|
+
food_date = datetime.strptime(
|
97
|
+
" ".join(
|
98
|
+
soup.find("div", {"id": "FINDYOURBINDAYS_FOODWASTEDATE_OUTERDIV"})
|
99
|
+
.get_text(strip=True)
|
100
|
+
.split()[8:10]
|
101
|
+
),
|
102
|
+
"%d %B",
|
103
|
+
).replace(year=datetime.now().year)
|
104
|
+
|
105
|
+
if datetime.now().month == 12 and rubbish_date.month == 1:
|
106
|
+
rubbish_date = rubbish_date + relativedelta(years=1)
|
107
|
+
if datetime.now().month == 12 and recycling_date.month == 1:
|
108
|
+
recycling_date = recycling_date + relativedelta(years=1)
|
109
|
+
if datetime.now().month == 12 and food_date.month == 1:
|
110
|
+
food_date = food_date + relativedelta(years=1)
|
111
|
+
|
112
|
+
collections.append(("Rubbish bin", rubbish_date))
|
113
|
+
collections.append(("Recycling bin", recycling_date))
|
114
|
+
collections.append(("Food waste bin", food_date))
|
115
|
+
|
116
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
117
|
+
for item in ordered_data:
|
118
|
+
dict_data = {
|
119
|
+
"type": item[0].capitalize(),
|
120
|
+
"collectionDate": item[1].strftime(date_format),
|
121
|
+
}
|
122
|
+
data["bins"].append(dict_data)
|
123
|
+
|
124
|
+
print()
|
125
|
+
except Exception as e:
|
126
|
+
# Here you can log the exception if needed
|
127
|
+
print(f"An error occurred: {e}")
|
128
|
+
# Optionally, re-raise the exception if you want it to propagate
|
129
|
+
raise
|
130
|
+
finally:
|
131
|
+
# This block ensures that the driver is closed regardless of an exception
|
132
|
+
if driver:
|
133
|
+
driver.quit()
|
134
|
+
return data
|