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,142 @@
1
+ from datetime import datetime
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
+ from bs4 import BeautifulSoup
6
+ from datetime import datetime
7
+ from selenium.webdriver.common.by import By
8
+ from selenium.webdriver.support import expected_conditions as EC
9
+ from selenium.webdriver.support.ui import Select
10
+ from selenium.webdriver.support.wait import WebDriverWait
11
+ from selenium.webdriver.common.keys import Keys
12
+
13
+ import pandas as pd
14
+ import urllib.request
15
+
16
+
17
+ class CouncilClass(AbstractGetBinDataClass):
18
+ """
19
+ Concrete classes have to implement all abstract operations of the base
20
+ class. They can also override some operations with a default
21
+ implementation.
22
+ """
23
+
24
+ def parse_data(self, page: str, **kwargs) -> dict:
25
+ driver = None
26
+ try:
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
+
32
+ # Create Selenium webdriver
33
+ page = f"https://www.eastriding.gov.uk/environment/bins-rubbish-recycling/bins-and-collections/bin-collection-dates/"
34
+
35
+ driver = create_webdriver(web_driver, headless)
36
+ driver.get(page)
37
+
38
+ wait = WebDriverWait(driver, 60)
39
+
40
+ try:
41
+ accept_cookies = wait.until(
42
+ EC.element_to_be_clickable(
43
+ (By.XPATH, '//*[@id="er-cookie-placeholder-settings"]/div/a[1]')
44
+ )
45
+ )
46
+
47
+ accept_cookies.click()
48
+ except:
49
+ print(
50
+ "Cookies acceptance element not found or clickable within the specified time."
51
+ )
52
+ pass
53
+
54
+ expand_postcode_box = wait.until(
55
+ EC.element_to_be_clickable(
56
+ (By.ID, "when-and-where-should-i-put-my-bin-out")
57
+ )
58
+ )
59
+
60
+ expand_postcode_box.click()
61
+
62
+ postcode_box = wait.until(
63
+ EC.element_to_be_clickable(
64
+ (By.XPATH, "//input[@placeholder='Enter your postcode']")
65
+ )
66
+ )
67
+ postcode_box.send_keys(user_postcode)
68
+
69
+ postcode_search_btn = wait.until(
70
+ EC.element_to_be_clickable((By.XPATH, "//button[text()='Search']"))
71
+ )
72
+ postcode_search_btn.send_keys(Keys.ENTER)
73
+
74
+ dropdown = wait.until(
75
+ EC.presence_of_element_located(
76
+ (
77
+ By.XPATH,
78
+ "//div[@class='er-select-wrapper']/select[@class='dropdown']",
79
+ )
80
+ )
81
+ )
82
+
83
+ options_present = wait.until(
84
+ EC.presence_of_all_elements_located(
85
+ (By.CSS_SELECTOR, "select.dropdown option")
86
+ )
87
+ )
88
+ drop = Select(dropdown)
89
+ drop.select_by_visible_text(str(user_paon))
90
+
91
+ results_present = wait.until(
92
+ EC.presence_of_element_located(
93
+ (
94
+ By.CLASS_NAME,
95
+ "results",
96
+ )
97
+ )
98
+ )
99
+
100
+ data = {"bins": []} # dictionary for data
101
+ soup = BeautifulSoup(driver.page_source, "html.parser")
102
+
103
+ bin_types = {} # Dictionary to store bin types
104
+
105
+ # Extract bin types from header elements
106
+ header_elements = soup.find_all(
107
+ "li", class_=lambda x: x and x.startswith("header")
108
+ )
109
+ for header_element in header_elements:
110
+ bin_type = header_element.get_text(strip=True)
111
+ bin_class = [
112
+ cls for cls in header_element.get("class") if cls != "header"
113
+ ]
114
+ if bin_class:
115
+ bin_types[bin_class[0]] = bin_type
116
+
117
+ # Extract collection dates and associate them with respective bin types
118
+ date_elements = soup.find_all(
119
+ "li", class_=lambda x: x and x.startswith("date")
120
+ )
121
+ for date_element in date_elements:
122
+ bin_class = [cls for cls in date_element.get("class") if cls != "date"]
123
+ if bin_class:
124
+ bin_type = bin_types.get(bin_class[0])
125
+ span_text = date_element.find("span").get_text(strip=True)
126
+ collection_date = datetime.strptime(span_text, "%a, %d %b %Y")
127
+ data["bins"].append(
128
+ {
129
+ "type": bin_type,
130
+ "collectionDate": collection_date.strftime(date_format),
131
+ }
132
+ )
133
+ except Exception as e:
134
+ # Here you can log the exception if needed
135
+ print(f"An error occurred: {e}")
136
+ # Optionally, re-raise the exception if you want it to propagate
137
+ raise
138
+ finally:
139
+ # This block ensures that the driver is closed regardless of an exception
140
+ if driver:
141
+ driver.quit()
142
+ return data
@@ -0,0 +1,112 @@
1
+ from time import sleep
2
+ from bs4 import BeautifulSoup
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 WebDriverWait, Select
6
+
7
+ from uk_bin_collection.uk_bin_collection.common import *
8
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
9
+
10
+
11
+ # import the wonderful Beautiful Soup and the URL grabber
12
+ class CouncilClass(AbstractGetBinDataClass):
13
+ """
14
+ Concrete classes have to implement all abstract operations of the base
15
+ class. They can also override some operations with a default
16
+ implementation.
17
+ """
18
+
19
+ def parse_data(self, page: str, **kwargs) -> dict:
20
+ driver = None
21
+ try:
22
+ user_uprn = kwargs.get("uprn")
23
+ user_postcode = kwargs.get("postcode")
24
+ web_driver = kwargs.get("web_driver")
25
+ headless = kwargs.get("headless")
26
+ check_uprn(user_uprn)
27
+ check_postcode(user_postcode)
28
+
29
+ # Create Selenium webdriver
30
+ driver = create_webdriver(web_driver, headless)
31
+ driver.get(
32
+ "https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder"
33
+ )
34
+
35
+ # Wait for iframe to load and switch to it
36
+ WebDriverWait(driver, 30).until(
37
+ EC.frame_to_be_available_and_switch_to_it((By.ID, "fillform-frame-1"))
38
+ )
39
+
40
+ # Wait for postcode entry box
41
+ postcode = WebDriverWait(driver, 10).until(
42
+ EC.presence_of_element_located((By.ID, "alt_postcode_search"))
43
+ )
44
+ # Enter postcode
45
+ postcode.send_keys(user_postcode.replace(" ", ""))
46
+
47
+ # Wait for address selection dropdown to appear
48
+ address = Select(
49
+ WebDriverWait(driver, 10).until(
50
+ EC.visibility_of_element_located((By.ID, "alt_choose_address"))
51
+ )
52
+ )
53
+
54
+ # Wait for spinner to disappear (signifies options are loaded for select)
55
+ WebDriverWait(driver, 10).until(
56
+ EC.invisibility_of_element_located((By.CLASS_NAME, "spinner-outer"))
57
+ )
58
+
59
+ # Sometimes the options aren't fully there despite the spinner being gone, wait another 2 seconds.
60
+ sleep(2)
61
+
62
+ # Select address by UPRN
63
+ address.select_by_value(user_uprn)
64
+
65
+ # Wait for spinner to disappear (signifies data is loaded)
66
+ WebDriverWait(driver, 10).until(
67
+ EC.invisibility_of_element_located((By.CLASS_NAME, "spinner-outer"))
68
+ )
69
+
70
+ sleep(2)
71
+
72
+ # Find data table
73
+ data_table = WebDriverWait(driver, 10).until(
74
+ EC.presence_of_element_located(
75
+ (
76
+ By.XPATH,
77
+ '//div[@data-field-name="collection_details"]/div[contains(@class, "fieldContent")]/div[contains(@class, "repeatable-table-wrapper")]',
78
+ )
79
+ )
80
+ )
81
+
82
+ # Make a BS4 object
83
+ soup = BeautifulSoup(
84
+ data_table.get_attribute("innerHTML"), features="html.parser"
85
+ )
86
+
87
+ data = {"bins": []}
88
+
89
+ rows = soup.find("table").find("tbody").find_all("tr")
90
+ for row in rows:
91
+ cols = row.find_all("td")
92
+ bin_type = cols[2].find_all("span")[1].text.title()
93
+ collection_date = cols[3].find_all("span")[1].text
94
+ collection_date = datetime.strptime(
95
+ collection_date, "%d/%m/%Y"
96
+ ).strftime(date_format)
97
+ dict_data = {"type": bin_type, "collectionDate": collection_date}
98
+ data["bins"].append(dict_data)
99
+
100
+ data["bins"].sort(
101
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
102
+ )
103
+ except Exception as e:
104
+ # Here you can log the exception if needed
105
+ print(f"An error occurred: {e}")
106
+ # Optionally, re-raise the exception if you want it to propagate
107
+ raise
108
+ finally:
109
+ # This block ensures that the driver is closed regardless of an exception
110
+ if driver:
111
+ driver.quit()
112
+ return data
@@ -0,0 +1,70 @@
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
+ uprn = kwargs.get("uprn")
16
+ # Check the UPRN is valid
17
+ check_uprn(uprn)
18
+
19
+ # Request URL
20
+ url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}"
21
+
22
+ # Make Request
23
+ requests.packages.urllib3.disable_warnings()
24
+ page = requests.get(url)
25
+
26
+ # Make a BS4 object
27
+ soup = BeautifulSoup(page.text, features="html.parser")
28
+ soup.prettify()
29
+
30
+ # Data to return
31
+ data = {"bins": []}
32
+
33
+ # Valid bin types
34
+ binTypes = [
35
+ "Household Waste Bin",
36
+ "Recycling Bin",
37
+ "Food Waste Bin",
38
+ "Glass Box and Batteries",
39
+ "Garden Waste Bin",
40
+ ]
41
+
42
+ # Value to create dict for DL values
43
+ keys, values = [], []
44
+
45
+ # Loop though DT and DD for DL containing bins
46
+ dl = soup.find("dl", {"class": "dl-horizontal"})
47
+ for dt in dl.find_all("dt"):
48
+ keys.append(dt.text.strip())
49
+ for dd in dl.find_all("dd"):
50
+ values.append(dd.text.strip())
51
+
52
+ # Create dict for bin name and string dates
53
+ binDict = dict(zip(keys, values))
54
+
55
+ # Process dict for valid bin types
56
+ for bin in list(binDict):
57
+ if bin in binTypes:
58
+ if not binDict[bin].startswith("You haven't yet signed up for"):
59
+ # Convert date
60
+ date = datetime.strptime(binDict[bin], "%a, %d %b %Y")
61
+
62
+ # Set bin data
63
+ dict_data = {
64
+ "type": bin,
65
+ "collectionDate": date.strftime(date_format),
66
+ }
67
+ data["bins"].append(dict_data)
68
+
69
+ # Return bin data
70
+ return data
@@ -0,0 +1,48 @@
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
+ # Get the paragraph lines from the page
20
+ data = {"bins": []}
21
+ page_text = soup.find("div", {"class": "collect"}).find_all("p")
22
+
23
+ # Parse the correct lines (find them, remove the ordinal indicator and make them the correct format date) and
24
+ # then add them to the dictionary
25
+ rubbish_day = datetime.strptime(
26
+ remove_ordinal_indicator_from_date_string(
27
+ page_text[2].find_next("strong").text
28
+ ),
29
+ "%d %B %Y",
30
+ ).strftime(date_format)
31
+ dict_data = {
32
+ "type": "Rubbish",
33
+ "collectionDate": rubbish_day,
34
+ }
35
+ data["bins"].append(dict_data)
36
+ recycling_day = datetime.strptime(
37
+ remove_ordinal_indicator_from_date_string(
38
+ page_text[4].find_next("strong").text
39
+ ),
40
+ "%d %B %Y",
41
+ ).strftime(date_format)
42
+ dict_data = {
43
+ "type": "Recycling",
44
+ "collectionDate": recycling_day,
45
+ }
46
+ data["bins"].append(dict_data)
47
+
48
+ return data
@@ -0,0 +1,61 @@
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
+ data = {"bins": []}
16
+ uprn = kwargs.get("uprn")
17
+ check_uprn(uprn)
18
+
19
+ requests.packages.urllib3.disable_warnings()
20
+ response = requests.get(
21
+ f"https://map.erewash.gov.uk/isharelive.web/myerewash.aspx?action=SetAddress&UniqueId={uprn}",
22
+ headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"},
23
+ )
24
+
25
+ soup = BeautifulSoup(response.text, features="html.parser")
26
+ collections = soup.find("div", {"aria-label": "Waste Collection"}).find_all(
27
+ "div", {"class": "atPanelContent"}
28
+ )
29
+ for c in collections:
30
+ bin_type = c.find("h4").get_text(strip=True)
31
+ if "my next" in bin_type.lower():
32
+ collection_info = c.find("div", {"class": "atPanelData"}).get_text(
33
+ strip=True
34
+ )
35
+ results = re.search(
36
+ "([A-Za-z]+ \\d+[A-Za-z]+ [A-Za-z]+ \\d*)", collection_info
37
+ )
38
+ if results:
39
+ collection_date = datetime.strptime(
40
+ remove_ordinal_indicator_from_date_string(results[1]).strip(),
41
+ "%A %d %B %Y",
42
+ ).strftime(date_format)
43
+ dict_data = {
44
+ "type": bin_type.replace("My Next ", "").replace(
45
+ " Collection", ""
46
+ ),
47
+ "collectionDate": collection_date,
48
+ }
49
+ data["bins"].append(dict_data)
50
+ if "garden waste" in collection_info.lower():
51
+ dict_data = {
52
+ "type": "Garden Waste",
53
+ "collectionDate": collection_date,
54
+ }
55
+ data["bins"].append(dict_data)
56
+
57
+ data["bins"].sort(
58
+ key=lambda x: datetime.strptime(x.get("collectionDate"), date_format)
59
+ )
60
+
61
+ return data
@@ -0,0 +1,65 @@
1
+ import json
2
+
3
+ import requests
4
+ from uk_bin_collection.uk_bin_collection.common import *
5
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
6
+
7
+
8
+ # import the wonderful Beautiful Soup and the URL grabber
9
+ class CouncilClass(AbstractGetBinDataClass):
10
+ """
11
+ Concrete classes have to implement all abstract operations of the
12
+ base class. They can also override some operations with a default
13
+ implementation.
14
+ """
15
+
16
+ def parse_data(self, page: str, **kwargs) -> dict:
17
+ user_uprn = kwargs.get("uprn")
18
+ check_uprn(user_uprn)
19
+
20
+ headers = {
21
+ "Accept": "application/json, text/javascript, */*; q=0.01",
22
+ "Accept-Language": "en-GB,en;q=0.7",
23
+ "Connection": "keep-alive",
24
+ "Content-Type": "application/json; charset=utf-8",
25
+ "Referer": "https://www.fenland.gov.uk/article/13114/?uprn=200002981143&lat=52.665569590474&lng=0.177905443639&postcode=PE13+3SL&line1=20+Felsted+Avenue&rad=5m&layers=2%2C3%2C1",
26
+ "Sec-Fetch-Dest": "empty",
27
+ "Sec-Fetch-Mode": "cors",
28
+ "Sec-Fetch-Site": "same-origin",
29
+ "Sec-GPC": "1",
30
+ "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",
31
+ "X-Requested-With": "XMLHttpRequest",
32
+ }
33
+
34
+ # It needs lat and lng for point data, but we don't need it >:)
35
+ params = {
36
+ "type": "loadlayer",
37
+ "layerId": "2",
38
+ "uprn": user_uprn,
39
+ "lat": "0.000000000001",
40
+ "lng": "0.000000000001",
41
+ }
42
+
43
+ requests.packages.urllib3.disable_warnings()
44
+ response = requests.get(
45
+ "https://www.fenland.gov.uk/article/13114/", params=params, headers=headers
46
+ )
47
+
48
+ # Returned data is just json, so we can get what we need
49
+ json_data = json.loads(response.text)["features"][0]["properties"]["upcoming"]
50
+ data = {"bins": []}
51
+
52
+ for item in json_data:
53
+ collections_list = item["collections"]
54
+ for bin in collections_list:
55
+ bin_type = bin["desc"]
56
+ bin_date = datetime.strptime(
57
+ bin["collectionDate"], "%Y-%m-%dT%H:%M:%SZ"
58
+ ).strftime(date_format)
59
+ dict_data = {
60
+ "type": bin_type,
61
+ "collectionDate": bin_date,
62
+ }
63
+ data["bins"].append(dict_data)
64
+
65
+ return data
@@ -0,0 +1,113 @@
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
+ from selenium.webdriver.common.keys import Keys
8
+ import time
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
+
16
+
17
+ class CouncilClass(AbstractGetBinDataClass):
18
+ """
19
+ Concrete classes have to implement all abstract operations of the
20
+ base class. They can also override some operations with a default
21
+ implementation.
22
+ """
23
+
24
+ def parse_data(self, page: str, **kwargs) -> dict:
25
+ driver = None
26
+ try:
27
+ page = "https://community.fdean.gov.uk/s/waste-collection-enquiry"
28
+
29
+ data = {"bins": []}
30
+
31
+ house_number = kwargs.get("paon")
32
+ postcode = kwargs.get("postcode")
33
+ full_address = f"{house_number}, {postcode}"
34
+ web_driver = kwargs.get("web_driver")
35
+ headless = kwargs.get("headless")
36
+
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
+ wait = WebDriverWait(driver, 60)
43
+ address_entry_field = wait.until(
44
+ EC.presence_of_element_located(
45
+ (By.XPATH, '//*[@id="combobox-input-19"]')
46
+ )
47
+ )
48
+
49
+ address_entry_field.send_keys(str(full_address))
50
+
51
+ address_entry_field = wait.until(
52
+ EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-19"]'))
53
+ )
54
+ address_entry_field.click()
55
+ address_entry_field.send_keys(Keys.BACKSPACE)
56
+ address_entry_field.send_keys(str(full_address[len(full_address) - 1]))
57
+
58
+ first_found_address = wait.until(
59
+ EC.element_to_be_clickable(
60
+ (By.XPATH, '//*[@id="dropdown-element-19"]/ul')
61
+ )
62
+ )
63
+
64
+ first_found_address.click()
65
+ # Wait for the 'Select your property' dropdown to appear and select the first result
66
+ next_btn = wait.until(
67
+ EC.element_to_be_clickable((By.XPATH, "//lightning-button/button"))
68
+ )
69
+ next_btn.click()
70
+ bin_data = wait.until(
71
+ EC.presence_of_element_located(
72
+ (By.XPATH, "//span[contains(text(), 'Container')]")
73
+ )
74
+ )
75
+
76
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
77
+
78
+ rows = soup.find_all("tr", class_="slds-hint-parent")
79
+ current_year = datetime.now().year
80
+
81
+ for row in rows:
82
+ columns = row.find_all("td")
83
+ if columns:
84
+ container_type = row.find("th").text.strip()
85
+ collection_day = re.sub(
86
+ r"[^a-zA-Z0-9,\s]", "", columns[0].get_text()
87
+ ).strip()
88
+
89
+ # Parse the date from the string
90
+ parsed_date = datetime.strptime(collection_day, "%a, %d %B")
91
+ if parsed_date < datetime(
92
+ parsed_date.year, parsed_date.month, parsed_date.day
93
+ ):
94
+ parsed_date = parsed_date.replace(year=current_year + 1)
95
+ else:
96
+ parsed_date = parsed_date.replace(year=current_year)
97
+ # Format the date as %d/%m/%Y
98
+ formatted_date = parsed_date.strftime("%d/%m/%Y")
99
+
100
+ # Add the bin type and collection date to the 'data' dictionary
101
+ data["bins"].append(
102
+ {"type": container_type, "collectionDate": formatted_date}
103
+ )
104
+ except Exception as e:
105
+ # Here you can log the exception if needed
106
+ print(f"An error occurred: {e}")
107
+ # Optionally, re-raise the exception if you want it to propagate
108
+ raise
109
+ finally:
110
+ # This block ensures that the driver is closed regardless of an exception
111
+ if driver:
112
+ driver.quit()
113
+ return data