Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1258d3d
fix: Rochdale Council
m26dvd Oct 21, 2025
bda23a7
fix: Norwich City Council
m26dvd Oct 21, 2025
07a3cbf
fix: Wokingham Borough Council
m26dvd Oct 21, 2025
1c6fa63
fix: London Borough of Harrow
m26dvd Oct 22, 2025
7d86ba7
fix: Hart District Council
m26dvd Oct 22, 2025
0c7cdd6
fix: Brighton & Hove
m26dvd Oct 27, 2025
6d82c7c
docs: Update Councils.md from input.json
actions-user Oct 27, 2025
ced5793
fix: Brighton & Hove
m26dvd Oct 27, 2025
c408ecc
Merge branch 'master' of https://github.com/m26dvd/UKBinCollectionData
m26dvd Oct 27, 2025
43e11ae
fix: London Borough of Hounslow
m26dvd Oct 27, 2025
ae00c0d
fix: Derby City Council
m26dvd Oct 27, 2025
404596f
fix: Chelmsford City Council
m26dvd Oct 27, 2025
2d3efea
fix: Boston Borough Council
m26dvd Oct 29, 2025
e2d12d7
Update BostonBoroughCouncil.py
m26dvd Oct 29, 2025
da7bf56
fix: Middlesborough Council
m26dvd Oct 29, 2025
a89320b
feat: Dumfries and Galloway Council
m26dvd Oct 29, 2025
87a3ad4
fix: Newport City Council
m26dvd Oct 29, 2025
1baa677
Update uk_bin_collection/uk_bin_collection/councils/LondonBoroughHarr…
m26dvd Nov 5, 2025
806d2ed
Update ChelmsfordCityCouncil.py
m26dvd Nov 5, 2025
2715ff4
Merge branch 'master' of https://github.com/m26dvd/UKBinCollectionData
m26dvd Nov 5, 2025
b7de4ca
Update uk_bin_collection/uk_bin_collection/councils/BostonBoroughCoun…
m26dvd Nov 5, 2025
7ba0cc9
Update uk_bin_collection/uk_bin_collection/councils/WokinghamBoroughC…
m26dvd Nov 5, 2025
75cbf30
fix: Southampton City Council
m26dvd Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions uk_bin_collection/tests/input.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@
"house_number": "44",
"postcode": "BN1 8NE",
"skip_get_url": true,
"url": "https://cityclean.brighton-hove.gov.uk/link/collections",
"url": "https://enviroservices.brighton-hove.gov.uk/link/collections",
"web_driver": "http://selenium:4444",
"wiki_name": "Brighton and Hove",
"wiki_note": "Use house number and postcode. Requires Selenium",
Expand Down Expand Up @@ -705,6 +705,14 @@
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.",
"LAD24CD": "E08000027"
},
"DumfriesandGallowayCouncil": {
"uprn": "137034556",
"skip_get_url": true,
"url": "https://www.dumfriesandgalloway.gov.uk",
"wiki_name": "Dumfries and Galloway Council",
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.",
"LAD24CD": "S12000006"
},
"DundeeCityCouncil": {
"uprn": "9059043390",
"url": "https://www.dundeecity.gov.uk/",
Expand Down Expand Up @@ -1385,7 +1393,7 @@
"LondonBoroughHounslow": {
"skip_get_url": true,
"uprn": "100021577765",
"url": "https://www.hounslow.gov.uk/homepage/86/recycling_and_waste_collection_day_finder",
"url": "https://my.hounslow.gov.uk/service/Waste_and_recycling_collections",
"wiki_name": "Hounslow",
"wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
"LAD24CD": "E09000018"
Expand Down Expand Up @@ -1547,7 +1555,6 @@
"house_number": "12 Constantine Court Park Road North, Middlesbrough",
"skip_get_url": true,
"url": "https://www.middlesbrough.gov.uk/recycling-and-rubbish/bin-collection-dates/",
"web_driver": "http://selenium:4444",
"wiki_name": "Middlesbrough",
"wiki_note": "Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver.",
"LAD24CD": "E06000002"
Expand Down Expand Up @@ -1652,13 +1659,11 @@
"LAD24CD": "E09000025"
},
"NewportCityCouncil": {
"postcode": "NP20 4HE",
"uprn": "100100688819",
"skip_get_url": true,
"house_number": "6",
"url": "https://www.newport.gov.uk/",
"web_driver": "http://selenium:4444",
"wiki_name": "Newport",
"wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.",
"wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.",
"LAD24CD": "W06000022"
},
"NorthAyrshireCouncil": {
Expand Down Expand Up @@ -1761,7 +1766,7 @@
"uprn": "47097627",
"url": "https://www.northtyneside.gov.uk/waste-collection-schedule",
"wiki_command_url_override": "https://www.northtyneside.gov.uk/waste-collection-schedule/view/XXXXXXXX",
"wiki_name": "North Tyneside",
"wiki_name": "North Tyneside",
"wiki_note": "Pass only the UPRN (no postcode). You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
"LAD24CD": "E08000022"
},
Expand Down Expand Up @@ -1797,7 +1802,7 @@
"house_number": "2",
"postcode": "NR2 3TT",
"url": "https://bnr-wrp.whitespacews.com",
"wiki_command_url_override": "hhttps://bnr-wrp.whitespacews.com",
"wiki_command_url_override": "https://bnr-wrp.whitespacews.com",
"wiki_name": "Norwich",
"wiki_note": "Pass the house number and postcode in their respective parameters.",
"LAD24CD": "E07000148"
Expand Down Expand Up @@ -1957,7 +1962,7 @@
"RochdaleCouncil": {
"postcode": "OL11 5BE",
"skip_get_url": true,
"uprn": "23049922",
"uprn": "10094358428",
"url": "https://webforms.rochdale.gov.uk/BinCalendar",
"wiki_name": "Rochdale",
"wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import time

from bs4 import BeautifulSoup
from selenium.common.exceptions import (
ElementClickInterceptedException,
NoSuchElementException,
TimeoutException,
)
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
Expand Down Expand Up @@ -28,13 +35,30 @@ def parse_data(self, page: str, **kwargs) -> dict:
check_postcode(user_postcode)

# Create Selenium webdriver
driver = create_webdriver(web_driver, headless, None, __name__)
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
driver = create_webdriver(web_driver, headless, user_agent, __name__)
driver.get("https://www.boston.gov.uk/findwastecollections")

accept_button = WebDriverWait(driver, timeout=30).until(
EC.element_to_be_clickable((By.NAME, "acceptall"))
# Wait for initial page load and Cloudflare bypass
WebDriverWait(driver, 30).until(
lambda d: "Just a moment" not in d.title and d.title != ""
)
accept_button.click()
time.sleep(3)

# Try to accept cookies if the banner appears
try:
accept_button = WebDriverWait(driver, timeout=10).until(
EC.element_to_be_clickable((By.NAME, "acceptall"))
)
accept_button.click()
time.sleep(2)
except (
TimeoutException,
NoSuchElementException,
ElementClickInterceptedException,
):
# Cookie banner not present or not clickable; continue without accepting
pass
Comment thread
m26dvd marked this conversation as resolved.

# Wait for the postcode field to appear then populate it
inputElement_postcode = WebDriverWait(driver, 30).until(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ def parse_data(self, page: str, **kwargs) -> dict:
try:
data = {"bins": []}
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}

url = "https://enviroservices.brighton-hove.gov.uk/link/collections"
uprn = kwargs.get("uprn")
user_paon = kwargs.get("paon")
postcode = kwargs.get("postcode")
web_driver = kwargs.get("web_driver")
headless = kwargs.get("headless")
driver = create_webdriver(web_driver, headless, None, __name__)
driver.get(kwargs.get("url"))
driver.get(url)

wait = WebDriverWait(driver, 60)
post_code_search = wait.until(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,95 +1,114 @@
import re
import time
from datetime import datetime, timedelta

import requests
from bs4 import BeautifulSoup
from icalevents.icalevents import events
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from icalevents.icalevents import events

from uk_bin_collection.uk_bin_collection.common import *
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass


class CouncilClass(AbstractGetBinDataClass):
def parse_data(self, page: str, **kwargs) -> dict:
driver = None
try:
data = {"bins": []}
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
user_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"

postcode = kwargs.get("postcode")
user_paon = kwargs.get("paon")
web_driver = kwargs.get("web_driver")
headless = kwargs.get("headless")
driver = create_webdriver(web_driver, headless, None, __name__)

driver = create_webdriver(web_driver, headless, user_agent, __name__)
wait = WebDriverWait(driver, 30)

# Navigate to bin collection page
driver.get("https://www.chelmsford.gov.uk/bins-and-recycling/check-your-collection-day/")

driver.get(
"https://www.chelmsford.gov.uk/bins-and-recycling/check-your-collection-day/"
)

# Handle cookie overlay
try:
accept_btn = wait.until(
EC.element_to_be_clickable((By.XPATH, "//*[contains(text(), 'ACCEPT')]"))
EC.element_to_be_clickable(
(By.XPATH, "//*[contains(text(), 'ACCEPT')]")
)
)
accept_btn.click()
time.sleep(1)
except:
except Exception as e:
# Cookie banner not present or already accepted
pass

# Find postcode input field (dynamic ID)
postcode_input = wait.until(
EC.presence_of_element_located((By.XPATH, "//input[contains(@id, '_keyword')]"))
EC.presence_of_element_located(
(By.XPATH, "//input[contains(@id, '_keyword')]")
)
)
postcode_input.clear()
postcode_input.send_keys(postcode)

# Click search button
submit_btn = wait.until(
EC.element_to_be_clickable((By.CLASS_NAME, "__submitButton"))
)
submit_btn.click()

# Wait for results table
wait.until(EC.presence_of_element_located((By.TAG_NAME, "table")))

# Get the collection round from the table row
soup = BeautifulSoup(driver.page_source, features="html.parser")

# Find the row containing the address
for row in soup.find_all("tr"):
if user_paon in row.get_text():
# Extract collection round (e.g., "Tuesday B")
row_text = row.get_text()
round_match = re.search(r"(Monday|Tuesday|Wednesday|Thursday|Friday)\s+([AB])", row_text)
round_match = re.search(
r"(Monday|Tuesday|Wednesday|Thursday|Friday)\s+([AB])", row_text
)
if round_match:
day = round_match.group(1).lower()
letter = round_match.group(2).lower()
ics_url = f"https://www.chelmsford.gov.uk/media/4ipavf0m/{day}-{letter}-calendar.ics"
ics_url = f"https://www.chelmsford.gov.uk/media/t03c4mik/{day}-{letter}-2025-26.ics"
Comment thread
m26dvd marked this conversation as resolved.
break
else:
raise ValueError(f"Could not find collection round for address: {user_paon}")

raise ValueError(
f"Could not find collection round for address: {user_paon}"
)

# Get events from ICS file within the next 60 days
now = datetime.now()
future = now + timedelta(days=60)

# Parse ICS calendar
upcoming_events = events(ics_url, start=now, end=future)

for event in sorted(upcoming_events, key=lambda e: e.start):
if event.summary and event.start:
data["bins"].append({
"type": event.summary,
"collectionDate": event.start.date().strftime(date_format)
})
collections = event.summary.split(",")
for collection in collections:
data["bins"].append(
{
"type": collection.strip(),
"collectionDate": event.start.date().strftime(
date_format
),
}
)
except Exception as e:
print(f"An error occurred: {e}")
raise
finally:
if driver:
driver.quit()
return data

return data
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def parse_data(self, page: str, **kwargs) -> dict:
check_uprn(user_uprn)
bindata = {"bins": []}

URI = f"https://secure.derby.gov.uk/binday/Binday?search.PremisesId={user_uprn}"
URI = f"https://secure.derby.gov.uk/binday/BinDays/{user_uprn}"
Comment thread
m26dvd marked this conversation as resolved.

# Make the GET request
session = requests.Session()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import re
import time
from datetime import datetime, timedelta

import requests
from bs4 import BeautifulSoup
from icalevents.icalevents import events
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

from uk_bin_collection.uk_bin_collection.common import *
from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass


class CouncilClass(AbstractGetBinDataClass):
def parse_data(self, page: str, **kwargs) -> dict:
driver = None
try:
data = {"bins": []}

user_uprn = kwargs.get("uprn")
check_uprn(user_uprn)

ics_url = f"https://www.dumfriesandgalloway.gov.uk/bins-recycling/waste-collection-schedule/download/{user_uprn}"

# Get events from ICS file within the next 60 days
now = datetime.now()
future = now + timedelta(days=60)

# Parse ICS calendar
upcoming_events = events(ics_url, start=now, end=future)

for event in sorted(upcoming_events, key=lambda e: e.start):
if event.summary and event.start:
collections = event.summary.split(",")
for collection in collections:
data["bins"].append(
{
"type": collection.strip(),
"collectionDate": event.start.date().strftime(
date_format
),
}
)
except Exception as e:
print(f"An error occurred: {e}")
raise
finally:
if driver:
driver.quit()

return data
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ def parse_data(self, page: str, **kwargs) -> dict:

# Iterate through each row
for row in rows:
cells = row.find_all("td")
bin_types = row.find("td", class_="bin-service")

# Check if there are exactly 3 cells in the row
if len(cells) == 3:
bin_type = cells[0].get_text(strip=True)
collection_date = self.format_date(cells[2].get_text(strip=True))
bin_types = bin_types.text.split("&")

# Create a dictionary for each bin and append to the bins list
bins.append({"type": bin_type, "collectionDate": collection_date})
collection_date = row.find("td", class_="bin-service-date")

collection_date = self.format_date(collection_date.text.strip())

for bin_type in bin_types:
# Create a dictionary for each bin and append to the bins list
bins.append(
{"type": bin_type.strip(), "collectionDate": collection_date}
)
Comment thread
m26dvd marked this conversation as resolved.

return {"bins": bins}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def parse_data(self, page: str, **kwargs) -> dict:
URI = f"https://www.harrow.gov.uk/ajax/bins?u={user_uprn}&r=12345"

# Make the GET request
response = requests.get(URI)
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}
response = requests.get(URI, headers=headers, timeout=30)

# Parse the JSON response
bin_collection = response.json()
Expand Down
Loading
Loading