Skip to content

Instantly share code, notes, and snippets.

@kism
Last active May 6, 2025 03:39
Show Gist options
  • Select an option

  • Save kism/15f8590739e2487c9646e63f8319d771 to your computer and use it in GitHub Desktop.

Select an option

Save kism/15f8590739e2487c9646e63f8319d771 to your computer and use it in GitHub Desktop.
Kooyong Tracker
#!/usr/bin/env python3
import argparse
import time
from selenium import webdriver
from bs4 import BeautifulSoup
DEFAULT_URL = "https://tallyroom.aec.gov.au/HouseDivisionPage-31496-221.htm"
PAGE_LOAD_TIMEOUT = 3
REFRESH_TIME = 60
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"url",
help="URL to scrape. Default is the Kooyong election page.",
default=DEFAULT_URL,
nargs="?",
)
args = parser.parse_args()
print("Loading selenium chrome...")
selenium_options = webdriver.ChromeOptions()
selenium_options.add_argument("--headless") # Run in headless mode
selenium_options.add_argument("--no-sandbox")
selenium_options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=selenium_options)
total_votes = 0
while True:
driver.get(args.url)
driver.implicitly_wait(PAGE_LOAD_TIMEOUT) # Seconds
page_source = driver.page_source
soup = BeautifulSoup(page_source, "html.parser")
results = []
# Find all elements named aria-label
elements = soup.find_all(attrs={"aria-label": True})
for element in elements:
# Print the aria-label attribute and its text
if (
"Interactive chart" not in element["aria-label"]
and "Two candidate preferred" not in element["aria-label"]
and "Votes." in element["aria-label"]
):
results.append(element["aria-label"])
results_nice = {}
for result in results:
# Split the result into parts
parts = result.split(", ")
candidate_name = parts[1] + " " + parts[0]
# Cast candidate_name to capital case
candidate_name = candidate_name.title()
candidate_votes_new = int(parts[2].split(".")[0].replace(",", "")) # New value is from the page
candidate_votes_old = results_nice.get(candidate_name, {}).get("raw", candidate_votes_new) # Old value is from the previous iteration, default to new value (first iteration)
candidate_votes = {}
candidate_votes["raw"] = candidate_votes_new
candidate_votes["diff"] = candidate_votes["raw"] - candidate_votes_old
results_nice[candidate_name] = candidate_votes
new_total_votes = sum(candidate["raw"] for candidate in results_nice.values())
if total_votes == 0:
total_votes = new_total_votes
vote_count_diff = new_total_votes - total_votes
total_votes = new_total_votes
intro = f"\n{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} - "
intro += f"Total votes: {total_votes}"
if vote_count_diff > 0:
intro += f" (+{vote_count_diff})"
print(intro)
for candidate, votes in results_nice.items():
msg = f"{candidate}: {votes['raw']} votes"
msg += f" ({(votes['raw'] / total_votes) * 100:.2f}%)"
if votes["diff"] > 0:
msg += f" (+{votes['diff']})"
elif votes["diff"] < 0:
msg += f" (-{votes['diff']})"
print(msg)
time.sleep(REFRESH_TIME)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nExiting...")

With uv

uv venv --python 3.12
source .venv/bin/activate
uv pip install bs4 selenium
python3 kooyong.py

With pip

python3 -m venv .venv
source .venv/bin/activate
pip install bs4 selenium
python3 kooyong.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment