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,103 @@
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ from uk_bin_collection.uk_bin_collection.common import *
4
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
5
+
6
+
7
+ # import the wonderful Beautiful Soup and the URL grabber
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
+ user_uprn = kwargs.get("uprn")
17
+ check_uprn(user_uprn)
18
+
19
+ # UPRN is passed in via a cookie. Set cookies/params and GET the page
20
+ cookies = {
21
+ "SVBINZONE": f"VALE%3AUPRN%40{user_uprn}",
22
+ }
23
+ headers = {
24
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
25
+ "Accept-Language": "en-GB,en;q=0.7",
26
+ "Cache-Control": "max-age=0",
27
+ "Connection": "keep-alive",
28
+ "Referer": "https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb?SOVA_TAG=VALE&ebd=0&ebz=1_1704201201813",
29
+ "Sec-Fetch-Dest": "document",
30
+ "Sec-Fetch-Mode": "navigate",
31
+ "Sec-Fetch-Site": "same-origin",
32
+ "Sec-Fetch-User": "?1",
33
+ "Sec-GPC": "1",
34
+ "Upgrade-Insecure-Requests": "1",
35
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
36
+ }
37
+ params = {
38
+ "SOVA_TAG": "VALE",
39
+ "ebd": "0",
40
+ }
41
+ requests.packages.urllib3.disable_warnings()
42
+ response = requests.get(
43
+ "https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb",
44
+ params=params,
45
+ headers=headers,
46
+ cookies=cookies,
47
+ )
48
+
49
+ # Parse response text for super speedy finding
50
+ soup = BeautifulSoup(response.text, features="html.parser")
51
+ soup.prettify()
52
+
53
+ data = {"bins": []}
54
+
55
+ # Page has slider info side by side, which are two instances of this class
56
+ for bin in soup.find_all("div", {"class": "bintxt"}):
57
+ try:
58
+ # Check bin type heading and make that bin type and colour
59
+ bin_type_info = list(bin.stripped_strings)
60
+ if "rubbish" in bin_type_info[0]:
61
+ bin_type = "Rubbish"
62
+ bin_colour = "Black"
63
+ elif "recycling" in bin_type_info[0]:
64
+ bin_type = "Recycling"
65
+ bin_colour = "Green"
66
+ else:
67
+ raise ValueError(f"No bin info found in {bin_type_info[0]}")
68
+
69
+ bin_date_info = list(
70
+ bin.find_next("div", {"class": "binextra"}).stripped_strings
71
+ )
72
+ # On standard collection schedule, date will be contained in the first string
73
+ if contains_date(bin_date_info[0]):
74
+ bin_date = get_next_occurrence_from_day_month(
75
+ datetime.strptime(
76
+ bin_date_info[0] + " " + datetime.today().strftime("%Y"),
77
+ "%A %d %B - %Y",
78
+ )
79
+ ).strftime(date_format)
80
+ # On exceptional collection schedule (e.g. around English Bank Holidays), date will be contained in the second stripped string
81
+ else:
82
+ bin_date = get_next_occurrence_from_day_month(
83
+ datetime.strptime(
84
+ bin_date_info[1] + " " + datetime.today().strftime("%Y"),
85
+ "%A %d %B - %Y",
86
+ )
87
+ ).strftime(date_format)
88
+ except Exception as ex:
89
+ raise ValueError(f"Error parsing bin data: {ex}")
90
+
91
+ # Build data dict for each entry
92
+ dict_data = {
93
+ "type": bin_type,
94
+ "collectionDate": bin_date,
95
+ "colour": bin_colour,
96
+ }
97
+ data["bins"].append(dict_data)
98
+
99
+ data["bins"].sort(
100
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
101
+ )
102
+
103
+ return data
@@ -0,0 +1,89 @@
1
+ from bs4 import BeautifulSoup
2
+
3
+ from uk_bin_collection.uk_bin_collection.common import *
4
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
5
+
6
+
7
+ # import the wonderful Beautiful Soup and the URL grabber
8
+ class CouncilClass(AbstractGetBinDataClass):
9
+ """
10
+ Concrete classes have to implement all abstract operations of the base
11
+ class. They can also override some operations with a default
12
+ implementation.
13
+ """
14
+
15
+ def parse_data(self, page: str, **kwargs) -> dict:
16
+ driver = None
17
+ try:
18
+ # Create Selenium webdriver
19
+ headless = kwargs.get("headless")
20
+ driver = create_webdriver(kwargs.get("web_driver"), headless)
21
+ driver.get(kwargs.get("url"))
22
+
23
+ # Make a BS4 object
24
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
25
+ soup.prettify()
26
+
27
+ data = {"bins": []}
28
+ sections = soup.find_all("div", {"class": "wil_c-content-section_heading"})
29
+ for s in sections:
30
+ if s.get_text(strip=True).lower() == "bin collections":
31
+ rows = s.find_next_sibling(
32
+ "div", {"class": "c-content-section_body"}
33
+ ).find_all("div", {"class": "u-mb-8"})
34
+ for row in rows:
35
+ title = row.find("div", {"class": "u-mb-4"})
36
+ collections = row.find_all("div", {"class": "u-mb-2"})
37
+ if title and collections:
38
+ for c in collections:
39
+ if (
40
+ c.get_text(strip=True)
41
+ .lower()
42
+ .startswith("next collection")
43
+ ):
44
+ # add next collection
45
+ next_collection_date = datetime.strptime(
46
+ c.get_text(strip=True).replace(
47
+ "Next collection - ", ""
48
+ ),
49
+ "%A, %d %B %Y",
50
+ ).strftime(date_format)
51
+ dict_data = {
52
+ "type": title.get_text(strip=True).capitalize(),
53
+ "collectionDate": next_collection_date,
54
+ }
55
+ data["bins"].append(dict_data)
56
+ # add future collections without duplicating next collection
57
+ future_collections = row.find(
58
+ "ul", {"class": "u-mt-4"}
59
+ ).find_all("li")
60
+ for c in future_collections:
61
+ future_collection_date = datetime.strptime(
62
+ c.get_text(strip=True),
63
+ "%A, %d %B %Y",
64
+ ).strftime(date_format)
65
+ if (
66
+ future_collection_date
67
+ != next_collection_date
68
+ ):
69
+ dict_data = {
70
+ "type": title.get_text(
71
+ strip=True
72
+ ).capitalize(),
73
+ "collectionDate": future_collection_date,
74
+ }
75
+ data["bins"].append(dict_data)
76
+
77
+ data["bins"].sort(
78
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
79
+ )
80
+ except Exception as e:
81
+ # Here you can log the exception if needed
82
+ print(f"An error occurred: {e}")
83
+ # Optionally, re-raise the exception if you want it to propagate
84
+ raise
85
+ finally:
86
+ # This block ensures that the driver is closed regardless of an exception
87
+ if driver:
88
+ driver.quit()
89
+ return data
@@ -0,0 +1,34 @@
1
+ # This script pulls (in one hit) the data
2
+ # from Warick District Council Bins Data
3
+
4
+ from bs4 import BeautifulSoup
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
+ baseclass. They can also override some
13
+ operations with a default implementation.
14
+ """
15
+
16
+ def parse_data(self, page: str, **kwargs) -> dict:
17
+ # Make a BS4 object
18
+ soup = BeautifulSoup(page.text, features="html.parser")
19
+ soup.prettify()
20
+
21
+ data = {"bins": []}
22
+
23
+ for element in soup.find_all("strong"):
24
+ bin_type = element.next_element
25
+ bin_type = bin_type.lstrip()
26
+ collectionDateElement = element.next_sibling.next_element.next_element
27
+ collectionDate = collectionDateElement.getText()
28
+ dict_data = {
29
+ "type": bin_type,
30
+ "collectionDate": collectionDate,
31
+ }
32
+ data["bins"].append(dict_data)
33
+
34
+ return data
@@ -0,0 +1,119 @@
1
+ from datetime import date, datetime
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from uk_bin_collection.uk_bin_collection.common import *
6
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
7
+
8
+
9
+ # 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
+ # pindex isn't actually paon, it's a url parameter that I'm guessing the council use as a property id
19
+ data = {"bins": []}
20
+ pindex = kwargs.get("paon")
21
+ user_postcode = kwargs.get("postcode")
22
+ check_postcode(user_postcode)
23
+
24
+ # WBC use a url parameter called "Track" that's generated when you start a form session.
25
+ # So first off, open the page, find the page link and copy it with the Track
26
+ start_url = "https://wav-wrp.whitespacews.com/"
27
+ s = requests.session()
28
+ response = s.get(start_url)
29
+ soup = BeautifulSoup(response.content, features="html.parser")
30
+ soup.prettify()
31
+ collection_page_link = soup.find_all(
32
+ "p", {"class": "govuk-body govuk-!-margin-bottom-0 colorblue lineheight15"}
33
+ )[0].find("a")["href"]
34
+ track_id = collection_page_link[33:60]
35
+
36
+ # Next we need to search using the postcode, but this is actually an important POST request
37
+ pc_headers = {
38
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
39
+ "image/webp,image/apng,*/*;q=0.8",
40
+ "Accept-Language": "en-GB,en;q=0.9",
41
+ "Cache-Control": "max-age=0",
42
+ "Connection": "keep-alive",
43
+ "Origin": "https://wav-wrp.whitespacews.com",
44
+ "Referer": "https://wav-wrp.whitespacews.com/"
45
+ + track_id
46
+ + "&serviceID=A&seq=2",
47
+ "Sec-Fetch-Dest": "document",
48
+ "Sec-Fetch-Mode": "navigate",
49
+ "Sec-Fetch-Site": "same-origin",
50
+ "Sec-Fetch-User": "?1",
51
+ "Sec-GPC": "1",
52
+ "Upgrade-Insecure-Requests": "1",
53
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
54
+ "like Gecko) Chrome/106.0.0.0 Safari/537.36",
55
+ }
56
+ form_data = {
57
+ "address_name_number": "",
58
+ "address_street": "",
59
+ "street_town": "",
60
+ "address_postcode": user_postcode,
61
+ }
62
+ response = s.post(
63
+ "https://wav-wrp.whitespacews.com/mop.php?serviceID=A&"
64
+ + track_id
65
+ + "&seq=2",
66
+ headers=pc_headers,
67
+ data=form_data,
68
+ )
69
+
70
+ # Finally, we can use pindex to find the address and get some data
71
+ request_url = (
72
+ "https://wav-wrp.whitespacews.com/mop.php?"
73
+ + track_id
74
+ + "&serviceID=A&seq=3&pIndex="
75
+ + pindex
76
+ )
77
+ headers = {
78
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
79
+ "image/webp,image/apng,*/*;q=0.8",
80
+ "Accept-Language": "en-GB,en;q=0.9",
81
+ "Connection": "keep-alive",
82
+ "Referer": "https://wav-wrp.whitespacews.com/mop.php?serviceID=A&"
83
+ + track_id
84
+ + "&seq=2",
85
+ "Sec-Fetch-Dest": "document",
86
+ "Sec-Fetch-Mode": "navigate",
87
+ "Sec-Fetch-Site": "same-origin",
88
+ "Sec-Fetch-User": "?1",
89
+ "Sec-GPC": "1",
90
+ "Upgrade-Insecure-Requests": "1",
91
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
92
+ "like Gecko) Chrome/106.0.0.0 Safari/537.36",
93
+ }
94
+
95
+ response = s.get(request_url, headers=headers)
96
+ soup = BeautifulSoup(response.content, features="html.parser")
97
+ soup.prettify()
98
+
99
+ # Find the list elements
100
+ u1_block = soup.find_all(
101
+ "u1",
102
+ {
103
+ "class": "displayinlineblock justifycontentleft alignitemscenter margin0 padding0"
104
+ },
105
+ )
106
+
107
+ for element in u1_block:
108
+ x = element.find_all_next(
109
+ "li", {"class": "displayinlineblock padding0px20px5px0px"}
110
+ )
111
+ dict_data = {
112
+ "type": x[2].text.strip(),
113
+ "collectionDate": datetime.strptime(
114
+ x[1].text.strip(), date_format
115
+ ).strftime(date_format),
116
+ }
117
+ data["bins"].append(dict_data)
118
+
119
+ return data
@@ -0,0 +1,86 @@
1
+ import json
2
+
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from uk_bin_collection.uk_bin_collection.common import *
6
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
7
+
8
+
9
+ # 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
+ user_uprn = kwargs.get("uprn")
19
+ check_uprn(user_uprn)
20
+
21
+ headers = {
22
+ "authority": "www.wealden.gov.uk",
23
+ "accept": "*/*",
24
+ "accept-language": "en-GB,en;q=0.7",
25
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
26
+ # Requests sorts cookies= alphabetically
27
+ # 'cookie': 'ARRAffinity=e45c20b343b490e3866d5d35c3dbda687e4a1357c2163c32922209862abb5872; ARRAffinitySameSite=e45c20b343b490e3866d5d35c3dbda687e4a1357c2163c32922209862abb5872',
28
+ "origin": "https://www.wealden.gov.uk",
29
+ "referer": "https://www.wealden.gov.uk/recycling-and-waste/bin-search/?uprn=10033413624",
30
+ "sec-fetch-dest": "empty",
31
+ "sec-fetch-mode": "cors",
32
+ "sec-fetch-site": "same-origin",
33
+ "sec-gpc": "1",
34
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
35
+ "x-requested-with": "XMLHttpRequest",
36
+ }
37
+
38
+ data = {
39
+ "action": "wealden_get_collections_for_uprn",
40
+ "uprn": user_uprn,
41
+ }
42
+
43
+ requests.packages.urllib3.disable_warnings()
44
+ response = requests.post(
45
+ "https://www.wealden.gov.uk/wp-admin/admin-ajax.php",
46
+ headers=headers,
47
+ data=data,
48
+ )
49
+ json_data = json.loads(response.text)
50
+
51
+ if json_data["status"] != "success":
52
+ raise ValueError("Error parsing data. Please open an issue on GitHub.")
53
+
54
+ property_data = json_data["collection"]
55
+ data = {"bins": []}
56
+ collections = []
57
+
58
+ if len(property_data["refuseCollectionDate"]) > 0:
59
+ bin_type = "Rubbish Bin"
60
+ bin_date = datetime.strptime(
61
+ property_data["refuseCollectionDate"], "%Y-%m-%dT%H:%M:%S"
62
+ )
63
+ collections.append((bin_type, bin_date))
64
+ if len(property_data["recyclingCollectionDate"]) > 0:
65
+ bin_type = "Recycling Bin"
66
+ bin_date = datetime.strptime(
67
+ property_data["recyclingCollectionDate"], "%Y-%m-%dT%H:%M:%S"
68
+ )
69
+ collections.append((bin_type, bin_date))
70
+ if len(property_data["gardenCollectionDate"]) > 0:
71
+ bin_type = "Garden Bin"
72
+ bin_date = datetime.strptime(
73
+ property_data["gardenCollectionDate"], "%Y-%m-%dT%H:%M:%S"
74
+ )
75
+ collections.append((bin_type, bin_date))
76
+
77
+ ordered_data = sorted(collections, key=lambda x: x[1])
78
+ data = {"bins": []}
79
+ for item in ordered_data:
80
+ dict_data = {
81
+ "type": item[0],
82
+ "collectionDate": item[1].strftime(date_format),
83
+ }
84
+ data["bins"].append(dict_data)
85
+
86
+ return data
@@ -0,0 +1,73 @@
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
+ def get_token(page) -> str:
7
+ """
8
+ Get a __token to include in the form data
9
+ :param page: Page html
10
+ :return: Form __token
11
+ """
12
+ soup = BeautifulSoup(page.text, features="html.parser")
13
+ soup.prettify()
14
+ token = soup.find("input", {"name": "__token"}).get("value")
15
+ return token
16
+
17
+
18
+ class CouncilClass(AbstractGetBinDataClass):
19
+ """
20
+ Concrete classes have to implement all abstract operations of the
21
+ base class. They can also override some operations with a default
22
+ implementation.
23
+ """
24
+
25
+ def parse_data(self, page: str, **kwargs) -> dict:
26
+ uprn = kwargs.get("uprn")
27
+ postcode = kwargs.get("postcode")
28
+ check_uprn(uprn)
29
+ check_postcode(postcode)
30
+
31
+ values = {
32
+ "__token": get_token(page),
33
+ "page": "492",
34
+ "locale": "en_GB",
35
+ "q9f451fe0ca70775687eeedd1e54b359e55f7c10c_0_0": postcode,
36
+ "q9f451fe0ca70775687eeedd1e54b359e55f7c10c_1_0": uprn,
37
+ "next": "Next",
38
+ }
39
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
40
+ requests.packages.urllib3.disable_warnings()
41
+ response = requests.request(
42
+ "POST",
43
+ "https://www.welhat.gov.uk/xfp/form/214",
44
+ headers=headers,
45
+ data=values,
46
+ )
47
+
48
+ soup = BeautifulSoup(response.text, features="html.parser")
49
+
50
+ rows = soup.find("table").find_all("tr")
51
+
52
+ # Form a JSON wrapper
53
+ data = {"bins": []}
54
+
55
+ # Loops the Rows
56
+ for row in rows:
57
+ cells = row.find_all("td")
58
+ if cells:
59
+ binType = cells[0].get_text(strip=True)
60
+ collectionDate = datetime.strptime(
61
+ cells[1].get_text(strip=True), "%A %d %B %Y"
62
+ ).strftime(date_format)
63
+
64
+ # Make each Bin element in the JSON
65
+ dict_data = {
66
+ "type": binType,
67
+ "collectionDate": collectionDate,
68
+ }
69
+
70
+ # Add data to the main JSON Wrapper
71
+ data["bins"].append(dict_data)
72
+
73
+ return data
@@ -0,0 +1,134 @@
1
+ import time
2
+
3
+ from bs4 import BeautifulSoup
4
+ from dateutil.relativedelta import relativedelta
5
+ from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.support import expected_conditions as EC
7
+ from selenium.webdriver.support.ui import Select
8
+ from selenium.webdriver.support.wait import WebDriverWait
9
+
10
+ from uk_bin_collection.uk_bin_collection.common import *
11
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
12
+
13
+
14
+ # import the wonderful Beautiful Soup and the URL grabber
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
+ data = {"bins": []}
26
+ collections = []
27
+ user_paon = kwargs.get("paon")
28
+ user_postcode = kwargs.get("postcode")
29
+ web_driver = kwargs.get("web_driver")
30
+ headless = kwargs.get("headless")
31
+ check_paon(user_paon)
32
+ check_postcode(user_postcode)
33
+
34
+ # Create Selenium webdriver
35
+ # driver = create_webdriver(web_driver, headless)
36
+ driver = webdriver.Chrome(
37
+ service=ChromeService(ChromeDriverManager().install())
38
+ )
39
+ driver.get("https://www.westberks.gov.uk/binday")
40
+
41
+ # Wait for the postcode field to appear then populate it
42
+ inputElement_postcode = WebDriverWait(driver, 30).until(
43
+ EC.presence_of_element_located(
44
+ (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPPOSTCODE")
45
+ )
46
+ )
47
+ inputElement_postcode.send_keys(user_postcode)
48
+
49
+ # Click search button
50
+ findAddress = WebDriverWait(driver, 10).until(
51
+ EC.presence_of_element_located(
52
+ (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPSEARCH")
53
+ )
54
+ )
55
+ findAddress.click()
56
+
57
+ WebDriverWait(driver, 10).until(
58
+ EC.element_to_be_clickable(
59
+ (
60
+ By.XPATH,
61
+ ""
62
+ "//*[@id='FINDYOURBINDAYS_ADDRESSLOOKUPADDRESS']//option[contains(., '"
63
+ + user_paon
64
+ + "')]",
65
+ )
66
+ )
67
+ ).click()
68
+
69
+ # Wait for the submit button to appear, then click it to get the collection dates
70
+ WebDriverWait(driver, 30).until(
71
+ EC.presence_of_element_located(
72
+ (By.XPATH, '//*[@id="FINDYOURBINDAYS_RUBBISHDATE"]/div')
73
+ )
74
+ )
75
+ time.sleep(2)
76
+
77
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
78
+ soup.prettify()
79
+
80
+ rubbish_date = datetime.strptime(
81
+ " ".join(
82
+ soup.find("div", {"id": "FINDYOURBINDAYS_RUBBISHDATE_OUTERDIV"})
83
+ .get_text(strip=True)
84
+ .split()[6:8]
85
+ ),
86
+ "%d %B",
87
+ ).replace(year=datetime.now().year)
88
+ recycling_date = datetime.strptime(
89
+ " ".join(
90
+ soup.find("div", {"id": "FINDYOURBINDAYS_RECYCLINGDATE_OUTERDIV"})
91
+ .get_text(strip=True)
92
+ .split()[6:8]
93
+ ),
94
+ "%d %B",
95
+ ).replace(year=datetime.now().year)
96
+ food_date = datetime.strptime(
97
+ " ".join(
98
+ soup.find("div", {"id": "FINDYOURBINDAYS_FOODWASTEDATE_OUTERDIV"})
99
+ .get_text(strip=True)
100
+ .split()[8:10]
101
+ ),
102
+ "%d %B",
103
+ ).replace(year=datetime.now().year)
104
+
105
+ if datetime.now().month == 12 and rubbish_date.month == 1:
106
+ rubbish_date = rubbish_date + relativedelta(years=1)
107
+ if datetime.now().month == 12 and recycling_date.month == 1:
108
+ recycling_date = recycling_date + relativedelta(years=1)
109
+ if datetime.now().month == 12 and food_date.month == 1:
110
+ food_date = food_date + relativedelta(years=1)
111
+
112
+ collections.append(("Rubbish bin", rubbish_date))
113
+ collections.append(("Recycling bin", recycling_date))
114
+ collections.append(("Food waste bin", food_date))
115
+
116
+ ordered_data = sorted(collections, key=lambda x: x[1])
117
+ for item in ordered_data:
118
+ dict_data = {
119
+ "type": item[0].capitalize(),
120
+ "collectionDate": item[1].strftime(date_format),
121
+ }
122
+ data["bins"].append(dict_data)
123
+
124
+ print()
125
+ except Exception as e:
126
+ # Here you can log the exception if needed
127
+ print(f"An error occurred: {e}")
128
+ # Optionally, re-raise the exception if you want it to propagate
129
+ raise
130
+ finally:
131
+ # This block ensures that the driver is closed regardless of an exception
132
+ if driver:
133
+ driver.quit()
134
+ return data