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,108 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from selenium.webdriver.common.by import By
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
4
|
+
from selenium.webdriver.support.ui import Select
|
5
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
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
|
15
|
+
base 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
|
+
page = "https://forms.north-norfolk.gov.uk/outreach/BinCollectionDays.ofml"
|
23
|
+
|
24
|
+
data = {"bins": []}
|
25
|
+
|
26
|
+
user_paon = kwargs.get("paon")
|
27
|
+
user_postcode = kwargs.get("postcode")
|
28
|
+
web_driver = kwargs.get("web_driver")
|
29
|
+
headless = kwargs.get("headless")
|
30
|
+
check_paon(user_paon)
|
31
|
+
check_postcode(user_postcode)
|
32
|
+
|
33
|
+
# Create Selenium webdriver
|
34
|
+
driver = create_webdriver(web_driver, headless)
|
35
|
+
driver.get(page)
|
36
|
+
|
37
|
+
# Populate postcode field
|
38
|
+
inputElement_postcode = driver.find_element(
|
39
|
+
By.ID,
|
40
|
+
"F_Address_subform:Postcode",
|
41
|
+
)
|
42
|
+
inputElement_postcode.send_keys(user_postcode)
|
43
|
+
|
44
|
+
# Click search button
|
45
|
+
driver.find_element(
|
46
|
+
By.ID,
|
47
|
+
"BA_Address_subform:Search_button",
|
48
|
+
).click()
|
49
|
+
|
50
|
+
# Wait for the 'Select address' dropdown to appear
|
51
|
+
dropdown = WebDriverWait(driver, 10).until(
|
52
|
+
EC.presence_of_element_located(
|
53
|
+
(By.XPATH, "//select[@id='F_Address_subform:Id']")
|
54
|
+
)
|
55
|
+
)
|
56
|
+
# Create a 'Select' for it, then select the matching house number/name option
|
57
|
+
dropdownSelect = Select(dropdown)
|
58
|
+
matchingOptions = [
|
59
|
+
o for o in dropdownSelect.options if user_paon.lower() in o.text.lower()
|
60
|
+
]
|
61
|
+
if matchingOptions:
|
62
|
+
matchingOptions[0].click()
|
63
|
+
|
64
|
+
# Wait for the results to appear
|
65
|
+
WebDriverWait(driver, 10).until(
|
66
|
+
EC.presence_of_element_located(
|
67
|
+
(By.XPATH, "//div[contains(@class, 'fieldmergedcolumn')]/ul")
|
68
|
+
)
|
69
|
+
)
|
70
|
+
|
71
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
72
|
+
|
73
|
+
bins_text = soup.find("div", id="Search_result_details_cps_hd")
|
74
|
+
|
75
|
+
if bins_text:
|
76
|
+
results = re.findall(
|
77
|
+
"Your next (.*?) Bin collection is ([A-Za-z]+ \\d\\d? [A-Za-z]+)",
|
78
|
+
bins_text.get_text(),
|
79
|
+
)
|
80
|
+
if results:
|
81
|
+
for result in results:
|
82
|
+
collection_date = datetime.strptime(
|
83
|
+
result[1] + " " + datetime.now().strftime("%Y"),
|
84
|
+
"%A %d %B %Y",
|
85
|
+
)
|
86
|
+
dict_data = {
|
87
|
+
"type": result[0],
|
88
|
+
"collectionDate": collection_date.strftime(date_format),
|
89
|
+
}
|
90
|
+
data["bins"].append(dict_data)
|
91
|
+
|
92
|
+
data["bins"].sort(
|
93
|
+
key=lambda x: datetime.strptime(
|
94
|
+
x.get("collectionDate"), date_format
|
95
|
+
)
|
96
|
+
)
|
97
|
+
else:
|
98
|
+
raise ValueError("No matching address for house number/name found.")
|
99
|
+
except Exception as e:
|
100
|
+
# Here you can log the exception if needed
|
101
|
+
print(f"An error occurred: {e}")
|
102
|
+
# Optionally, re-raise the exception if you want it to propagate
|
103
|
+
raise
|
104
|
+
finally:
|
105
|
+
# This block ensures that the driver is closed regardless of an exception
|
106
|
+
if driver:
|
107
|
+
driver.quit()
|
108
|
+
return data
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import hashlib
|
2
|
+
import math
|
3
|
+
import time
|
4
|
+
from datetime import datetime as dtm, timedelta
|
5
|
+
|
6
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
7
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
8
|
+
|
9
|
+
|
10
|
+
def myFunc(e):
|
11
|
+
return e["start"]
|
12
|
+
|
13
|
+
|
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
|
+
data = {"bins": []}
|
23
|
+
uprn = kwargs.get("uprn")
|
24
|
+
check_uprn(uprn)
|
25
|
+
today = int(datetime.now().timestamp()) * 1000
|
26
|
+
dateforurl = datetime.now().strftime("%Y-%m-%d")
|
27
|
+
dateforurl2 = (datetime.now() + timedelta(days=42)).strftime("%Y-%m-%d")
|
28
|
+
headers = {
|
29
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)",
|
30
|
+
}
|
31
|
+
requests.packages.urllib3.disable_warnings()
|
32
|
+
|
33
|
+
# Get variables for workings
|
34
|
+
response = requests.get(
|
35
|
+
f"https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/{uprn}/{dateforurl}/{dateforurl2}",
|
36
|
+
headers=headers,
|
37
|
+
)
|
38
|
+
if response.status_code != 200:
|
39
|
+
raise ValueError("No bin data found for provided UPRN..")
|
40
|
+
|
41
|
+
json_response = json.loads(response.text)
|
42
|
+
|
43
|
+
output_dict = [
|
44
|
+
x
|
45
|
+
for x in json_response
|
46
|
+
if int("".join(filter(str.isdigit, x["start"]))) >= today
|
47
|
+
]
|
48
|
+
|
49
|
+
output_json = output_dict
|
50
|
+
output_json.sort(key=myFunc)
|
51
|
+
|
52
|
+
i = 0
|
53
|
+
while i < len(output_json):
|
54
|
+
sov = output_json[i]["title"].lower()
|
55
|
+
if "recycling" in sov:
|
56
|
+
bin_type = "Recycling"
|
57
|
+
elif "garden" in sov:
|
58
|
+
bin_type = "Garden"
|
59
|
+
elif "refuse" in sov:
|
60
|
+
bin_type = "General"
|
61
|
+
else:
|
62
|
+
bin_type = "Unknown"
|
63
|
+
dateofbin = int("".join(filter(str.isdigit, output_json[i]["start"])))
|
64
|
+
day = dtm.fromtimestamp(dateofbin / 1000)
|
65
|
+
collection_data = {
|
66
|
+
"type": bin_type,
|
67
|
+
"collectionDate": day.strftime(date_format),
|
68
|
+
}
|
69
|
+
data["bins"].append(collection_data)
|
70
|
+
i += 1
|
71
|
+
|
72
|
+
return data
|
@@ -0,0 +1,76 @@
|
|
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
|
+
class CouncilClass(AbstractGetBinDataClass):
|
7
|
+
"""
|
8
|
+
Concrete classes have to implement all abstract operations of the
|
9
|
+
base class. They can also override some operations with a default
|
10
|
+
implementation.
|
11
|
+
"""
|
12
|
+
|
13
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
14
|
+
api_url = "https://forms.n-somerset.gov.uk/Waste/CollectionSchedule"
|
15
|
+
uprn = kwargs.get("uprn")
|
16
|
+
postcode = kwargs.get("postcode")
|
17
|
+
check_uprn(uprn)
|
18
|
+
check_postcode(postcode)
|
19
|
+
|
20
|
+
# Get schedule from API
|
21
|
+
values = {
|
22
|
+
"PreviousHouse": "",
|
23
|
+
"PreviousPostcode": postcode,
|
24
|
+
"Postcode": postcode,
|
25
|
+
"SelectedUprn": uprn,
|
26
|
+
}
|
27
|
+
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
|
28
|
+
requests.packages.urllib3.disable_warnings()
|
29
|
+
response = requests.request("POST", api_url, headers=headers, data=values)
|
30
|
+
|
31
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
32
|
+
|
33
|
+
rows = soup.find("table", {"class": re.compile("table")}).find_all("tr")
|
34
|
+
|
35
|
+
# Form a JSON wrapper
|
36
|
+
data = {"bins": []}
|
37
|
+
|
38
|
+
# Loops the Rows
|
39
|
+
for row in rows:
|
40
|
+
cells = row.find_all("td")
|
41
|
+
if cells:
|
42
|
+
binType = cells[0].get_text(strip=True)
|
43
|
+
collectionDate = (
|
44
|
+
cells[1].get_text(strip=True) + " " + datetime.now().strftime("%Y")
|
45
|
+
)
|
46
|
+
nextCollectionDate = (
|
47
|
+
cells[2].get_text(strip=True) + " " + datetime.now().strftime("%Y")
|
48
|
+
)
|
49
|
+
|
50
|
+
# Make each Bin element in the JSON
|
51
|
+
dict_data = {
|
52
|
+
"type": binType,
|
53
|
+
"collectionDate": get_next_occurrence_from_day_month(
|
54
|
+
datetime.strptime(collectionDate, "%A %d %B %Y")
|
55
|
+
).strftime(date_format),
|
56
|
+
}
|
57
|
+
|
58
|
+
# Add data to the main JSON Wrapper
|
59
|
+
data["bins"].append(dict_data)
|
60
|
+
|
61
|
+
# Make each next Bin element in the JSON
|
62
|
+
dict_data = {
|
63
|
+
"type": binType,
|
64
|
+
"collectionDate": get_next_occurrence_from_day_month(
|
65
|
+
datetime.strptime(nextCollectionDate, "%A %d %B %Y")
|
66
|
+
).strftime(date_format),
|
67
|
+
}
|
68
|
+
|
69
|
+
# Add data to the main JSON Wrapper
|
70
|
+
data["bins"].append(dict_data)
|
71
|
+
|
72
|
+
data["bins"].sort(
|
73
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
74
|
+
)
|
75
|
+
|
76
|
+
return data
|
@@ -0,0 +1,220 @@
|
|
1
|
+
import math
|
2
|
+
from datetime import *
|
3
|
+
|
4
|
+
import requests
|
5
|
+
from bs4 import BeautifulSoup
|
6
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
7
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
8
|
+
|
9
|
+
|
10
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
11
|
+
class CouncilClass(AbstractGetBinDataClass):
|
12
|
+
"""
|
13
|
+
Concrete classes have to implement all abstract operations of the
|
14
|
+
base class. They can also override some operations with a default
|
15
|
+
implementation.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
19
|
+
requests.packages.urllib3.disable_warnings()
|
20
|
+
data = {"bins": []}
|
21
|
+
user_uprn = kwargs.get("uprn")
|
22
|
+
user_postcode = kwargs.get("postcode")
|
23
|
+
check_uprn(user_uprn)
|
24
|
+
check_postcode(user_postcode)
|
25
|
+
|
26
|
+
# Get form data
|
27
|
+
s = requests.session()
|
28
|
+
cookies = {
|
29
|
+
"ntc-cookie-policy": "1",
|
30
|
+
"SSESS6ec6d5d2d471c0357053d5993a839bce": "qBdR7XhmSMd5_PDBIqG0It2R0Fq67igrejRY-WOcskE",
|
31
|
+
"has_js": "1",
|
32
|
+
}
|
33
|
+
headers = {
|
34
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
35
|
+
"Accept-Language": "en-GB,en;q=0.7",
|
36
|
+
"Cache-Control": "max-age=0",
|
37
|
+
"Connection": "keep-alive",
|
38
|
+
"Origin": "https://my.northtyneside.gov.uk",
|
39
|
+
"Referer": "https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
|
40
|
+
"Sec-Fetch-Dest": "document",
|
41
|
+
"Sec-Fetch-Mode": "navigate",
|
42
|
+
"Sec-Fetch-Site": "same-origin",
|
43
|
+
"Sec-Fetch-User": "?1",
|
44
|
+
"Sec-GPC": "1",
|
45
|
+
"Upgrade-Insecure-Requests": "1",
|
46
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
|
47
|
+
"sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Brave";v="108"',
|
48
|
+
"sec-ch-ua-mobile": "?0",
|
49
|
+
"sec-ch-ua-platform": '"Windows"',
|
50
|
+
}
|
51
|
+
ajax_data = {
|
52
|
+
"postcode": user_postcode,
|
53
|
+
"form_build_id": "form-BQ47tM0NKADE0s8toYkdSef3QBn6lDM-yBseqIOho80",
|
54
|
+
"form_id": "ntc_address_wizard",
|
55
|
+
"_triggering_element_name": "op",
|
56
|
+
"_triggering_element_value": "Find",
|
57
|
+
"ajax_html_ids[]": [
|
58
|
+
"ntc-web-my",
|
59
|
+
"skip-link",
|
60
|
+
"navbar",
|
61
|
+
"navbar-collapse",
|
62
|
+
"search-block-form",
|
63
|
+
"ntc-web-search-input-label",
|
64
|
+
"ntc-web-search-input",
|
65
|
+
"ui-id-1",
|
66
|
+
"ntc-web-main",
|
67
|
+
"main-content",
|
68
|
+
"block-system-main",
|
69
|
+
"web-drupal-content",
|
70
|
+
"web-drupal-content-main",
|
71
|
+
"node-4024",
|
72
|
+
"block-ntc-address-ntc-address-finder",
|
73
|
+
"wizard-form-wrapper",
|
74
|
+
"ntc-address-wizard",
|
75
|
+
"edit-postcode",
|
76
|
+
"edit-find",
|
77
|
+
"backtotop",
|
78
|
+
],
|
79
|
+
"ajax_page_state[theme]": "ntc_bootstrap",
|
80
|
+
"ajax_page_state[theme_token]": "LN05JIzI6rocWDiBpDyVeywYveuS4jlxD_N0_hhp2Ko",
|
81
|
+
"ajax_page_state[css][0]": "1",
|
82
|
+
"ajax_page_state[css][modules/system/system.base.css]": "1",
|
83
|
+
"ajax_page_state[css][misc/ui/jquery.ui.core.css]": "1",
|
84
|
+
"ajax_page_state[css][misc/ui/jquery.ui.theme.css]": "1",
|
85
|
+
"ajax_page_state[css][misc/ui/jquery.ui.menu.css]": "1",
|
86
|
+
"ajax_page_state[css][misc/ui/jquery.ui.autocomplete.css]": "1",
|
87
|
+
"ajax_page_state[css][sites/all/modules/calendar/css/calendar_multiday.css]": "1",
|
88
|
+
"ajax_page_state[css][sites/all/modules/date/date_repeat_field/date_repeat_field.css]": "1",
|
89
|
+
"ajax_page_state[css][modules/field/theme/field.css]": "1",
|
90
|
+
"ajax_page_state[css][modules/node/node.css]": "1",
|
91
|
+
"ajax_page_state[css][sites/all/modules/youtube/css/youtube.css]": "1",
|
92
|
+
"ajax_page_state[css][sites/all/modules/views/css/views.css]": "1",
|
93
|
+
"ajax_page_state[css][sites/all/modules/back_to_top/css/back_to_top.css]": "1",
|
94
|
+
"ajax_page_state[css][sites/all/modules/ckeditor/css/ckeditor.css]": "1",
|
95
|
+
"ajax_page_state[css][sites/all/modules/ctools/css/ctools.css]": "1",
|
96
|
+
"ajax_page_state[css][sites/all/modules/panels/css/panels.css]": "1",
|
97
|
+
"ajax_page_state[css][sites/all/modules/taxonomy_access/taxonomy_access.css]": "1",
|
98
|
+
"ajax_page_state[css][sites/all/modules/search_autocomplete/css/themes/minimal.css]": "1",
|
99
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/bootstrap.css]": "1",
|
100
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/generic.css]": "1",
|
101
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/custom.css]": "1",
|
102
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/components.css]": "1",
|
103
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/modules.css]": "1",
|
104
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/fostering.css]": "1",
|
105
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/responsive.css]": "1",
|
106
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/ie10.css]": "1",
|
107
|
+
"ajax_page_state[css][sites/all/themes/ntc_bootstrap/css/ie.css]": "1",
|
108
|
+
"ajax_page_state[js][0]": "1",
|
109
|
+
"ajax_page_state[js][1]": "1",
|
110
|
+
"ajax_page_state[js][sites/all/themes/bootstrap/js/bootstrap.js]": "1",
|
111
|
+
"ajax_page_state[js][//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js]": "1",
|
112
|
+
"ajax_page_state[js][misc/jquery-extend-3.4.0.js]": "1",
|
113
|
+
"ajax_page_state[js][misc/jquery-html-prefilter-3.5.0-backport.js]": "1",
|
114
|
+
"ajax_page_state[js][misc/jquery.once.js]": "1",
|
115
|
+
"ajax_page_state[js][misc/drupal.js]": "1",
|
116
|
+
"ajax_page_state[js][//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js]": "1",
|
117
|
+
"ajax_page_state[js][sites/all/modules/jquery_update/replace/ui/external/jquery.cookie.js]": "1",
|
118
|
+
"ajax_page_state[js][sites/all/modules/jquery_update/replace/misc/jquery.form.min.js]": "1",
|
119
|
+
"ajax_page_state[js][misc/ajax.js]": "1",
|
120
|
+
"ajax_page_state[js][sites/all/modules/jquery_update/js/jquery_update.js]": "1",
|
121
|
+
"ajax_page_state[js][sites/all/modules/back_to_top/js/back_to_top.js]": "1",
|
122
|
+
"ajax_page_state[js][sites/all/themes/bootstrap/js/misc/_progress.js]": "1",
|
123
|
+
"ajax_page_state[js][sites/all/modules/field_group/field_group.js]": "1",
|
124
|
+
"ajax_page_state[js][sites/all/modules/search_autocomplete/js/jquery.autocomplete.js]": "1",
|
125
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.contentMenuScroller.js]": "1",
|
126
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.alertClose.js]": "1",
|
127
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.activeTrail.js]": "1",
|
128
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.expandLinkToDiv.js]": "1",
|
129
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/NTC.jquery.events.js]": "1",
|
130
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/cookieconsent.js]": "1",
|
131
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/google-analytics.js]": "1",
|
132
|
+
"ajax_page_state[js][sites/all/themes/ntc_bootstrap/scripts/ios-orientationchange-fix.js]": "1",
|
133
|
+
"ajax_page_state[js][sites/all/themes/bootstrap/js/misc/ajax.js]": "1",
|
134
|
+
"ajax_page_state[jquery_version]": "1.10",
|
135
|
+
}
|
136
|
+
uprn_data = {
|
137
|
+
"house_number": "0000" + f"{user_uprn}",
|
138
|
+
"op": "Use",
|
139
|
+
"form_build_id": "form-BQ47tM0NKADE0s8toYkdSef3QBn6lDM-yBseqIOho80",
|
140
|
+
"form_id": "ntc_address_wizard",
|
141
|
+
}
|
142
|
+
collections = []
|
143
|
+
|
144
|
+
response = s.post(
|
145
|
+
"https://my.northtyneside.gov.uk/system/ajax",
|
146
|
+
# cookies=cookies,
|
147
|
+
headers=headers,
|
148
|
+
data=ajax_data,
|
149
|
+
verify=False,
|
150
|
+
)
|
151
|
+
response = s.post(
|
152
|
+
"https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
|
153
|
+
# cookies=cookies,
|
154
|
+
headers=headers,
|
155
|
+
data=uprn_data,
|
156
|
+
verify=False,
|
157
|
+
)
|
158
|
+
response = s.get(
|
159
|
+
"https://my.northtyneside.gov.uk/category/81/bin-collection-dates",
|
160
|
+
# cookies=cookies,
|
161
|
+
headers=headers,
|
162
|
+
data=uprn_data,
|
163
|
+
verify=False,
|
164
|
+
)
|
165
|
+
|
166
|
+
# Parse form page and get the day of week text
|
167
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
168
|
+
soup.prettify()
|
169
|
+
bin_text = soup.find("section", {"class": "block block-ntc-bins clearfix"})
|
170
|
+
regular_text = bin_text.select("p:nth-child(2) > strong")[0].text.strip()
|
171
|
+
x = bin_text.select("p:nth-child(4) > strong")
|
172
|
+
if len(bin_text.select("p:nth-child(4) > strong")) == 1:
|
173
|
+
special_text = bin_text.select("p:nth-child(4) > strong")[0].text.strip()
|
174
|
+
else:
|
175
|
+
special_text = bin_text.select("p:nth-child(5) > strong")[0].text.strip()
|
176
|
+
|
177
|
+
# Since calendar only shows until end of March 2024, work out how many weeks that is
|
178
|
+
weeks_total = math.floor((datetime(2024, 4, 1) - datetime.now()).days / 7)
|
179
|
+
|
180
|
+
# Convert day text to series of dates using previous calculation
|
181
|
+
regular_collections = get_weekday_dates_in_period(
|
182
|
+
datetime.today(),
|
183
|
+
days_of_week.get(regular_text.capitalize()),
|
184
|
+
amount=weeks_total,
|
185
|
+
)
|
186
|
+
special_collections = get_weekday_dates_in_period(
|
187
|
+
datetime.today(), days_of_week.get(special_text.capitalize())
|
188
|
+
)
|
189
|
+
|
190
|
+
# Differentiate between regular and recycling bins
|
191
|
+
for item in regular_collections:
|
192
|
+
item_as_date = datetime.strptime(item, date_format)
|
193
|
+
# Check if holiday (calendar only has one day that's a holiday, and it's moved to the next day)
|
194
|
+
if is_holiday(item_as_date, Region.ENG):
|
195
|
+
item_as_date += timedelta(days=1)
|
196
|
+
# Use the isoweek number to separate collections - at the time of writing 11th Jan is week 2, which
|
197
|
+
# is for the grey bin
|
198
|
+
if (item_as_date.date().isocalendar()[1] % 2) == 0:
|
199
|
+
collections.append(("Regular bin (green)", item_as_date))
|
200
|
+
|
201
|
+
else:
|
202
|
+
collections.append(("Recycling bin (grey)", item_as_date))
|
203
|
+
|
204
|
+
# Add the special collection dates to the collection tuple
|
205
|
+
collections += [
|
206
|
+
("Special collection (bookable)", datetime.strptime(item, date_format))
|
207
|
+
for item in special_collections
|
208
|
+
]
|
209
|
+
|
210
|
+
# Sort the collections tuple by date, the add to dictionary and return
|
211
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
212
|
+
data = {"bins": []}
|
213
|
+
for item in ordered_data:
|
214
|
+
dict_data = {
|
215
|
+
"type": item[0],
|
216
|
+
"collectionDate": item[1].strftime(date_format),
|
217
|
+
}
|
218
|
+
data["bins"].append(dict_data)
|
219
|
+
|
220
|
+
return data
|
@@ -0,0 +1,114 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from datetime import datetime, timedelta
|
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
|
+
import re # Import regular expressions
|
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
|
+
|
15
|
+
|
16
|
+
class CouncilClass(AbstractGetBinDataClass):
|
17
|
+
"""
|
18
|
+
Concrete classes have to implement all abstract operations of the
|
19
|
+
base class. They can also override some operations with a default
|
20
|
+
implementation.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
24
|
+
driver = None
|
25
|
+
try:
|
26
|
+
data = {"bins": []}
|
27
|
+
|
28
|
+
user_uprn = kwargs.get("uprn")
|
29
|
+
user_postcode = kwargs.get("postcode")
|
30
|
+
web_driver = kwargs.get("web_driver")
|
31
|
+
headless = kwargs.get("headless")
|
32
|
+
check_uprn(user_uprn)
|
33
|
+
check_postcode(user_postcode)
|
34
|
+
# Create Selenium webdriver
|
35
|
+
page = f"https://my.nwleics.gov.uk/my-property-finder?address={user_postcode}&go=1"
|
36
|
+
|
37
|
+
driver = create_webdriver(web_driver, headless)
|
38
|
+
driver.get(page)
|
39
|
+
|
40
|
+
# If you bang in the house number (or property name) and postcode in the box it should find your property
|
41
|
+
|
42
|
+
# iframe_presense = WebDriverWait(driver, 30).until(
|
43
|
+
# EC.presence_of_element_located((By.ID, "fillform-frame-1"))
|
44
|
+
# )
|
45
|
+
|
46
|
+
# driver.switch_to.frame(iframe_presense)
|
47
|
+
wait = WebDriverWait(driver, 60)
|
48
|
+
|
49
|
+
address_link = wait.until(
|
50
|
+
EC.element_to_be_clickable(
|
51
|
+
(By.XPATH, f'//a[contains(@href, "{user_uprn}")]')
|
52
|
+
)
|
53
|
+
)
|
54
|
+
|
55
|
+
address_link.click()
|
56
|
+
|
57
|
+
refuse_element = wait.until(
|
58
|
+
EC.presence_of_element_located(
|
59
|
+
(By.XPATH, f'//h3[contains(text(), "Refuse Collection Dates")]')
|
60
|
+
)
|
61
|
+
)
|
62
|
+
|
63
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
64
|
+
|
65
|
+
# Find the unordered list containing refuse collection details
|
66
|
+
refuse_list = soup.find("ul", class_="refuse")
|
67
|
+
|
68
|
+
current_year = datetime.now().year
|
69
|
+
|
70
|
+
if refuse_list:
|
71
|
+
# Iterate through list items within the unordered list
|
72
|
+
for li in refuse_list.find_all("li"):
|
73
|
+
date = li.find(
|
74
|
+
"strong", class_="date"
|
75
|
+
).text.strip() # Extract the date
|
76
|
+
waste_type = li.find("a").text.strip() # Extract the waste type
|
77
|
+
|
78
|
+
# Parse the date from the string
|
79
|
+
# check for today and tomorrow
|
80
|
+
if date.lower() == "today":
|
81
|
+
parsed_date = datetime.now().date()
|
82
|
+
elif date.lower() == "tomorrow":
|
83
|
+
parsed_date = (datetime.now() + timedelta(days=1)).date()
|
84
|
+
else:
|
85
|
+
date = re.sub(r"(st|nd|rd|th)", "", date)
|
86
|
+
parsed_date = datetime.strptime(date, "%a %d %b").date()
|
87
|
+
|
88
|
+
current_date = datetime.now().date()
|
89
|
+
|
90
|
+
# double check we've got a year and if not the current one
|
91
|
+
if parsed_date.year < current_date.year:
|
92
|
+
parsed_date = parsed_date.replace(year=current_date.year)
|
93
|
+
|
94
|
+
# check if the date is in the past and if so add a year
|
95
|
+
if parsed_date < current_date:
|
96
|
+
parsed_date = parsed_date.replace(year=current_date.year + 1)
|
97
|
+
|
98
|
+
# Append data to your 'bins' list (this replicates your existing logic)
|
99
|
+
data["bins"].append(
|
100
|
+
{
|
101
|
+
"type": waste_type,
|
102
|
+
"collectionDate": parsed_date.strftime("%d/%m/%Y"),
|
103
|
+
}
|
104
|
+
)
|
105
|
+
except Exception as e:
|
106
|
+
# Here you can log the exception if needed
|
107
|
+
print(f"An error occurred: {e}")
|
108
|
+
# Optionally, re-raise the exception if you want it to propagate
|
109
|
+
raise
|
110
|
+
finally:
|
111
|
+
# This block ensures that the driver is closed regardless of an exception
|
112
|
+
if driver:
|
113
|
+
driver.quit()
|
114
|
+
return data
|
@@ -0,0 +1,58 @@
|
|
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
|
+
class CouncilClass(AbstractGetBinDataClass):
|
7
|
+
"""
|
8
|
+
Concrete classes have to implement all abstract operations of the
|
9
|
+
base class. They can also override some operations with a default
|
10
|
+
implementation.
|
11
|
+
"""
|
12
|
+
|
13
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
14
|
+
uprn = kwargs.get("uprn")
|
15
|
+
check_uprn(uprn)
|
16
|
+
|
17
|
+
# Figure bin data URL from UPRN
|
18
|
+
url = "https://www.northyorks.gov.uk/bin-calendar/lookup"
|
19
|
+
|
20
|
+
payload = {
|
21
|
+
"selected_address": uprn,
|
22
|
+
"submit": "Continue",
|
23
|
+
"form_id": "bin_calendar_lookup_form",
|
24
|
+
}
|
25
|
+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
26
|
+
|
27
|
+
# This endpoint redirects to the data url.
|
28
|
+
response = requests.request("POST", url, headers=headers, data=payload)
|
29
|
+
bin_data_url = f"{response.url}/ajax"
|
30
|
+
|
31
|
+
# Get bin data
|
32
|
+
response = requests.request("GET", bin_data_url)
|
33
|
+
bin_data = response.json()
|
34
|
+
|
35
|
+
# Parse bin data
|
36
|
+
soup = BeautifulSoup(bin_data[1]["data"], "html.parser")
|
37
|
+
|
38
|
+
# All collection info is in the table
|
39
|
+
table = (
|
40
|
+
soup.find("div", {"id": "upcoming-collection"}).find("table").find("tbody")
|
41
|
+
)
|
42
|
+
rows = table.find_all("tr")
|
43
|
+
|
44
|
+
data = {"bins": []}
|
45
|
+
for row in rows:
|
46
|
+
cols = row.find_all("td")
|
47
|
+
# First column is date
|
48
|
+
bin_date = datetime.strptime(cols[0].text.strip(), "%d %B %Y")
|
49
|
+
# Third column is type
|
50
|
+
bin_type = cols[2].text.strip()
|
51
|
+
# This bin
|
52
|
+
this_bin = {
|
53
|
+
"type": bin_type,
|
54
|
+
"collectionDate": bin_date.strftime(date_format),
|
55
|
+
}
|
56
|
+
data["bins"].append(this_bin)
|
57
|
+
|
58
|
+
return data
|