uk_bin_collection 0.119.0__py3-none-any.whl → 0.123.1__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/tests/input.json +129 -10
- uk_bin_collection/tests/test_common_functions.py +26 -0
- uk_bin_collection/uk_bin_collection/common.py +30 -6
- uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +0 -1
- uk_bin_collection/uk_bin_collection/councils/BaberghDistrictCouncil.py +3 -1
- uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py +8 -5
- uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +10 -2
- uk_bin_collection/uk_bin_collection/councils/CarmarthenshireCountyCouncil.py +3 -3
- uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py +102 -0
- uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py +3 -3
- uk_bin_collection/uk_bin_collection/councils/CumberlandAllerdaleCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/EastAyrshireCouncil.py +11 -8
- uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +14 -0
- uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +17 -0
- uk_bin_collection/uk_bin_collection/councils/HackneyCouncil.py +85 -0
- uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +83 -0
- uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +10 -5
- uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py +71 -0
- uk_bin_collection/uk_bin_collection/councils/KingsLynnandWestNorfolkBC.py +59 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughHavering.py +75 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughLewisham.py +140 -0
- uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +3 -1
- uk_bin_collection/uk_bin_collection/councils/MonmouthshireCountyCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/MorayCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py +66 -0
- uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py +113 -0
- uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +87 -0
- uk_bin_collection/uk_bin_collection/councils/ThurrockCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +29 -10
- uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +12 -10
- uk_bin_collection/uk_bin_collection/councils/WyreForestDistrictCouncil.py +65 -0
- {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/METADATA +1 -1
- {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/RECORD +38 -21
- {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/LICENSE +0 -0
- {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/WHEEL +0 -0
- {uk_bin_collection-0.119.0.dist-info → uk_bin_collection-0.123.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
import requests
|
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
|
+
# 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
|
+
|
18
|
+
user_postcode = kwargs.get("postcode")
|
19
|
+
user_paon = kwargs.get("paon")
|
20
|
+
check_postcode(user_postcode)
|
21
|
+
check_paon(user_paon)
|
22
|
+
bindata = {"bins": []}
|
23
|
+
|
24
|
+
URI = "https://abc-wrp.whitespacews.com/"
|
25
|
+
|
26
|
+
session = requests.Session()
|
27
|
+
|
28
|
+
# get link from first page as has some kind of unique hash
|
29
|
+
r = session.get(
|
30
|
+
URI,
|
31
|
+
)
|
32
|
+
r.raise_for_status()
|
33
|
+
soup = BeautifulSoup(r.text, features="html.parser")
|
34
|
+
|
35
|
+
alink = soup.find("a", text="View My Collections")
|
36
|
+
|
37
|
+
if alink is None:
|
38
|
+
raise Exception("Initial page did not load correctly")
|
39
|
+
|
40
|
+
# greplace 'seq' query string to skip next step
|
41
|
+
nextpageurl = alink["href"].replace("seq=1", "seq=2")
|
42
|
+
|
43
|
+
data = {
|
44
|
+
"address_name_number": user_paon,
|
45
|
+
"address_postcode": user_postcode,
|
46
|
+
}
|
47
|
+
|
48
|
+
# get list of addresses
|
49
|
+
r = session.post(nextpageurl, data)
|
50
|
+
r.raise_for_status()
|
51
|
+
|
52
|
+
soup = BeautifulSoup(r.text, features="html.parser")
|
53
|
+
|
54
|
+
# get first address (if you don't enter enough argument values this won't find the right address)
|
55
|
+
alink = soup.find("div", id="property_list").find("a")
|
56
|
+
|
57
|
+
if alink is None:
|
58
|
+
raise Exception("Address not found")
|
59
|
+
|
60
|
+
nextpageurl = URI + alink["href"]
|
61
|
+
|
62
|
+
# get collection page
|
63
|
+
r = session.get(
|
64
|
+
nextpageurl,
|
65
|
+
)
|
66
|
+
r.raise_for_status()
|
67
|
+
soup = BeautifulSoup(r.text, features="html.parser")
|
68
|
+
|
69
|
+
if soup.find("span", id="waste-hint"):
|
70
|
+
raise Exception("No scheduled services at this address")
|
71
|
+
|
72
|
+
u1s = soup.find("section", id="scheduled-collections").find_all("u1")
|
73
|
+
|
74
|
+
for u1 in u1s:
|
75
|
+
lis = u1.find_all("li", recursive=False)
|
76
|
+
|
77
|
+
date = lis[1].text.replace("\n", "")
|
78
|
+
bin_type = lis[2].text.replace("\n", "")
|
79
|
+
|
80
|
+
dict_data = {
|
81
|
+
"type": bin_type,
|
82
|
+
"collectionDate": datetime.strptime(
|
83
|
+
date,
|
84
|
+
"%d/%m/%Y",
|
85
|
+
).strftime(date_format),
|
86
|
+
}
|
87
|
+
bindata["bins"].append(dict_data)
|
88
|
+
|
89
|
+
bindata["bins"].sort(
|
90
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
91
|
+
)
|
92
|
+
|
93
|
+
return bindata
|
@@ -30,14 +30,17 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
30
30
|
# Find each <time> element in the schedule
|
31
31
|
for entry in soup.find_all("time"):
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
"
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
ScheduleItems = entry.find_all(class_="ScheduleItem")
|
34
|
+
|
35
|
+
for ScheduleItem in ScheduleItems:
|
36
|
+
dict_data = {
|
37
|
+
"type": ScheduleItem.text.strip(),
|
38
|
+
"collectionDate": datetime.strptime(
|
39
|
+
entry["datetime"],
|
40
|
+
"%Y-%m-%d",
|
41
|
+
).strftime("%d/%m/%Y"),
|
42
|
+
}
|
43
|
+
bindata["bins"].append(dict_data)
|
41
44
|
|
42
45
|
bindata["bins"].sort(
|
43
46
|
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from bs4 import BeautifulSoup
|
2
|
+
|
2
3
|
from uk_bin_collection.uk_bin_collection.common import *
|
3
4
|
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
4
5
|
|
@@ -45,4 +46,17 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
45
46
|
}
|
46
47
|
data["bins"].append(dict_data)
|
47
48
|
|
49
|
+
if len(page_text) > 5:
|
50
|
+
garden_day = datetime.strptime(
|
51
|
+
remove_ordinal_indicator_from_date_string(
|
52
|
+
page_text[6].find_next("strong").text
|
53
|
+
),
|
54
|
+
"%d %B %Y",
|
55
|
+
).strftime(date_format)
|
56
|
+
dict_data = {
|
57
|
+
"type": "Garden",
|
58
|
+
"collectionDate": garden_day,
|
59
|
+
}
|
60
|
+
data["bins"].append(dict_data)
|
61
|
+
|
48
62
|
return data
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import requests
|
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
|
+
# 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
|
+
|
18
|
+
user_uprn = kwargs.get("uprn")
|
19
|
+
check_uprn(user_uprn)
|
20
|
+
bindata = {"bins": []}
|
21
|
+
|
22
|
+
URI = f"https://service.folkestone-hythe.gov.uk/webapp/myarea/index.php?uprn={user_uprn}"
|
23
|
+
|
24
|
+
# Make the GET request
|
25
|
+
response = requests.get(URI)
|
26
|
+
|
27
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
28
|
+
|
29
|
+
soup = soup.find("div", {"id": "bincollections"})
|
30
|
+
|
31
|
+
# Find the Recycling and Non-Recyclables sections
|
32
|
+
bin_schedule = {}
|
33
|
+
|
34
|
+
# Extract the recycling schedule
|
35
|
+
recycling_section = soup.find("span", text=lambda x: x and "Recycling" in x)
|
36
|
+
if recycling_section:
|
37
|
+
bin_types = recycling_section.text.replace("Recycling: ", "").split(" / ")
|
38
|
+
recycling_dates = recycling_section.find_next("ul").find_all("li")
|
39
|
+
bin_schedule["Recycling"] = [date.text.strip() for date in recycling_dates]
|
40
|
+
for date in recycling_dates:
|
41
|
+
for bin_type in bin_types:
|
42
|
+
dict_data = {
|
43
|
+
"type": bin_type.strip(),
|
44
|
+
"collectionDate": datetime.strptime(
|
45
|
+
remove_ordinal_indicator_from_date_string(
|
46
|
+
date.text.strip()
|
47
|
+
),
|
48
|
+
"%A %d %B %Y",
|
49
|
+
).strftime("%d/%m/%Y"),
|
50
|
+
}
|
51
|
+
bindata["bins"].append(dict_data)
|
52
|
+
|
53
|
+
# Extract the non-recyclables schedule
|
54
|
+
non_recyclables_section = soup.find(
|
55
|
+
"span", text=lambda x: x and "Non-Recyclables" in x
|
56
|
+
)
|
57
|
+
if non_recyclables_section:
|
58
|
+
bin_types = non_recyclables_section.text.replace(
|
59
|
+
"Non-Recyclables: ", ""
|
60
|
+
).split(" / ")
|
61
|
+
non_recyclables_dates = non_recyclables_section.find_next("ul").find_all(
|
62
|
+
"li"
|
63
|
+
)
|
64
|
+
for date in non_recyclables_dates:
|
65
|
+
for bin_type in bin_types:
|
66
|
+
dict_data = {
|
67
|
+
"type": bin_type.strip(),
|
68
|
+
"collectionDate": datetime.strptime(
|
69
|
+
remove_ordinal_indicator_from_date_string(
|
70
|
+
date.text.strip()
|
71
|
+
),
|
72
|
+
"%A %d %B %Y",
|
73
|
+
).strftime("%d/%m/%Y"),
|
74
|
+
}
|
75
|
+
bindata["bins"].append(dict_data)
|
76
|
+
|
77
|
+
bindata["bins"].sort(
|
78
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
79
|
+
)
|
80
|
+
|
81
|
+
return bindata
|
@@ -27,6 +27,23 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
27
27
|
"../Images/Bins/ashBin.gif": "Ash bin",
|
28
28
|
}
|
29
29
|
|
30
|
+
fieldset = soup.find("fieldset")
|
31
|
+
ps = fieldset.find_all("p")
|
32
|
+
for p in ps:
|
33
|
+
collection = p.text.strip().replace("Your next ", "").split(".")[0]
|
34
|
+
bin_type = collection.split(" day is")[0]
|
35
|
+
collection_date = datetime.strptime(
|
36
|
+
remove_ordinal_indicator_from_date_string(collection).split("day is ")[
|
37
|
+
1
|
38
|
+
],
|
39
|
+
"%A %d %B %Y",
|
40
|
+
)
|
41
|
+
dict_data = {
|
42
|
+
"type": bin_type,
|
43
|
+
"collectionDate": collection_date.strftime(date_format),
|
44
|
+
}
|
45
|
+
data["bins"].append(dict_data)
|
46
|
+
|
30
47
|
# Find the page body with all the calendars
|
31
48
|
body = soup.find("div", {"id": "Application_ctl00"})
|
32
49
|
calendars = body.find_all_next("table", {"title": "Calendar"})
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
|
5
|
+
from uk_bin_collection.uk_bin_collection.common import *
|
6
|
+
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
|
7
|
+
|
8
|
+
|
9
|
+
# import the wonderful Beautiful Soup and the URL grabber
|
10
|
+
class CouncilClass(AbstractGetBinDataClass):
|
11
|
+
"""
|
12
|
+
Concrete classes have to implement all abstract operations of the
|
13
|
+
base class. They can also override some operations with a default
|
14
|
+
implementation.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def parse_data(self, page: str, **kwargs) -> dict:
|
18
|
+
|
19
|
+
user_paon = kwargs.get("paon")
|
20
|
+
user_postcode = kwargs.get("postcode")
|
21
|
+
check_postcode(user_postcode)
|
22
|
+
check_paon(user_paon)
|
23
|
+
bindata = {"bins": []}
|
24
|
+
|
25
|
+
URI = "https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/property/opensearch"
|
26
|
+
|
27
|
+
data = {
|
28
|
+
"Postcode": user_postcode,
|
29
|
+
}
|
30
|
+
headers = {"Content-Type": "application/json"}
|
31
|
+
|
32
|
+
# Make the GET request
|
33
|
+
response = requests.post(URI, json=data, headers=headers)
|
34
|
+
|
35
|
+
addresses = response.json()
|
36
|
+
|
37
|
+
for address in addresses["addressSummaries"]:
|
38
|
+
summary = address["summary"]
|
39
|
+
if user_paon in summary:
|
40
|
+
systemId = address["systemId"]
|
41
|
+
if systemId:
|
42
|
+
URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getproperty/{systemId}"
|
43
|
+
|
44
|
+
response = requests.get(URI)
|
45
|
+
|
46
|
+
address = response.json()
|
47
|
+
|
48
|
+
binIDs = address["providerSpecificFields"][
|
49
|
+
"attributes_wasteContainersAssignableWasteContainers"
|
50
|
+
]
|
51
|
+
for binID in binIDs.split(","):
|
52
|
+
URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getbin/{binID}"
|
53
|
+
response = requests.get(URI)
|
54
|
+
getBin = response.json()
|
55
|
+
|
56
|
+
bin_type = getBin["subTitle"]
|
57
|
+
|
58
|
+
URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getcollection/{binID}"
|
59
|
+
response = requests.get(URI)
|
60
|
+
getcollection = response.json()
|
61
|
+
|
62
|
+
collectionID = getcollection["scheduleCodeWorkflowID"]
|
63
|
+
|
64
|
+
URI = f"https://waste-api-hackney-live.ieg4.net/f806d91c-e133-43a6-ba9a-c0ae4f4cccf6/alloywastepages/getworkflow/{collectionID}"
|
65
|
+
response = requests.get(URI)
|
66
|
+
collection_dates = response.json()
|
67
|
+
|
68
|
+
dates = collection_dates["trigger"]["dates"]
|
69
|
+
|
70
|
+
for date in dates:
|
71
|
+
parsed_datetime = datetime.strptime(
|
72
|
+
date, "%Y-%m-%dT%H:%M:%SZ"
|
73
|
+
).strftime(date_format)
|
74
|
+
|
75
|
+
dict_data = {
|
76
|
+
"type": bin_type.strip(),
|
77
|
+
"collectionDate": parsed_datetime,
|
78
|
+
}
|
79
|
+
bindata["bins"].append(dict_data)
|
80
|
+
|
81
|
+
bindata["bins"].sort(
|
82
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
83
|
+
)
|
84
|
+
|
85
|
+
return bindata
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
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
|
+
# 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
|
+
|
20
|
+
user_uprn = kwargs.get("uprn")
|
21
|
+
check_uprn(user_uprn)
|
22
|
+
bindata = {"bins": []}
|
23
|
+
|
24
|
+
SESSION_URL = "https://online.hartlepool.gov.uk/authapi/isauthenticated?uri=https%253A%252F%252Fonline.hartlepool.gov.uk%252Fservice%252FRefuse_and_recycling___check_bin_day&hostname=online.hartlepool.gov.uk&withCredentials=true"
|
25
|
+
|
26
|
+
API_URL = "https://online.hartlepool.gov.uk/apibroker/runLookup"
|
27
|
+
|
28
|
+
headers = {
|
29
|
+
"Content-Type": "application/json",
|
30
|
+
"Accept": "application/json",
|
31
|
+
"User-Agent": "Mozilla/5.0",
|
32
|
+
"X-Requested-With": "XMLHttpRequest",
|
33
|
+
"Referer": "https://online.hartlepool.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=",
|
34
|
+
}
|
35
|
+
s = requests.session()
|
36
|
+
r = s.get(SESSION_URL)
|
37
|
+
r.raise_for_status()
|
38
|
+
session_data = r.json()
|
39
|
+
sid = session_data["auth-session"]
|
40
|
+
params = {
|
41
|
+
"id": "5ec67e019ffdd",
|
42
|
+
"repeat_against": "",
|
43
|
+
"noRetry": "true",
|
44
|
+
"getOnlyTokens": "undefined",
|
45
|
+
"log_id": "",
|
46
|
+
"app_name": "AF-Renderer::Self",
|
47
|
+
# unix_timestamp
|
48
|
+
"_": str(int(time.time() * 1000)),
|
49
|
+
"sid": sid,
|
50
|
+
}
|
51
|
+
|
52
|
+
data = {
|
53
|
+
"formValues": {
|
54
|
+
"Section 1": {
|
55
|
+
"collectionLocationUPRN": {
|
56
|
+
"value": user_uprn,
|
57
|
+
},
|
58
|
+
},
|
59
|
+
},
|
60
|
+
}
|
61
|
+
|
62
|
+
r = s.post(API_URL, json=data, headers=headers, params=params)
|
63
|
+
r.raise_for_status()
|
64
|
+
|
65
|
+
data = r.json()
|
66
|
+
rows_data = data["integration"]["transformed"]["rows_data"]["0"]
|
67
|
+
if not isinstance(rows_data, dict):
|
68
|
+
raise ValueError("Invalid data returned from API")
|
69
|
+
|
70
|
+
soup = BeautifulSoup(rows_data["HTMLCollectionDatesText"], "html.parser")
|
71
|
+
|
72
|
+
# Find all div elements containing the bin schedule
|
73
|
+
for div in soup.find_all("div"):
|
74
|
+
# Extract bin type and date from the span tag
|
75
|
+
text = div.find("span").text.strip()
|
76
|
+
bin_type, date = text.split(" ", 1)
|
77
|
+
dict_data = {
|
78
|
+
"type": bin_type,
|
79
|
+
"collectionDate": date,
|
80
|
+
}
|
81
|
+
bindata["bins"].append(dict_data)
|
82
|
+
|
83
|
+
return bindata
|
@@ -83,11 +83,16 @@ class CouncilClass(AbstractGetBinDataClass):
|
|
83
83
|
)
|
84
84
|
|
85
85
|
# Select address from dropdown and wait
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
WebDriverWait(driver, 10).until(
|
87
|
+
EC.element_to_be_clickable(
|
88
|
+
(
|
89
|
+
By.XPATH,
|
90
|
+
"//select[@id='FINDBINDAYSHIGHPEAK_ADDRESSSELECT_ADDRESS']//option[contains(., '"
|
91
|
+
+ user_paon
|
92
|
+
+ "')]",
|
93
|
+
)
|
94
|
+
)
|
95
|
+
).click()
|
91
96
|
|
92
97
|
WebDriverWait(driver, 10).until(
|
93
98
|
EC.presence_of_element_located(
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import requests
|
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
|
+
# 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
|
+
|
18
|
+
user_uprn = kwargs.get("uprn")
|
19
|
+
check_uprn(user_uprn)
|
20
|
+
bindata = {"bins": []}
|
21
|
+
|
22
|
+
URI = f"https://www.hinckley-bosworth.gov.uk/set-location?id={user_uprn}&redirect=refuse&rememberloc="
|
23
|
+
|
24
|
+
# Make the GET request
|
25
|
+
response = requests.get(URI)
|
26
|
+
|
27
|
+
# Parse the HTML
|
28
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
29
|
+
|
30
|
+
# Find all the bin collection date containers
|
31
|
+
bin_schedule = []
|
32
|
+
collection_divs = soup.find_all(
|
33
|
+
"div", class_=["first_date_bins", "last_date_bins"]
|
34
|
+
)
|
35
|
+
|
36
|
+
for div in collection_divs:
|
37
|
+
# Extract the date
|
38
|
+
date = div.find("h3", class_="collectiondate").text.strip().replace(":", "")
|
39
|
+
|
40
|
+
# Extract bin types
|
41
|
+
bins = [img["alt"] for img in div.find_all("img", class_="collection")]
|
42
|
+
|
43
|
+
# Append to the schedule
|
44
|
+
bin_schedule.append({"date": date, "bins": bins})
|
45
|
+
|
46
|
+
current_year = datetime.now().year
|
47
|
+
current_month = datetime.now().month
|
48
|
+
|
49
|
+
# Print the schedule
|
50
|
+
for entry in bin_schedule:
|
51
|
+
bin_types = entry["bins"]
|
52
|
+
date = datetime.strptime(entry["date"], "%d %B")
|
53
|
+
|
54
|
+
if (current_month > 9) and (date.month < 4):
|
55
|
+
date = date.replace(year=(current_year + 1))
|
56
|
+
else:
|
57
|
+
date = date.replace(year=current_year)
|
58
|
+
|
59
|
+
for bin_type in bin_types:
|
60
|
+
|
61
|
+
dict_data = {
|
62
|
+
"type": bin_type,
|
63
|
+
"collectionDate": date.strftime("%d/%m/%Y"),
|
64
|
+
}
|
65
|
+
bindata["bins"].append(dict_data)
|
66
|
+
|
67
|
+
bindata["bins"].sort(
|
68
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
69
|
+
)
|
70
|
+
|
71
|
+
return bindata
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import requests
|
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
|
+
# 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
|
+
|
18
|
+
user_uprn = kwargs.get("uprn")
|
19
|
+
check_uprn(user_uprn)
|
20
|
+
user_uprn = user_uprn.zfill(12)
|
21
|
+
bindata = {"bins": []}
|
22
|
+
|
23
|
+
URI = "https://www.west-norfolk.gov.uk/info/20174/bins_and_recycling_collection_dates"
|
24
|
+
|
25
|
+
headers = {"Cookie": f"bcklwn_uprn={user_uprn}"}
|
26
|
+
|
27
|
+
# Make the GET request
|
28
|
+
response = requests.get(URI, headers=headers)
|
29
|
+
|
30
|
+
soup = BeautifulSoup(response.content, features="html.parser")
|
31
|
+
soup.prettify()
|
32
|
+
|
33
|
+
# Find all bin_date_container divs
|
34
|
+
bin_date_containers = soup.find_all("div", class_="bin_date_container")
|
35
|
+
|
36
|
+
# Loop through each bin_date_container
|
37
|
+
for container in bin_date_containers:
|
38
|
+
# Extract the collection date
|
39
|
+
date = (
|
40
|
+
container.find("h3", class_="collectiondate").text.strip().rstrip(":")
|
41
|
+
)
|
42
|
+
|
43
|
+
# Extract the bin type from the alt attribute of the img tag
|
44
|
+
bin_type = container.find("img")["alt"]
|
45
|
+
|
46
|
+
dict_data = {
|
47
|
+
"type": bin_type,
|
48
|
+
"collectionDate": datetime.strptime(
|
49
|
+
date,
|
50
|
+
"%A %d %B %Y",
|
51
|
+
).strftime("%d/%m/%Y"),
|
52
|
+
}
|
53
|
+
bindata["bins"].append(dict_data)
|
54
|
+
|
55
|
+
bindata["bins"].sort(
|
56
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")
|
57
|
+
)
|
58
|
+
|
59
|
+
return bindata
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from bs4 import BeautifulSoup
|
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
|
+
# 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
|
+
|
20
|
+
user_uprn = kwargs.get("uprn")
|
21
|
+
check_uprn(user_uprn)
|
22
|
+
bindata = {"bins": []}
|
23
|
+
|
24
|
+
URI = "https://lbhapiprod.azure-api.net"
|
25
|
+
endpoint = f"{URI}/whitespace/GetCollectionByUprnAndDate"
|
26
|
+
subscription_key = "2ea6a75f9ea34bb58d299a0c9f84e72e"
|
27
|
+
|
28
|
+
# Get today's date in 'YYYY-MM-DD' format
|
29
|
+
collection_date = datetime.now().strftime("%Y-%m-%d")
|
30
|
+
|
31
|
+
# Define the request headers
|
32
|
+
headers = {
|
33
|
+
"Content-Type": "application/json",
|
34
|
+
"Ocp-Apim-Subscription-Key": subscription_key,
|
35
|
+
}
|
36
|
+
|
37
|
+
# Define the request body
|
38
|
+
data = {
|
39
|
+
"getCollectionByUprnAndDate": {
|
40
|
+
"getCollectionByUprnAndDateInput": {
|
41
|
+
"uprn": user_uprn,
|
42
|
+
"nextCollectionFromDate": collection_date,
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
# Make the POST request
|
47
|
+
response = requests.post(endpoint, headers=headers, data=json.dumps(data))
|
48
|
+
response.raise_for_status() # Raise an exception for HTTP errors
|
49
|
+
|
50
|
+
# Parse the JSON response
|
51
|
+
response_data = response.json()
|
52
|
+
|
53
|
+
collections = (
|
54
|
+
response_data.get("getCollectionByUprnAndDateResponse", {})
|
55
|
+
.get("getCollectionByUprnAndDateResult", {})
|
56
|
+
.get("Collections", [])
|
57
|
+
)
|
58
|
+
|
59
|
+
for collection in collections:
|
60
|
+
bin_type = collection["service"]
|
61
|
+
collection_date = collection["date"]
|
62
|
+
|
63
|
+
dict_data = {
|
64
|
+
"type": bin_type,
|
65
|
+
"collectionDate": datetime.strptime(
|
66
|
+
collection_date,
|
67
|
+
"%d/%m/%Y %H:%M:%S",
|
68
|
+
).strftime(date_format),
|
69
|
+
}
|
70
|
+
bindata["bins"].append(dict_data)
|
71
|
+
bindata["bins"].sort(
|
72
|
+
key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
|
73
|
+
)
|
74
|
+
|
75
|
+
return bindata
|