With uv
uv venv --python 3.12
source .venv/bin/activate
uv pip install bs4 selenium
python3 kooyong.pyWith pip
python3 -m venv .venv
source .venv/bin/activate
pip install bs4 selenium
python3 kooyong.py| #!/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...") |