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,58 @@
|
|
1
|
+
import urllib3
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
|
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
|
+
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
|
+
# get the page data
|
17
|
+
request = urllib3.request(method="get", url=kwargs["url"])
|
18
|
+
page_data = request.data
|
19
|
+
|
20
|
+
# Make a BS4 object
|
21
|
+
soup = BeautifulSoup(page_data, features="html.parser")
|
22
|
+
soup.prettify()
|
23
|
+
|
24
|
+
# Form a JSON wrapper
|
25
|
+
data = {"bins": []}
|
26
|
+
|
27
|
+
# Find section with bins in
|
28
|
+
sections = soup.find_all("div", {"class": "card h-100"})
|
29
|
+
|
30
|
+
# there may also be a recycling one too
|
31
|
+
sections_recycling = soup.find_all(
|
32
|
+
"div", {"class": "card h-100 card-recycling"}
|
33
|
+
)
|
34
|
+
if len(sections_recycling) > 0:
|
35
|
+
sections.append(sections_recycling[0])
|
36
|
+
|
37
|
+
# For each bin section, get the text and the list elements
|
38
|
+
for item in sections:
|
39
|
+
header = item.find("div", {"class": "card-header"})
|
40
|
+
bin_type_element = header.find_next("b")
|
41
|
+
if bin_type_element is not None:
|
42
|
+
bin_type = bin_type_element.text
|
43
|
+
array_expected_types = ["Domestic", "Recycling"]
|
44
|
+
if bin_type in array_expected_types:
|
45
|
+
date = (
|
46
|
+
item.find_next("p", {"class": "card-text"})
|
47
|
+
.find_next("mark")
|
48
|
+
.next_sibling.strip()
|
49
|
+
)
|
50
|
+
next_collection = datetime.strptime(date, "%d/%m/%Y")
|
51
|
+
|
52
|
+
dict_data = {
|
53
|
+
"type": bin_type,
|
54
|
+
"collectionDate": next_collection.strftime(date_format),
|
55
|
+
}
|
56
|
+
data["bins"].append(dict_data)
|
57
|
+
|
58
|
+
return data
|
@@ -0,0 +1,203 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
user_postcode = kwargs.get("postcode")
|
16
|
+
check_postcode(user_postcode)
|
17
|
+
user_uprn = kwargs.get("uprn")
|
18
|
+
check_uprn(user_uprn)
|
19
|
+
|
20
|
+
headers = {
|
21
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) "
|
22
|
+
"Chrome/87.0.4280.141 Safari/537.36"
|
23
|
+
}
|
24
|
+
|
25
|
+
requests.packages.urllib3.disable_warnings()
|
26
|
+
with requests.Session() as s:
|
27
|
+
# Set Headers
|
28
|
+
s.headers = headers
|
29
|
+
|
30
|
+
# Get the first page - This is the Search for property by Post Code page
|
31
|
+
resource = s.get(
|
32
|
+
"https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:6CDD2A34C912312074D8E2410531401A8C00EFF7"
|
33
|
+
)
|
34
|
+
# Create a BeautifulSoup object from the page's HTML
|
35
|
+
soup = BeautifulSoup(resource.text, "html.parser")
|
36
|
+
|
37
|
+
# The page contains a number of values that must be passed into subsequent requests - extract them here
|
38
|
+
payload = {
|
39
|
+
i["name"]: i.get("value", "") for i in soup.select("input[name]")
|
40
|
+
}
|
41
|
+
payload2 = {
|
42
|
+
i["data-for"]: i.get("value", "")
|
43
|
+
for i in soup.select("input[data-for]")
|
44
|
+
}
|
45
|
+
payload_salt = soup.select_one('input[id="pSalt"]').get("value")
|
46
|
+
payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get(
|
47
|
+
"value"
|
48
|
+
)
|
49
|
+
|
50
|
+
# Add the PostCode and 'SEARCH' to the payload
|
51
|
+
payload["p_request"] = "SEARCH"
|
52
|
+
payload["P153_POST_CODE"] = user_postcode
|
53
|
+
|
54
|
+
# Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested
|
55
|
+
merged_list = {**payload, **payload2}
|
56
|
+
new_list = []
|
57
|
+
other_list = {}
|
58
|
+
for key in merged_list.keys():
|
59
|
+
temp_list = {}
|
60
|
+
val = merged_list[key]
|
61
|
+
if key in [
|
62
|
+
"P153_UPRN",
|
63
|
+
"P153_TEMP",
|
64
|
+
"P153_SYSDATE",
|
65
|
+
"P0_LANGUAGE",
|
66
|
+
"P153_POST_CODE",
|
67
|
+
]:
|
68
|
+
temp_list = {"n": key, "v": val}
|
69
|
+
new_list.append(temp_list)
|
70
|
+
elif key in [
|
71
|
+
"p_flow_id",
|
72
|
+
"p_flow_step_id",
|
73
|
+
"p_instance",
|
74
|
+
"p_page_submission_id",
|
75
|
+
"p_request",
|
76
|
+
"p_reload_on_submit",
|
77
|
+
]:
|
78
|
+
other_list[key] = val
|
79
|
+
else:
|
80
|
+
temp_list = {"n": key, "v": "", "ck": val}
|
81
|
+
new_list.append(temp_list)
|
82
|
+
|
83
|
+
json_builder = {
|
84
|
+
"pageItems": {
|
85
|
+
"itemsToSubmit": new_list,
|
86
|
+
"protected": payload_protected,
|
87
|
+
"rowVersion": "",
|
88
|
+
"formRegionChecksums": [],
|
89
|
+
},
|
90
|
+
"salt": payload_salt,
|
91
|
+
}
|
92
|
+
json_object = json.dumps(json_builder, separators=(",", ":"))
|
93
|
+
other_list["p_json"] = json_object
|
94
|
+
|
95
|
+
# Set Referrer header
|
96
|
+
s.headers.update(
|
97
|
+
{
|
98
|
+
"referer": "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:6CDD2A34C912312074D8E2410531401A8C00EFF7"
|
99
|
+
}
|
100
|
+
)
|
101
|
+
|
102
|
+
# Generate POST including all the JSON we just built
|
103
|
+
s.post(
|
104
|
+
"https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list
|
105
|
+
)
|
106
|
+
|
107
|
+
# The second page on the portal would normally allow you to select your property from a dropdown list of
|
108
|
+
# those that are at the postcode entered on the previous page
|
109
|
+
# The required cookies are stored within the session so re-use the session to keep them
|
110
|
+
resource = s.get(
|
111
|
+
"https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days"
|
112
|
+
)
|
113
|
+
|
114
|
+
# Create a BeautifulSoup object from the page's HTML
|
115
|
+
soup = BeautifulSoup(resource.text, "html.parser")
|
116
|
+
|
117
|
+
# The page contains a number of values that must be passed into subsequent requests - extract them here
|
118
|
+
payload = {
|
119
|
+
i["name"]: i.get("value", "") for i in soup.select("input[name]")
|
120
|
+
}
|
121
|
+
payload2 = {
|
122
|
+
i["data-for"]: i.get("value", "")
|
123
|
+
for i in soup.select("input[data-for]")
|
124
|
+
}
|
125
|
+
payload_salt = soup.select_one('input[id="pSalt"]').get("value")
|
126
|
+
payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get(
|
127
|
+
"value"
|
128
|
+
)
|
129
|
+
|
130
|
+
# Add the UPRN and 'SUBMIT' to the payload
|
131
|
+
payload["p_request"] = "SUBMIT"
|
132
|
+
payload["P153_UPRN"] = user_uprn
|
133
|
+
|
134
|
+
# Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested
|
135
|
+
merged_list = {**payload, **payload2}
|
136
|
+
new_list = []
|
137
|
+
other_list = {}
|
138
|
+
for key in merged_list.keys():
|
139
|
+
temp_list = {}
|
140
|
+
val = merged_list[key]
|
141
|
+
if key in ["P153_UPRN", "P153_TEMP", "P153_SYSDATE", "P0_LANGUAGE"]:
|
142
|
+
temp_list = {"n": key, "v": val}
|
143
|
+
new_list.append(temp_list)
|
144
|
+
elif key in ["P153_ZABY"]:
|
145
|
+
temp_list = {"n": key, "v": "1", "ck": val}
|
146
|
+
new_list.append(temp_list)
|
147
|
+
elif key in ["P153_POST_CODE"]:
|
148
|
+
temp_list = {"n": key, "v": user_postcode, "ck": val}
|
149
|
+
new_list.append(temp_list)
|
150
|
+
elif key in [
|
151
|
+
"p_flow_id",
|
152
|
+
"p_flow_step_id",
|
153
|
+
"p_instance",
|
154
|
+
"p_page_submission_id",
|
155
|
+
"p_request",
|
156
|
+
"p_reload_on_submit",
|
157
|
+
]:
|
158
|
+
other_list[key] = val
|
159
|
+
else:
|
160
|
+
temp_list = {"n": key, "v": "", "ck": val}
|
161
|
+
new_list.append(temp_list)
|
162
|
+
|
163
|
+
json_builder = {
|
164
|
+
"pageItems": {
|
165
|
+
"itemsToSubmit": new_list,
|
166
|
+
"protected": payload_protected,
|
167
|
+
"rowVersion": "",
|
168
|
+
"formRegionChecksums": [],
|
169
|
+
},
|
170
|
+
"salt": payload_salt,
|
171
|
+
}
|
172
|
+
|
173
|
+
json_object = json.dumps(json_builder, separators=(",", ":"))
|
174
|
+
other_list["p_json"] = json_object
|
175
|
+
|
176
|
+
# Generate POST including all the JSON we just built
|
177
|
+
s.post(
|
178
|
+
"https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list
|
179
|
+
)
|
180
|
+
|
181
|
+
# The third and final page on the portal shows the detail of the waste collection services
|
182
|
+
# The required cookies are stored within the session so re-use the session to keep them
|
183
|
+
resource = s.get(
|
184
|
+
"https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days"
|
185
|
+
)
|
186
|
+
|
187
|
+
# Create a BeautifulSoup object from the page's HTML
|
188
|
+
soup = BeautifulSoup(resource.text, "html.parser")
|
189
|
+
data = {"bins": []}
|
190
|
+
|
191
|
+
# Loop through the items on the page and build a JSON object for ingestion
|
192
|
+
for item in soup.select(".t-MediaList-item"):
|
193
|
+
for value in item.select(".t-MediaList-body"):
|
194
|
+
dict_data = {
|
195
|
+
"type": value.select("span")[1].get_text(strip=True).title(),
|
196
|
+
"collectionDate": datetime.strptime(
|
197
|
+
value.select(".t-MediaList-desc")[0].get_text(strip=True),
|
198
|
+
"%A, %d %B, %Y",
|
199
|
+
).strftime(date_format),
|
200
|
+
}
|
201
|
+
data["bins"].append(dict_data)
|
202
|
+
|
203
|
+
return data
|
@@ -0,0 +1,115 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from datetime import datetime
|
3
|
+
from selenium.webdriver.common.by import By
|
4
|
+
from selenium.webdriver.support import expected_conditions as EC
|
5
|
+
from selenium.webdriver.support.ui import Select
|
6
|
+
from selenium.webdriver.support.wait import WebDriverWait
|
7
|
+
|
8
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
9
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
10
|
+
|
11
|
+
|
12
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
13
|
+
|
14
|
+
|
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
|
+
page = (
|
26
|
+
"https://myselfservice.ne-derbyshire.gov.uk/service/Check_your_Bin_Day"
|
27
|
+
)
|
28
|
+
|
29
|
+
data = {"bins": []}
|
30
|
+
|
31
|
+
user_uprn = kwargs.get("uprn")
|
32
|
+
user_postcode = kwargs.get("postcode")
|
33
|
+
web_driver = kwargs.get("web_driver")
|
34
|
+
headless = kwargs.get("headless")
|
35
|
+
check_uprn(user_uprn)
|
36
|
+
check_postcode(user_postcode)
|
37
|
+
# Create Selenium webdriver
|
38
|
+
driver = create_webdriver(web_driver, headless)
|
39
|
+
driver.get(page)
|
40
|
+
|
41
|
+
# If you bang in the house number (or property name) and postcode in the box it should find your property
|
42
|
+
|
43
|
+
iframe_presense = WebDriverWait(driver, 30).until(
|
44
|
+
EC.presence_of_element_located((By.ID, "fillform-frame-1"))
|
45
|
+
)
|
46
|
+
|
47
|
+
driver.switch_to.frame(iframe_presense)
|
48
|
+
wait = WebDriverWait(driver, 60)
|
49
|
+
inputElement_postcodesearch = wait.until(
|
50
|
+
EC.element_to_be_clickable((By.NAME, "postcode_search"))
|
51
|
+
)
|
52
|
+
|
53
|
+
inputElement_postcodesearch.send_keys(str(user_postcode))
|
54
|
+
|
55
|
+
# Wait for the 'Select your property' dropdown to appear and select the first result
|
56
|
+
dropdown = wait.until(EC.element_to_be_clickable((By.NAME, "selAddress")))
|
57
|
+
|
58
|
+
dropdown_options = wait.until(
|
59
|
+
EC.presence_of_element_located((By.CLASS_NAME, "lookup-option"))
|
60
|
+
)
|
61
|
+
|
62
|
+
# Create a 'Select' for it, then select the first address in the list
|
63
|
+
# (Index 0 is "Make a selection from the list")
|
64
|
+
drop_down_values = Select(dropdown)
|
65
|
+
option_element = wait.until(
|
66
|
+
EC.presence_of_element_located(
|
67
|
+
(By.CSS_SELECTOR, f'option.lookup-option[value="{str(user_uprn)}"]')
|
68
|
+
)
|
69
|
+
)
|
70
|
+
|
71
|
+
drop_down_values.select_by_value(str(user_uprn))
|
72
|
+
|
73
|
+
# Wait for the 'View more' link to appear, then click it to get the full set of dates
|
74
|
+
h3_element = wait.until(
|
75
|
+
EC.presence_of_element_located(
|
76
|
+
(By.XPATH, "//th[contains(text(), 'Waste Collection')]")
|
77
|
+
)
|
78
|
+
)
|
79
|
+
|
80
|
+
soup = BeautifulSoup(driver.page_source, features="html.parser")
|
81
|
+
|
82
|
+
target_h3 = soup.find("h3", string="Collection Details")
|
83
|
+
tables_after_h3 = target_h3.parent.parent.find_next("table")
|
84
|
+
|
85
|
+
table_rows = tables_after_h3.find_all("tr")
|
86
|
+
for row in table_rows:
|
87
|
+
rowdata = row.find_all("td")
|
88
|
+
if len(rowdata) == 3:
|
89
|
+
labels = rowdata[0].find_all("label")
|
90
|
+
# Strip the day (i.e., Monday) out of the collection date string for parsing
|
91
|
+
if len(labels) >= 2:
|
92
|
+
date_label = labels[1]
|
93
|
+
datestring = date_label.text.strip()
|
94
|
+
|
95
|
+
# Add the bin type and collection date to the 'data' dictionary
|
96
|
+
data["bins"].append(
|
97
|
+
{
|
98
|
+
"type": rowdata[2].text.strip(),
|
99
|
+
"collectionDate": datetime.strptime(
|
100
|
+
datestring, "%d/%m/%Y"
|
101
|
+
).strftime(
|
102
|
+
date_format
|
103
|
+
), # Format the date as needed
|
104
|
+
}
|
105
|
+
)
|
106
|
+
except Exception as e:
|
107
|
+
# Here you can log the exception if needed
|
108
|
+
print(f"An error occurred: {e}")
|
109
|
+
# Optionally, re-raise the exception if you want it to propagate
|
110
|
+
raise
|
111
|
+
finally:
|
112
|
+
# This block ensures that the driver is closed regardless of an exception
|
113
|
+
if driver:
|
114
|
+
driver.quit()
|
115
|
+
return data
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from uk_bin_collection.uk_bin_collection.common import date_format
|
4
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
5
|
+
|
6
|
+
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
baseclass. They can also override some
|
11
|
+
operations with a default implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
# Make a BS4 object
|
16
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
17
|
+
soup.prettify()
|
18
|
+
|
19
|
+
data = {"bins": []}
|
20
|
+
|
21
|
+
# Get list items that can be seen on page
|
22
|
+
for element in soup.find_all(
|
23
|
+
"li", {"class": "list-group-item p-0 p-3 bin-collection-item"}
|
24
|
+
):
|
25
|
+
element_text = element.text.strip().split("\n\n")
|
26
|
+
element_text = [x.strip() for x in element_text]
|
27
|
+
|
28
|
+
bin_type = element_text[1]
|
29
|
+
collection_date = pd.Timestamp(element_text[0]).strftime(date_format)
|
30
|
+
|
31
|
+
dict_data = {
|
32
|
+
"type": bin_type,
|
33
|
+
"collectionDate": collection_date,
|
34
|
+
}
|
35
|
+
data["bins"].append(dict_data)
|
36
|
+
|
37
|
+
# Get hidden list items too
|
38
|
+
for element in soup.find_all(
|
39
|
+
"li", {"class": "list-group-item p-0 p-3 bin-collection-item d-none"}
|
40
|
+
):
|
41
|
+
element_text = element.text.strip().split("\n\n")
|
42
|
+
element_text = [x.strip() for x in element_text]
|
43
|
+
|
44
|
+
bin_type = element_text[1]
|
45
|
+
collection_date = pd.Timestamp(element_text[0]).strftime(date_format)
|
46
|
+
|
47
|
+
dict_data = {
|
48
|
+
"type": bin_type,
|
49
|
+
"collectionDate": collection_date,
|
50
|
+
}
|
51
|
+
data["bins"].append(dict_data)
|
52
|
+
|
53
|
+
return data
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
# Make a BS4 object
|
16
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
17
|
+
soup.prettify()
|
18
|
+
|
19
|
+
data = {"bins": []}
|
20
|
+
|
21
|
+
for bins in soup.find_all(
|
22
|
+
"div", {"class": lambda L: L and L.startswith("bg-")}
|
23
|
+
):
|
24
|
+
for bin in bins.find_all("p"):
|
25
|
+
if bin.find("strong"):
|
26
|
+
results = re.search(
|
27
|
+
"Next Collection:(.*?) on ([A-Za-z]+, \\d\\d? [A-Za-z]+ \\d{4})",
|
28
|
+
bin.find("strong").get_text(strip=True),
|
29
|
+
)
|
30
|
+
if results:
|
31
|
+
collection_date = datetime.strptime(
|
32
|
+
results.groups()[1], "%A, %d %B %Y"
|
33
|
+
)
|
34
|
+
data["bins"].append(
|
35
|
+
{
|
36
|
+
"type": bins.h3.get_text(strip=True),
|
37
|
+
"collectionDate": collection_date.strftime(date_format),
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
data["bins"].sort(
|
42
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
43
|
+
)
|
44
|
+
|
45
|
+
return data
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
3
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
|
+
|
5
|
+
|
6
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
7
|
+
class CouncilClass(AbstractGetBinDataClass):
|
8
|
+
"""
|
9
|
+
Concrete classes have to implement all abstract operations of the
|
10
|
+
base class. They can also override some operations with a default
|
11
|
+
implementation.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
15
|
+
# Make a BS4 object
|
16
|
+
soup = BeautifulSoup(page.text, features="html.parser")
|
17
|
+
soup.prettify()
|
18
|
+
|
19
|
+
data = {"bins": []}
|
20
|
+
for bins in soup.select("div[class^=waste-type-container]"):
|
21
|
+
bin_type = bins.div.h3.text.strip()
|
22
|
+
collection_date = bins.select("div > p")[0].get_text(strip=True)
|
23
|
+
next_collection_date = bins.select("div > p")[1].get_text(strip=True)
|
24
|
+
if collection_date:
|
25
|
+
# Add collection date
|
26
|
+
dict_data = {
|
27
|
+
"type": bin_type,
|
28
|
+
"collectionDate": datetime.strptime(
|
29
|
+
collection_date, "%d %B %Y"
|
30
|
+
).strftime(date_format),
|
31
|
+
}
|
32
|
+
data["bins"].append(dict_data)
|
33
|
+
# Add next collection date
|
34
|
+
dict_data = {
|
35
|
+
"type": bin_type,
|
36
|
+
"collectionDate": datetime.strptime(
|
37
|
+
next_collection_date, "%d %B %Y"
|
38
|
+
).strftime(date_format),
|
39
|
+
}
|
40
|
+
data["bins"].append(dict_data)
|
41
|
+
|
42
|
+
data["bins"].sort(
|
43
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
44
|
+
)
|
45
|
+
|
46
|
+
return data
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
2
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
3
|
+
|
4
|
+
|
5
|
+
class CouncilClass(AbstractGetBinDataClass):
|
6
|
+
"""
|
7
|
+
Concrete classes have to implement all abstract operations of the
|
8
|
+
base class. They can also override some operations with a default
|
9
|
+
implementation.
|
10
|
+
"""
|
11
|
+
|
12
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
13
|
+
uprn = kwargs.get("uprn")
|
14
|
+
check_uprn(uprn)
|
15
|
+
|
16
|
+
headers = {
|
17
|
+
"Accept": "application/json, text/javascript, */*; q=0.01",
|
18
|
+
"Accept-Encoding": "gzip, deflate, br",
|
19
|
+
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
|
20
|
+
"Connection": "keep-alive",
|
21
|
+
"Host": "m.northlincs.gov.uk",
|
22
|
+
"Origin": "https://www.northlincs.gov.uk",
|
23
|
+
"Referer": "https://www.northlincs.gov.uk/",
|
24
|
+
"sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"',
|
25
|
+
"sec-ch-ua-mobile": "?0",
|
26
|
+
"sec-ch-ua-platform": '"Windows"',
|
27
|
+
"Sec-Fetch-Dest": "empty",
|
28
|
+
"Sec-Fetch-Mode": "cors",
|
29
|
+
"Sec-Fetch-Site": "same-site",
|
30
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
|
31
|
+
}
|
32
|
+
requests.packages.urllib3.disable_warnings()
|
33
|
+
response = requests.get(
|
34
|
+
f"https://m.northlincs.gov.uk/bin_collections?no_collections=20&uprn={uprn}",
|
35
|
+
headers=headers,
|
36
|
+
)
|
37
|
+
if response.status_code != 200:
|
38
|
+
raise ValueError("No bin data found for provided UPRN.")
|
39
|
+
json_data = json.loads(response.text.encode().decode("utf-8-sig"))
|
40
|
+
|
41
|
+
data = {"bins": []}
|
42
|
+
for c in json_data["Collections"]:
|
43
|
+
bin_type = c["BinCodeDescription"].strip()
|
44
|
+
if bin_type.lower() != "textiles bag":
|
45
|
+
dict_data = {
|
46
|
+
"type": bin_type,
|
47
|
+
"collectionDate": get_next_occurrence_from_day_month(
|
48
|
+
datetime.strptime(
|
49
|
+
c["BinCollectionDate"].replace(" (*)", "").strip()
|
50
|
+
+ " "
|
51
|
+
+ datetime.now().strftime("%Y"),
|
52
|
+
"%A %d %B %Y",
|
53
|
+
)
|
54
|
+
).strftime(date_format),
|
55
|
+
}
|
56
|
+
data["bins"].append(dict_data)
|
57
|
+
|
58
|
+
return data
|