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.
Files changed (171) hide show
  1. uk_bin_collection/README.rst +0 -0
  2. uk_bin_collection/tests/council_feature_input_parity.py +79 -0
  3. uk_bin_collection/tests/features/environment.py +7 -0
  4. uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
  5. uk_bin_collection/tests/input.json +1077 -0
  6. uk_bin_collection/tests/output.schema +41 -0
  7. uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
  8. uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
  9. uk_bin_collection/tests/test_collect_data.py +104 -0
  10. uk_bin_collection/tests/test_common_functions.py +342 -0
  11. uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
  12. uk_bin_collection/uk_bin_collection/common.py +292 -0
  13. uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
  14. uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
  15. uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
  16. uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
  17. uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
  18. uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
  19. uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
  20. uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
  21. uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
  22. uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
  23. uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
  24. uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
  25. uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
  26. uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
  27. uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
  28. uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
  29. uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
  30. uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
  31. uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
  32. uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
  33. uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
  34. uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
  35. uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
  36. uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
  37. uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
  38. uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
  39. uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
  40. uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
  41. uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
  42. uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
  43. uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
  44. uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
  45. uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
  46. uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
  47. uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
  48. uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
  49. uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
  50. uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
  51. uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
  52. uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
  53. uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
  54. uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
  55. uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
  56. uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
  57. uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
  58. uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
  59. uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
  60. uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
  61. uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
  62. uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
  63. uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
  64. uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
  65. uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
  66. uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
  67. uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
  68. uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
  69. uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
  70. uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
  71. uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
  72. uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
  73. uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
  74. uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
  75. uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
  76. uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
  77. uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
  78. uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
  79. uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
  80. uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
  81. uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
  82. uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
  83. uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
  84. uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
  85. uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
  86. uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
  87. uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
  88. uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
  89. uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
  90. uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
  91. uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
  92. uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
  93. uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
  94. uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
  95. uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
  96. uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
  97. uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
  98. uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
  99. uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
  100. uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
  101. uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
  102. uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
  103. uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
  104. uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
  105. uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
  106. uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
  107. uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
  108. uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
  109. uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
  110. uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
  111. uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
  112. uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
  113. uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
  114. uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
  115. uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
  116. uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
  117. uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
  118. uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
  119. uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
  120. uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
  121. uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
  122. uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
  123. uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
  124. uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
  125. uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
  126. uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
  127. uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
  128. uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
  129. uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
  130. uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
  131. uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
  132. uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
  133. uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
  134. uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
  135. uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
  136. uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
  137. uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
  138. uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
  139. uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
  140. uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
  141. uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
  142. uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
  143. uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
  144. uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
  145. uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
  146. uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
  147. uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
  148. uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
  149. uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
  150. uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
  151. uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
  152. uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
  153. uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
  154. uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
  155. uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
  156. uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
  157. uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
  158. uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
  159. uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
  160. uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
  161. uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
  162. uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
  163. uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
  164. uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
  165. uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
  166. uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
  167. uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
  168. uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
  169. uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
  170. uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
  171. 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