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,180 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from selenium.webdriver.common.by import By
|
5
|
+
from selenium.webdriver.support import expected_conditions as EC
|
6
|
+
from selenium.webdriver.support.ui import Select
|
7
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
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
|
+
def get_seasonal_overrides():
|
14
|
+
url = "https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day"
|
15
|
+
response = requests.get(url)
|
16
|
+
if response.status_code == 200:
|
17
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
18
|
+
body_div = soup.find("div", class_="field--name-body")
|
19
|
+
ul_element = body_div.find("ul")
|
20
|
+
if ul_element:
|
21
|
+
li_elements = ul_element.find_all("li")
|
22
|
+
overrides_dict = {}
|
23
|
+
for li_element in li_elements:
|
24
|
+
li_text = li_element.text.strip()
|
25
|
+
li_text = re.sub(r"\([^)]*\)", "", li_text).strip()
|
26
|
+
if "Collections for" in li_text and "will be revised to" in li_text:
|
27
|
+
parts = li_text.split("will be revised to")
|
28
|
+
original_date = (
|
29
|
+
parts[0]
|
30
|
+
.replace("Collections for", "")
|
31
|
+
.replace("\xa0", " ")
|
32
|
+
.strip()
|
33
|
+
)
|
34
|
+
revised_date = parts[1].strip()
|
35
|
+
|
36
|
+
# Extract day and month
|
37
|
+
date_parts = original_date.split()[1:]
|
38
|
+
if len(date_parts) == 2:
|
39
|
+
day, month = date_parts
|
40
|
+
# Ensure original_date has leading zeros for single-digit days
|
41
|
+
day = day.zfill(2)
|
42
|
+
original_date = f"{original_date.split()[0]} {day} {month}"
|
43
|
+
|
44
|
+
# Store the information in the dictionary
|
45
|
+
overrides_dict[original_date] = revised_date
|
46
|
+
return overrides_dict
|
47
|
+
else:
|
48
|
+
print("UL element not found within the specified div.")
|
49
|
+
else:
|
50
|
+
print(f"Failed to retrieve the page. Status code: {response.status_code}")
|
51
|
+
|
52
|
+
|
53
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
54
|
+
class CouncilClass(AbstractGetBinDataClass):
|
55
|
+
"""
|
56
|
+
Concrete classes have to implement all abstract operations of the
|
57
|
+
base class. They can also override some operations with a default
|
58
|
+
implementation.
|
59
|
+
"""
|
60
|
+
|
61
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
62
|
+
driver = None
|
63
|
+
try:
|
64
|
+
user_postcode = kwargs.get("postcode")
|
65
|
+
if not user_postcode:
|
66
|
+
raise ValueError("No postcode provided.")
|
67
|
+
check_postcode(user_postcode)
|
68
|
+
|
69
|
+
user_paon = kwargs.get("paon")
|
70
|
+
check_paon(user_paon)
|
71
|
+
headless = kwargs.get("headless")
|
72
|
+
web_driver = kwargs.get("web_driver")
|
73
|
+
driver = create_webdriver(web_driver, headless)
|
74
|
+
page = "https://account.barnet.gov.uk/Forms/Home/Redirector/Index/?id=6a2ac067-3322-46e5-96e4-16c0c214454a&mod=OA&casetype=BAR&formname=BNTCOLDATE"
|
75
|
+
driver.get(page)
|
76
|
+
|
77
|
+
time.sleep(5)
|
78
|
+
|
79
|
+
postcode_input = WebDriverWait(driver, 10).until(
|
80
|
+
EC.presence_of_element_located(
|
81
|
+
(By.CSS_SELECTOR, '[aria-label="Postcode"]')
|
82
|
+
)
|
83
|
+
)
|
84
|
+
|
85
|
+
postcode_input.send_keys(user_postcode)
|
86
|
+
|
87
|
+
find_address_button = WebDriverWait(driver, 10).until(
|
88
|
+
EC.presence_of_element_located(
|
89
|
+
(By.CSS_SELECTOR, '[value="Find address"]')
|
90
|
+
)
|
91
|
+
)
|
92
|
+
find_address_button.click()
|
93
|
+
|
94
|
+
time.sleep(15)
|
95
|
+
# Wait for address box to be visible
|
96
|
+
select_address_input = WebDriverWait(driver, 10).until(
|
97
|
+
EC.presence_of_element_located(
|
98
|
+
(
|
99
|
+
By.ID,
|
100
|
+
"MainContent_CUSTOM_FIELD_808562d4b07f437ea751317cabd19d9eeaf8742f49cb4f7fa9bef99405b859f2",
|
101
|
+
)
|
102
|
+
)
|
103
|
+
)
|
104
|
+
|
105
|
+
# Select address based
|
106
|
+
select = Select(select_address_input)
|
107
|
+
addr_label = f"{user_postcode}, {user_paon},"
|
108
|
+
for addr_option in select.options:
|
109
|
+
option_name = addr_option.accessible_name[0 : len(addr_label)]
|
110
|
+
if option_name == addr_label:
|
111
|
+
break
|
112
|
+
select.select_by_value(addr_option.text)
|
113
|
+
|
114
|
+
time.sleep(10)
|
115
|
+
# Wait for the specified div to be present
|
116
|
+
target_div_id = "MainContent_CUSTOM_FIELD_808562d4b07f437ea751317cabd19d9ed93a174c32b14f839b65f6abc42d8108_div"
|
117
|
+
target_div = WebDriverWait(driver, 10).until(
|
118
|
+
EC.presence_of_element_located((By.ID, target_div_id))
|
119
|
+
)
|
120
|
+
|
121
|
+
time.sleep(5)
|
122
|
+
soup = BeautifulSoup(driver.page_source, "html.parser")
|
123
|
+
|
124
|
+
# Find the div with the specified id
|
125
|
+
target_div = soup.find("div", {"id": target_div_id})
|
126
|
+
|
127
|
+
# Handle the additional table of info for xmas
|
128
|
+
try:
|
129
|
+
overrides_dict = get_seasonal_overrides()
|
130
|
+
except Exception as e:
|
131
|
+
overrides_dict = {}
|
132
|
+
|
133
|
+
# Check if the div is found
|
134
|
+
if target_div:
|
135
|
+
bin_data = {"bins": []}
|
136
|
+
|
137
|
+
for bin_div in target_div.find_all(
|
138
|
+
"div",
|
139
|
+
{"style": re.compile("background-color:.*; padding-left: 4px;")},
|
140
|
+
):
|
141
|
+
bin_type = bin_div.find("strong").text.strip()
|
142
|
+
collection_date_string = (
|
143
|
+
re.search(r"Next collection date:\s+(.*)", bin_div.text)
|
144
|
+
.group(1)
|
145
|
+
.strip()
|
146
|
+
.replace(",", "")
|
147
|
+
)
|
148
|
+
if collection_date_string in overrides_dict:
|
149
|
+
# Replace with the revised date from overrides_dict
|
150
|
+
collection_date_string = overrides_dict[collection_date_string]
|
151
|
+
|
152
|
+
current_date = datetime.now()
|
153
|
+
parsed_date = datetime.strptime(
|
154
|
+
collection_date_string + f" {current_date.year}", "%A %d %B %Y"
|
155
|
+
)
|
156
|
+
# Check if the parsed date is in the past and not today
|
157
|
+
if parsed_date.date() < current_date.date():
|
158
|
+
# If so, set the year to the next year
|
159
|
+
parsed_date = parsed_date.replace(year=current_date.year + 1)
|
160
|
+
else:
|
161
|
+
# If not, set the year to the current year
|
162
|
+
parsed_date = parsed_date.replace(year=current_date.year)
|
163
|
+
formatted_date = parsed_date.strftime("%d/%m/%Y")
|
164
|
+
|
165
|
+
contains_date(formatted_date)
|
166
|
+
bin_info = {"type": bin_type, "collectionDate": formatted_date}
|
167
|
+
bin_data["bins"].append(bin_info)
|
168
|
+
else:
|
169
|
+
raise ValueError("Collection data not found.")
|
170
|
+
|
171
|
+
except Exception as e:
|
172
|
+
# Here you can log the exception if needed
|
173
|
+
print(f"An error occurred: {e}")
|
174
|
+
# Optionally, re-raise the exception if you want it to propagate
|
175
|
+
raise
|
176
|
+
finally:
|
177
|
+
# This block ensures that the driver is closed regardless of an exception
|
178
|
+
if driver:
|
179
|
+
driver.quit()
|
180
|
+
return bin_data
|
@@ -0,0 +1,109 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from dateutil.relativedelta import relativedelta
|
4
|
+
import requests
|
5
|
+
from datetime import datetime
|
6
|
+
from uk_bin_collection.uk_bin_collection.common import (
|
7
|
+
check_postcode,
|
8
|
+
check_uprn,
|
9
|
+
date_format,
|
10
|
+
)
|
11
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
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: Any) -> Dict[str, Any]:
|
22
|
+
data: Dict[str, Any] = {"bins": []}
|
23
|
+
|
24
|
+
# Get UPRN and postcode from kwargs
|
25
|
+
user_uprn = str(kwargs.get("uprn"))
|
26
|
+
user_postcode = str(kwargs.get("postcode"))
|
27
|
+
check_postcode(user_postcode)
|
28
|
+
check_uprn(user_uprn)
|
29
|
+
|
30
|
+
# Pass in form data and make the POST request
|
31
|
+
headers = {
|
32
|
+
"authority": "waste.barnsley.gov.uk",
|
33
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
34
|
+
"accept-language": "en-GB,en;q=0.9",
|
35
|
+
"cache-control": "no-cache",
|
36
|
+
"content-type": "application/x-www-form-urlencoded",
|
37
|
+
"origin": "https://waste.barnsley.gov.uk",
|
38
|
+
"pragma": "no-cache",
|
39
|
+
"referer": "https://waste.barnsley.gov.uk/ViewCollection/SelectAddress",
|
40
|
+
"sec-ch-ua": '"Chromium";v="118", "Opera GX";v="104", "Not=A?Brand";v="99"',
|
41
|
+
"sec-ch-ua-mobile": "?0",
|
42
|
+
"sec-ch-ua-platform": '"Windows"',
|
43
|
+
"sec-fetch-dest": "document",
|
44
|
+
"sec-fetch-mode": "navigate",
|
45
|
+
"sec-fetch-site": "same-origin",
|
46
|
+
"sec-fetch-user": "?1",
|
47
|
+
"upgrade-insecure-requests": "1",
|
48
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.118 Safari/537.36",
|
49
|
+
}
|
50
|
+
form_data = {
|
51
|
+
"personInfo.person1.HouseNumberOrName": "",
|
52
|
+
"personInfo.person1.Postcode": f"{user_postcode}",
|
53
|
+
"personInfo.person1.UPRN": f"{user_uprn}",
|
54
|
+
"person1_SelectAddress": "Select address",
|
55
|
+
}
|
56
|
+
response = requests.post(
|
57
|
+
"https://waste.barnsley.gov.uk/ViewCollection/SelectAddress",
|
58
|
+
headers=headers,
|
59
|
+
data=form_data,
|
60
|
+
)
|
61
|
+
|
62
|
+
if response.status_code != 200:
|
63
|
+
raise ConnectionRefusedError(
|
64
|
+
"Error getting results from website! Please open an issue on GitHub!"
|
65
|
+
)
|
66
|
+
|
67
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
68
|
+
|
69
|
+
results = soup.find_all("fieldset")
|
70
|
+
|
71
|
+
# Next collection details
|
72
|
+
highlight_content = results[0].find("div", {"class": "highlight-content"})
|
73
|
+
bin_date_str = highlight_content.find(
|
74
|
+
"em", {"class": "ui-bin-next-date"}
|
75
|
+
).text.strip()
|
76
|
+
bin_type = (
|
77
|
+
highlight_content.find("p", {"class": "ui-bin-next-type"}).text.strip()
|
78
|
+
+ " bin"
|
79
|
+
)
|
80
|
+
|
81
|
+
if bin_date_str == "Today":
|
82
|
+
bin_date = datetime.today()
|
83
|
+
elif bin_date_str == "Tomorrow":
|
84
|
+
bin_date = datetime.today() + relativedelta(days=1)
|
85
|
+
else:
|
86
|
+
bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
|
87
|
+
|
88
|
+
dict_data = {
|
89
|
+
"type": bin_type,
|
90
|
+
"collectionDate": bin_date.strftime(date_format),
|
91
|
+
}
|
92
|
+
data["bins"].append(dict_data)
|
93
|
+
|
94
|
+
# Upcoming collections
|
95
|
+
upcoming_collections = results[1].find("tbody").find_all("tr")
|
96
|
+
for row in upcoming_collections:
|
97
|
+
columns = row.find_all("td")
|
98
|
+
bin_date_str = columns[0].text.strip()
|
99
|
+
bin_types = columns[1].text.strip().split(", ")
|
100
|
+
|
101
|
+
for bin_type in bin_types:
|
102
|
+
bin_date = datetime.strptime(bin_date_str, "%A, %B %d, %Y")
|
103
|
+
dict_data = {
|
104
|
+
"type": bin_type.strip() + " bin",
|
105
|
+
"collectionDate": bin_date.strftime(date_format),
|
106
|
+
}
|
107
|
+
data["bins"].append(dict_data)
|
108
|
+
|
109
|
+
return data
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from datetime import datetime
|
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
|
+
COLLECTION_KINDS = {
|
8
|
+
"waste": "rteelem_ctl03_pnlCollections_Refuse",
|
9
|
+
"recycling": "rteelem_ctl03_pnlCollections_Recycling",
|
10
|
+
"glass": "rteelem_ctl03_pnlCollections_Glass",
|
11
|
+
# Garden waste data is only returned if the property is subscribed to the Garden Waste service
|
12
|
+
"garden": "rteelem_ctl03_pnlCollections_GardenWaste",
|
13
|
+
}
|
14
|
+
|
15
|
+
|
16
|
+
class CouncilClass(AbstractGetBinDataClass):
|
17
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
requests.packages.urllib3.disable_warnings()
|
19
|
+
|
20
|
+
user_uprn = kwargs.get("uprn")
|
21
|
+
check_uprn(user_uprn)
|
22
|
+
|
23
|
+
cookies = {
|
24
|
+
"cookie_control_popup": "A",
|
25
|
+
"WhenAreMyBinsCollected": f"{user_uprn}",
|
26
|
+
}
|
27
|
+
|
28
|
+
headers = {
|
29
|
+
"Accept": "*/*",
|
30
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
31
|
+
"Referer": "https://www.basingstoke.gov.uk/",
|
32
|
+
"Sec-Fetch-Dest": "document",
|
33
|
+
"Sec-Fetch-Mode": "navigate",
|
34
|
+
"Sec-Fetch-Site": "cross-site",
|
35
|
+
"Sec-Fetch-User": "?1",
|
36
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.188 Safari/537.36",
|
37
|
+
}
|
38
|
+
|
39
|
+
response = requests.get(
|
40
|
+
"https://www.basingstoke.gov.uk/bincollections",
|
41
|
+
cookies=cookies,
|
42
|
+
headers=headers,
|
43
|
+
verify=False,
|
44
|
+
)
|
45
|
+
|
46
|
+
if response.status_code != 200 or response.text == "0|error|500||":
|
47
|
+
raise SystemError(
|
48
|
+
"Error retrieving data! Please try again or raise an issue on GitHub!"
|
49
|
+
)
|
50
|
+
|
51
|
+
# Make a BS4 object
|
52
|
+
soup = BeautifulSoup(response.text, features="html.parser")
|
53
|
+
soup.prettify()
|
54
|
+
|
55
|
+
bins = []
|
56
|
+
|
57
|
+
for collection_type, collection_class in COLLECTION_KINDS.items():
|
58
|
+
for date in soup.select(f"div#{collection_class} li"):
|
59
|
+
date_pattern = r"\d{1,2}\s\w+\s\d{4}" # Regex pattern to extract date
|
60
|
+
match = re.search(date_pattern, date.get_text(strip=True))
|
61
|
+
|
62
|
+
if match:
|
63
|
+
extracted_date = match.group()
|
64
|
+
formatted_date = datetime.strptime(
|
65
|
+
extracted_date, "%d %B %Y"
|
66
|
+
).strftime(date_format)
|
67
|
+
|
68
|
+
bins.append(
|
69
|
+
{"type": collection_type, "collectionDate": formatted_date}
|
70
|
+
)
|
71
|
+
|
72
|
+
return {"bins": bins}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import json
|
2
|
+
import requests
|
3
|
+
from bs4 import BeautifulSoup
|
4
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
5
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
6
|
+
import ssl
|
7
|
+
import urllib3
|
8
|
+
|
9
|
+
|
10
|
+
class CustomHttpAdapter(requests.adapters.HTTPAdapter):
|
11
|
+
"""Transport adapter" that allows us to use custom ssl_context."""
|
12
|
+
|
13
|
+
def __init__(self, ssl_context=None, **kwargs):
|
14
|
+
self.ssl_context = ssl_context
|
15
|
+
super().__init__(**kwargs)
|
16
|
+
|
17
|
+
def init_poolmanager(self, connections, maxsize, block=False):
|
18
|
+
self.poolmanager = urllib3.poolmanager.PoolManager(
|
19
|
+
num_pools=connections,
|
20
|
+
maxsize=maxsize,
|
21
|
+
block=block,
|
22
|
+
ssl_context=self.ssl_context,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class CouncilClass(AbstractGetBinDataClass):
|
27
|
+
"""
|
28
|
+
Concrete classes have to implement all abstract operations of the
|
29
|
+
base class. They can also override some operations with a default
|
30
|
+
implementation.
|
31
|
+
"""
|
32
|
+
|
33
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
34
|
+
user_uprn = kwargs.get("uprn")
|
35
|
+
check_uprn(user_uprn)
|
36
|
+
|
37
|
+
headers = {
|
38
|
+
"Accept": "application/json, text/javascript, */*; q=0.01",
|
39
|
+
"Accept-Language": "en-GB,en;q=0.9",
|
40
|
+
"Cache-Control": "no-cache",
|
41
|
+
"Connection": "keep-alive",
|
42
|
+
"Content-Type": "application/json; charset=utf-8",
|
43
|
+
"Pragma": "no-cache",
|
44
|
+
"Referer": "https://www.bathnes.gov.uk/webforms/waste/collectionday/",
|
45
|
+
"Sec-Fetch-Dest": "empty",
|
46
|
+
"Sec-Fetch-Mode": "cors",
|
47
|
+
"Sec-Fetch-Site": "same-origin",
|
48
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.188 Safari/537.36",
|
49
|
+
"X-Requested-With": "XMLHttpRequest",
|
50
|
+
}
|
51
|
+
|
52
|
+
session = requests.Session()
|
53
|
+
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
54
|
+
ctx.options |= 0x4
|
55
|
+
session.mount("https://", CustomHttpAdapter(ctx))
|
56
|
+
|
57
|
+
requests.packages.urllib3.disable_warnings()
|
58
|
+
response = session.get(
|
59
|
+
f"https://www.bathnes.gov.uk/webapi/api/BinsAPI/v2/getbartecroute/{user_uprn}/true",
|
60
|
+
headers=headers,
|
61
|
+
)
|
62
|
+
if response.text == "":
|
63
|
+
raise ValueError(
|
64
|
+
"Error parsing data. Please check the provided UPRN. "
|
65
|
+
"If this error continues please open an issue on GitHub."
|
66
|
+
)
|
67
|
+
json_data = json.loads(response.text)
|
68
|
+
|
69
|
+
data = {"bins": []}
|
70
|
+
|
71
|
+
if len(json_data["residualNextDate"]) > 0:
|
72
|
+
dict_data = {
|
73
|
+
"type": "Black Rubbish Bin",
|
74
|
+
"collectionDate": datetime.strptime(
|
75
|
+
json_data["residualNextDate"], "%Y-%m-%dT%H:%M:%S"
|
76
|
+
).strftime(date_format),
|
77
|
+
}
|
78
|
+
data["bins"].append(dict_data)
|
79
|
+
if len(json_data["recyclingNextDate"]) > 0:
|
80
|
+
dict_data = {
|
81
|
+
"type": "Recycling Containers",
|
82
|
+
"collectionDate": datetime.strptime(
|
83
|
+
json_data["recyclingNextDate"], "%Y-%m-%dT%H:%M:%S"
|
84
|
+
).strftime(date_format),
|
85
|
+
}
|
86
|
+
data["bins"].append(dict_data)
|
87
|
+
if len(json_data["organicNextDate"]) > 0:
|
88
|
+
dict_data = {
|
89
|
+
"type": "Garden Waste",
|
90
|
+
"collectionDate": datetime.strptime(
|
91
|
+
json_data["organicNextDate"], "%Y-%m-%dT%H:%M:%S"
|
92
|
+
).strftime(date_format),
|
93
|
+
}
|
94
|
+
data["bins"].append(dict_data)
|
95
|
+
|
96
|
+
data["bins"].sort(
|
97
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
98
|
+
)
|
99
|
+
|
100
|
+
return data
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import json
|
2
|
+
import requests
|
3
|
+
from bs4 import BeautifulSoup
|
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
|
+
api_url = f"https://bbaz-as-prod-bartecapi.azurewebsites.net/api/bincollections/residential/getbyuprn/{user_uprn}/35"
|
21
|
+
|
22
|
+
requests.packages.urllib3.disable_warnings()
|
23
|
+
response = requests.get(api_url)
|
24
|
+
|
25
|
+
if response.status_code != 200:
|
26
|
+
raise ConnectionError("Could not get latest data!")
|
27
|
+
|
28
|
+
json_data = json.loads(response.text)["BinCollections"]
|
29
|
+
data = {"bins": []}
|
30
|
+
collections = []
|
31
|
+
|
32
|
+
for day in json_data:
|
33
|
+
for bin in day:
|
34
|
+
bin_type = bin["BinType"]
|
35
|
+
next_date = datetime.strptime(
|
36
|
+
bin["JobScheduledStart"], "%Y-%m-%dT%H:%M:%S"
|
37
|
+
)
|
38
|
+
collections.append((bin_type, next_date))
|
39
|
+
|
40
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
41
|
+
data = {"bins": []}
|
42
|
+
for item in ordered_data:
|
43
|
+
dict_data = {
|
44
|
+
"type": item[0],
|
45
|
+
"collectionDate": item[1].strftime(date_format),
|
46
|
+
}
|
47
|
+
data["bins"].append(dict_data)
|
48
|
+
|
49
|
+
return data
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from datetime import 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
|
+
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
|
+
user_postcode = kwargs.get("postcode")
|
19
|
+
|
20
|
+
check_uprn(user_uprn)
|
21
|
+
check_postcode(user_postcode)
|
22
|
+
|
23
|
+
# Start a new session to walk through the form
|
24
|
+
requests.packages.urllib3.disable_warnings()
|
25
|
+
s = requests.session()
|
26
|
+
|
27
|
+
headers = {
|
28
|
+
"Origin": "https://www.centralbedfordshire.gov.uk",
|
29
|
+
"Referer": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day",
|
30
|
+
}
|
31
|
+
|
32
|
+
files = {
|
33
|
+
"postcode": (None, user_postcode),
|
34
|
+
"address": (None, user_uprn),
|
35
|
+
}
|
36
|
+
|
37
|
+
response = requests.post(
|
38
|
+
"https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day#my_bin_collections",
|
39
|
+
headers=headers,
|
40
|
+
files=files,
|
41
|
+
)
|
42
|
+
|
43
|
+
# Make that BS4 object and use it to prettify the response
|
44
|
+
soup = BeautifulSoup(response.content, features="html.parser")
|
45
|
+
soup.prettify()
|
46
|
+
|
47
|
+
collections_div = soup.find(id="collections")
|
48
|
+
|
49
|
+
# Get the collection items on the page and strip the bits of text that we don't care for
|
50
|
+
collections = []
|
51
|
+
for bin in collections_div.find_all("h3"):
|
52
|
+
next_bin = bin.next_sibling
|
53
|
+
|
54
|
+
while next_bin.name != "h3" and next_bin.name != "p":
|
55
|
+
if next_bin.name != "br":
|
56
|
+
collection_date = datetime.strptime(bin.text, "%A, %d %B %Y")
|
57
|
+
collections.append((next_bin, collection_date))
|
58
|
+
next_bin = next_bin.next_sibling
|
59
|
+
|
60
|
+
# Sort the collections by date order rather than bin type, then return as a dictionary (with str date)
|
61
|
+
ordered_data = sorted(collections, key=lambda x: x[1])
|
62
|
+
data = {"bins": []}
|
63
|
+
for item in ordered_data:
|
64
|
+
dict_data = {
|
65
|
+
"type": item[0],
|
66
|
+
"collectionDate": item[1].strftime(date_format),
|
67
|
+
}
|
68
|
+
data["bins"].append(dict_data)
|
69
|
+
|
70
|
+
return data
|