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,137 @@
1
+ import re
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+
5
+ from uk_bin_collection.uk_bin_collection.common import *
6
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
7
+
8
+ # This script pulls (in one hit) the data from Bromley Council Bins Data
9
+ import datetime
10
+ from datetime import datetime
11
+ from selenium.webdriver.common.by import By
12
+ from selenium.webdriver.support import expected_conditions as EC
13
+ from selenium.webdriver.support.ui import Select
14
+ from selenium.webdriver.support.wait import WebDriverWait
15
+ from selenium.webdriver.common.keys import Keys
16
+ import time
17
+
18
+
19
+ # import the wonderful Beautiful Soup and the URL grabber
20
+ class CouncilClass(AbstractGetBinDataClass):
21
+ """
22
+ Concrete classes have to implement all abstract operations of the
23
+ base class. They can also override some operations with a default
24
+ implementation.
25
+ """
26
+
27
+ def parse_data(self, page: str, **kwargs) -> dict:
28
+ driver = None
29
+ try:
30
+ data = {"bins": []}
31
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
32
+
33
+ uprn = kwargs.get("uprn")
34
+ user_paon = kwargs.get("paon")
35
+ postcode = kwargs.get("postcode")
36
+ web_driver = kwargs.get("web_driver")
37
+ headless = kwargs.get("headless")
38
+ driver = create_webdriver(web_driver, headless)
39
+ driver.get(kwargs.get("url"))
40
+
41
+ wait = WebDriverWait(driver, 60)
42
+ post_code_search = wait.until(
43
+ EC.presence_of_element_located((By.CLASS_NAME, "form-control"))
44
+ )
45
+ post_code_search.send_keys(postcode)
46
+
47
+ submit_btn = wait.until(
48
+ EC.presence_of_element_located(
49
+ (By.XPATH, f"//button[contains(@class, 'mx-name-actionButton3')]")
50
+ )
51
+ )
52
+
53
+ submit_btn.send_keys(Keys.ENTER)
54
+
55
+ dropdown_options = wait.until(
56
+ EC.presence_of_element_located(
57
+ (By.XPATH, f'//option[contains(text(), "{user_paon}")]')
58
+ )
59
+ )
60
+ parent_element = dropdown_options.find_element(
61
+ By.XPATH, ".."
62
+ ) # Using ".." to move up to the parent element
63
+
64
+ # Create a 'Select' for it, then select the first address in the list
65
+ # (Index 0 is "Make a selection from the list")
66
+ dropdownSelect = Select(parent_element)
67
+ dropdownSelect.select_by_visible_text(str(user_paon))
68
+
69
+ submit_btn = wait.until(
70
+ EC.presence_of_element_located(
71
+ (By.XPATH, f"//button[contains(@class, 'mx-name-actionButton5')]")
72
+ )
73
+ )
74
+
75
+ submit_btn.send_keys(Keys.ENTER)
76
+
77
+ results = wait.until(
78
+ EC.presence_of_element_located(
79
+ (By.XPATH, f'//span[contains(@class,"collection-sub")]')
80
+ )
81
+ )
82
+
83
+ # Make a BS4 object
84
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
85
+ # Initialize current month and year (you can modify these values based on your requirement)
86
+ data = {"bins": []}
87
+
88
+ current_date = datetime.now()
89
+
90
+ # Find all elements with class starting with 'mx-name-index-'
91
+ bins = soup.find_all(class_=lambda x: x and x.startswith("mx-name-index-"))
92
+
93
+ for bin_item in bins:
94
+ bin_type = bin_item.find(class_="collection-main").text.strip()
95
+ day_of_week_elements = bin_item.find_all(class_="collection-header")
96
+ bin_date = None
97
+
98
+ for elem in day_of_week_elements:
99
+ if (
100
+ elem.text.strip() != bin_type
101
+ ): # Avoid taking the bin type as the date
102
+ next_sibling = elem.find_next_sibling()
103
+ if next_sibling:
104
+ bin_date_str = next_sibling.text.strip()
105
+ try:
106
+ # Try parsing the date string in the format 'dd Month' (e.g., '30 Dec', '5 January')
107
+ bin_date = datetime.strptime(bin_date_str, "%d %b")
108
+ except ValueError:
109
+ try:
110
+ # If the above format fails, try 'dd MonthName' (e.g., '30 December', '5 January')
111
+ bin_date = datetime.strptime(bin_date_str, "%d %B")
112
+ except ValueError:
113
+ pass
114
+
115
+ if bin_date:
116
+ # Set the year based on the logic provided
117
+ if bin_date.month < current_date.month:
118
+ bin_date = bin_date.replace(
119
+ year=current_date.year + 1
120
+ )
121
+ else:
122
+ bin_date = bin_date.replace(year=current_date.year)
123
+ # Format the date to the desired format
124
+ bin_date = bin_date.strftime("%d/%m/%Y")
125
+ break
126
+ dict_data = {"type": bin_type, "collectionDate": bin_date}
127
+ data["bins"].append(dict_data)
128
+ except Exception as e:
129
+ # Here you can log the exception if needed
130
+ print(f"An error occurred: {e}")
131
+ # Optionally, re-raise the exception if you want it to propagate
132
+ raise
133
+ finally:
134
+ # This block ensures that the driver is closed regardless of an exception
135
+ if driver:
136
+ driver.quit()
137
+ return data
@@ -0,0 +1,141 @@
1
+ import ast
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
+ requests.packages.urllib3.disable_warnings()
22
+ s = requests.session()
23
+
24
+ service_type_headers = {
25
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,"
26
+ "image/webp,image/apng,*/*;q=0.8",
27
+ "Accept-Language": "en-GB,en;q=0.9",
28
+ "Cache-Control": "max-age=0",
29
+ "Connection": "keep-alive",
30
+ "Referer": "https://www.bristol.gov.uk/",
31
+ "Sec-Fetch-Dest": "document",
32
+ "Sec-Fetch-Mode": "navigate",
33
+ "Sec-Fetch-Site": "cross-site",
34
+ "Sec-Fetch-User": "?1",
35
+ "Sec-GPC": "1",
36
+ "Upgrade-Insecure-Requests": "1",
37
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
38
+ "like Gecko) Chrome/105.0.0.0 Safari/537.36",
39
+ }
40
+ service_type_params = {
41
+ "servicetypeid": "7dce896c-b3ba-ea11-a812-000d3a7f1cdc",
42
+ }
43
+ response = s.get(
44
+ "https://bristolcouncil.powerappsportals.com/completedynamicformunauth/",
45
+ params=service_type_params,
46
+ headers=service_type_headers,
47
+ )
48
+
49
+ llpg_headers = {
50
+ "Accept": "*/*",
51
+ "Accept-Language": "en-GB,en;q=0.9",
52
+ "Connection": "keep-alive",
53
+ "Ocp-Apim-Subscription-Key": "47ffd667d69c4a858f92fc38dc24b150",
54
+ "Ocp-Apim-Trace": "true",
55
+ "Origin": "https://bristolcouncil.powerappsportals.com",
56
+ "Referer": "https://bristolcouncil.powerappsportals.com/",
57
+ "Sec-Fetch-Dest": "empty",
58
+ "Sec-Fetch-Mode": "cors",
59
+ "Sec-Fetch-Site": "cross-site",
60
+ "Sec-GPC": "1",
61
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
62
+ "like Gecko) Chrome/105.0.0.0 Safari/537.36",
63
+ }
64
+ llpg_uprn = "UPRN" + user_uprn
65
+ llpg_json_data = {
66
+ "Uprn": llpg_uprn,
67
+ }
68
+ response = s.post(
69
+ "https://bcprdapidyna002.azure-api.net/bcprdfundyna001-llpg/DetailedLLPG",
70
+ headers=llpg_headers,
71
+ json=llpg_json_data,
72
+ )
73
+
74
+ headers = {
75
+ "Accept": "*/*",
76
+ "Accept-Language": "en-GB,en;q=0.9",
77
+ "Connection": "keep-alive",
78
+ # Already added when you pass json=
79
+ # 'Content-Type': 'application/json',
80
+ "Ocp-Apim-Subscription-Key": "47ffd667d69c4a858f92fc38dc24b150",
81
+ "Ocp-Apim-Trace": "true",
82
+ "Origin": "https://bristolcouncil.powerappsportals.com",
83
+ "Referer": "https://bristolcouncil.powerappsportals.com/",
84
+ "Sec-Fetch-Dest": "empty",
85
+ "Sec-Fetch-Mode": "cors",
86
+ "Sec-Fetch-Site": "cross-site",
87
+ "Sec-GPC": "1",
88
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
89
+ }
90
+ json_data = {
91
+ "uprn": user_uprn,
92
+ }
93
+ response = s.post(
94
+ "https://bcprdapidyna002.azure-api.net/bcprdfundyna001-alloy/NextCollectionDates",
95
+ headers=headers,
96
+ json=json_data,
97
+ )
98
+
99
+ # Make a BS4 object
100
+ soup = BeautifulSoup(response.text, features="html.parser")
101
+ soup.prettify()
102
+
103
+ # Soup returns API response rather than HTML, so parse those strings
104
+ string_data = soup.text.split("data")[1]
105
+ collection_data = string_data.split("]}")
106
+
107
+ # Remove the spare ] and , characters at the of each list element
108
+ fixed_data = [i[1:] for i in collection_data]
109
+
110
+ # Remove the last list element since it's garbage (funny since this is a bin project)
111
+ fixed_data.pop()
112
+ collection_data.clear()
113
+
114
+ # Make some more changes:
115
+ idx = 0
116
+ for i in fixed_data:
117
+ if idx == 0:
118
+ # Remove two extra characters if it's the first element
119
+ i = i[2:]
120
+ # Append some characters to the end of each line to make to dict
121
+ i = i + "]}"
122
+ idx += 1
123
+ # Reuse the collection_data list to make a list of dictionaries - one for each bin
124
+ collection_data.append(ast.literal_eval(i))
125
+
126
+ collections = []
127
+ for bin in collection_data:
128
+ bin_type = bin["containerName"]
129
+ next_collection = datetime.strptime(
130
+ bin["collection"][0]["nextCollectionDate"], "%Y-%m-%dT%H:%M:%S"
131
+ ).strftime(date_format)
132
+ # Could work out next date using the roundDescription and the is_holiday function in common.py
133
+ collections.append((bin_type, next_collection))
134
+
135
+ ordered_data = sorted(collections, key=lambda x: x[1])
136
+ data = {"bins": []}
137
+ for item in ordered_data:
138
+ dict_data = {"type": item[0], "collectionDate": item[1]}
139
+ data["bins"].append(dict_data)
140
+
141
+ return data
@@ -0,0 +1,115 @@
1
+ # This script pulls (in one hit) the data from Bromley Council Bins Data
2
+ import datetime
3
+ from bs4 import BeautifulSoup
4
+ from datetime import datetime
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
+ from selenium.webdriver.common.keys import Keys
10
+ import time
11
+
12
+ from dateutil.relativedelta import relativedelta
13
+ from bs4 import BeautifulSoup
14
+ from uk_bin_collection.uk_bin_collection.common import *
15
+ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass
16
+
17
+
18
+ # import the wonderful Beautiful Soup and the URL grabber
19
+ class CouncilClass(AbstractGetBinDataClass):
20
+ """
21
+ Concrete classes have to implement all abstract operations of the
22
+ base class. They can also override some operations with a default
23
+ implementation.
24
+ """
25
+
26
+ def parse_data(self, page: str, **kwargs) -> dict:
27
+ # Make a BS4 object
28
+ driver = None
29
+ try:
30
+ bin_data_dict = {"bins": []}
31
+ collections = []
32
+ web_driver = kwargs.get("web_driver")
33
+ headless = kwargs.get("headless")
34
+
35
+ data = {"bins": []}
36
+
37
+ # Get our initial session running
38
+ driver = create_webdriver(web_driver, headless)
39
+ driver.get(kwargs.get("url"))
40
+
41
+ wait = WebDriverWait(driver, 30)
42
+ results = wait.until(
43
+ EC.presence_of_element_located((By.CLASS_NAME, "waste-service-image"))
44
+ )
45
+ # Search for the specific bins in the table using BS
46
+ # Parse the HTML content
47
+ # Find all elements with the class 'container-name' to extract bin types
48
+ # Parse the HTML content
49
+ soup = BeautifulSoup(driver.page_source, "html.parser")
50
+ soup.prettify
51
+
52
+ # Find all elements with class 'govuk-summary-list'
53
+ bin_info = []
54
+ waste_services = soup.find_all(
55
+ "h3", class_="govuk-heading-m waste-service-name"
56
+ )
57
+
58
+ for service in waste_services:
59
+ service_title = service.get_text(strip=True)
60
+ next_collection = service.find_next_sibling().find(
61
+ "dt", text="Next collection"
62
+ )
63
+
64
+ if next_collection:
65
+ next_collection_date = next_collection.find_next_sibling().get_text(
66
+ strip=True
67
+ )
68
+ # Extract date part and remove the suffix
69
+ next_collection_date_parse = next_collection_date.split(",")[
70
+ 1
71
+ ].strip()
72
+ day = next_collection_date_parse.split()[0]
73
+ month = next_collection_date_parse.split()[1]
74
+
75
+ # Remove the suffix (e.g., 'th', 'nd', 'rd', 'st') from the day
76
+ if day.endswith(("th", "nd", "rd", "st")):
77
+ day = day[:-2] # Remove the last two characters
78
+
79
+ # Reconstruct the date string without the suffix
80
+ date_without_suffix = f"{day} {month}"
81
+
82
+ # Parse the date string to a datetime object
83
+ date_object = datetime.strptime(date_without_suffix, "%d %B")
84
+
85
+ # Get the current year
86
+ current_year = datetime.now().year
87
+
88
+ # Check if the parsed date is in the past compared to the current date
89
+ if date_object < datetime.now():
90
+ # If the parsed date is in the past, assume it's for the next year
91
+ current_year += 1
92
+ # Append the year to the date
93
+ date_with_year = date_object.replace(year=current_year)
94
+
95
+ # Format the date with the year
96
+ date_with_year_formatted = date_with_year.strftime(
97
+ "%d/%m/%Y"
98
+ ) # Format the date as '%d/%m/%Y'
99
+
100
+ # Create the dictionary with the formatted data
101
+ dict_data = {
102
+ "type": service_title,
103
+ "collectionDate": date_with_year_formatted,
104
+ }
105
+ data["bins"].append(dict_data)
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,107 @@
1
+ from bs4 import BeautifulSoup
2
+ from selenium.webdriver.common.by import By
3
+ from selenium.webdriver.support import expected_conditions as EC
4
+ from selenium.webdriver.support.ui import Select
5
+ from selenium.webdriver.support.wait import WebDriverWait
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
15
+ base 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
+ page = "https://selfservice.broxtowe.gov.uk/renderform.aspx?t=217&k=9D2EF214E144EE796430597FB475C3892C43C528"
23
+
24
+ data = {"bins": []}
25
+
26
+ user_uprn = kwargs.get("uprn")
27
+ user_postcode = kwargs.get("postcode")
28
+ web_driver = kwargs.get("web_driver")
29
+ headless = kwargs.get("headless")
30
+ check_uprn(user_uprn)
31
+ check_postcode(user_postcode)
32
+
33
+ # Create Selenium webdriver
34
+ driver = create_webdriver(web_driver, headless)
35
+ driver.get(page)
36
+
37
+ # Populate postcode field
38
+ inputElement_postcode = driver.find_element(
39
+ By.ID,
40
+ "ctl00_ContentPlaceHolder1_FF5683TB",
41
+ )
42
+ inputElement_postcode.send_keys(user_postcode)
43
+
44
+ # Click search button
45
+ driver.find_element(
46
+ By.ID,
47
+ "ctl00_ContentPlaceHolder1_FF5683BTN",
48
+ ).click()
49
+
50
+ # Wait for the 'Select address' dropdown to appear and select option matching UPRN
51
+ dropdown = WebDriverWait(driver, 10).until(
52
+ EC.presence_of_element_located(
53
+ (By.ID, "ctl00_ContentPlaceHolder1_FF5683DDL")
54
+ )
55
+ )
56
+ # Create a 'Select' for it, then select the matching URPN option
57
+ dropdownSelect = Select(dropdown)
58
+ dropdownSelect.select_by_value("U" + user_uprn)
59
+
60
+ # Wait for the submit button to appear, then click it to get the collection dates
61
+ submit = WebDriverWait(driver, 10).until(
62
+ EC.presence_of_element_located(
63
+ (By.ID, "ctl00_ContentPlaceHolder1_btnSubmit")
64
+ )
65
+ )
66
+ submit.click()
67
+
68
+ soup = BeautifulSoup(driver.page_source, features="html.parser")
69
+
70
+ bins_div = soup.find("div", id="ctl00_ContentPlaceHolder1_FF5686FormGroup")
71
+ if bins_div:
72
+ bins_table = bins_div.find("table")
73
+ if bins_table:
74
+ # Get table rows
75
+ for row in bins_table.find_all("tr"):
76
+ # Get the rows cells
77
+ cells = row.find_all("td")
78
+ bin_type = cells[0].get_text(strip=True)
79
+ # Skip header row
80
+ if bin_type and cells[3] and bin_type != "Bin Type":
81
+ if len(cells[3].get_text(strip=True)) > 0:
82
+ collection_date = datetime.strptime(
83
+ cells[3].get_text(strip=True), "%A, %d %B %Y"
84
+ )
85
+ dict_data = {
86
+ "type": bin_type,
87
+ "collectionDate": collection_date.strftime(
88
+ date_format
89
+ ),
90
+ }
91
+ data["bins"].append(dict_data)
92
+
93
+ data["bins"].sort(
94
+ key=lambda x: datetime.strptime(
95
+ x.get("collectionDate"), "%d/%m/%Y"
96
+ )
97
+ )
98
+ except Exception as e:
99
+ # Here you can log the exception if needed
100
+ print(f"An error occurred: {e}")
101
+ # Optionally, re-raise the exception if you want it to propagate
102
+ raise
103
+ finally:
104
+ # This block ensures that the driver is closed regardless of an exception
105
+ if driver:
106
+ driver.quit()
107
+ return data
@@ -0,0 +1,95 @@
1
+ import pandas as pd
2
+ import time
3
+ from selenium.webdriver.common.by import By
4
+ from selenium.webdriver.common.keys import Keys
5
+ from selenium.webdriver.support.ui import 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
+ class CouncilClass(AbstractGetBinDataClass):
12
+ """
13
+ Concrete classes have to implement all abstract operations of the
14
+ base class. They can also override some operations with a default
15
+ implementation.
16
+ """
17
+
18
+ def get_data(self, df) -> dict:
19
+ # Create dictionary of data to be returned
20
+ data = {"bins": []}
21
+
22
+ # Output collection data into dictionary
23
+ for i, row in df.iterrows():
24
+ dict_data = {
25
+ "type": row["Collection Name"],
26
+ "collectionDate": row["Next Collection Due"],
27
+ }
28
+
29
+ data["bins"].append(dict_data)
30
+
31
+ return data
32
+
33
+ def parse_data(self, page: str, **kwargs) -> dict:
34
+ driver = None
35
+ try:
36
+ page = "https://chiltern.gov.uk/collection-dates"
37
+
38
+ # Assign user info
39
+ user_postcode = kwargs.get("postcode")
40
+ user_paon = kwargs.get("paon")
41
+ web_driver = kwargs.get("web_driver")
42
+ headless = kwargs.get("headless")
43
+
44
+ # Create Selenium webdriver
45
+ driver = create_webdriver(web_driver, headless)
46
+ driver.get(page)
47
+
48
+ # Enter postcode in text box and wait
49
+ inputElement_pc = driver.find_element(
50
+ By.ID,
51
+ "COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_ADDRESSSELECTIONPOSTCODE",
52
+ )
53
+ inputElement_pc.send_keys(user_postcode)
54
+ inputElement_pc.send_keys(Keys.ENTER)
55
+
56
+ time.sleep(4)
57
+
58
+ # Select address from dropdown and wait
59
+ inputElement_ad = Select(
60
+ driver.find_element(
61
+ By.ID,
62
+ "COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_ADDRESSSELECTIONADDRESS",
63
+ )
64
+ )
65
+
66
+ inputElement_ad.select_by_visible_text(user_paon)
67
+
68
+ time.sleep(4)
69
+
70
+ # Submit address information and wait
71
+ inputElement_bn = driver.find_element(
72
+ By.ID, "COPYOFECHOCOLLECTIONDATES_ADDRESSSELECTION_NAV1_NEXT"
73
+ ).click()
74
+
75
+ time.sleep(4)
76
+
77
+ # Read next collection information into Pandas
78
+ table = driver.find_element(
79
+ By.ID, "COPYOFECHOCOLLECTIONDATES_PAGE1_DATES2"
80
+ ).get_attribute("outerHTML")
81
+ df = pd.read_html(table, header=[1])
82
+ df = df[0]
83
+
84
+ # Parse data into dict
85
+ data = self.get_data(df)
86
+ except Exception as e:
87
+ # Here you can log the exception if needed
88
+ print(f"An error occurred: {e}")
89
+ # Optionally, re-raise the exception if you want it to propagate
90
+ raise
91
+ finally:
92
+ # This block ensures that the driver is closed regardless of an exception
93
+ if driver:
94
+ driver.quit()
95
+ return data
@@ -0,0 +1,65 @@
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
+ collections = []
17
+ data = {"bins": []}
18
+
19
+ # Get and check postcode and PAON
20
+ postcode = kwargs.get("postcode")
21
+ paon = kwargs.get("paon")
22
+ check_postcode(postcode)
23
+ check_paon(paon)
24
+
25
+ # Make API call to get property info using postcode
26
+ addr_response = requests.get(
27
+ f'https://www.bury.gov.uk/app-services/getProperties?postcode={postcode.replace(" ", "")}'
28
+ )
29
+ if addr_response.status_code != 200:
30
+ raise ConnectionAbortedError("Issue encountered getting addresses.")
31
+ address_json = json.loads(addr_response.text)["response"]
32
+
33
+ # This makes addr the next item that has the house number. Since these are ordered by house number, a single
34
+ # number like 3 wouldn't return 33
35
+ addr = next(item for item in address_json if paon in item["addressLine1"])
36
+
37
+ # Make API call to get bin data using property ID
38
+ response = requests.get(
39
+ f'https://www.bury.gov.uk/app-services/getPropertyById?id={addr.get("id")}'
40
+ )
41
+ if response.status_code != 200:
42
+ raise ConnectionAbortedError("Issue encountered getting bin data.")
43
+ bin_list = json.loads(response.text)["response"]["bins"]
44
+
45
+ # The JSON actually returns the next collections and a large calendar. But I opted just for the next dates.
46
+ for bin_colour, collection_data in bin_list.items():
47
+ bin_type = bin_colour
48
+ bin_date = datetime.strptime(
49
+ remove_ordinal_indicator_from_date_string(
50
+ collection_data.get("nextCollection")
51
+ ),
52
+ "%A %d %B %Y",
53
+ )
54
+ collections.append((bin_type, bin_date))
55
+
56
+ # Dates are ordered correctly - soonest first
57
+ ordered_data = sorted(collections, key=lambda x: x[1])
58
+ for item in ordered_data:
59
+ dict_data = {
60
+ "type": item[0],
61
+ "collectionDate": item[1].strftime(date_format),
62
+ }
63
+ data["bins"].append(dict_data)
64
+
65
+ return data