Automatic Election Result Updates with Pushover
My fire department has a small levy on the ballot today. I wanted to get updated election results on my phone. It turns out doing this with Pushover is really easy. Pushover allows you to push notifications to your phone and other devices.
Setting Up Pushover
If you don't already have one, create a Pushover account, install the app, and buy a license for at least one device. Then, login and click the 'Create New Application/API Token' link. Fill in the basic info and click <Create Application>. Each app has a free 7500 message a month. More than enough for me.
Once your app is created you'll get an API Token/Key. You'll need this and your user key (which can be found in the top-right corner of your main account page) to push to your devices.
Getting The Election Results
The results are posted on my State's election website. Because I'd never used it before I decided to scrape the data with BeautifulSoup. The data is machine generated as a bunch of tables without useful IDs. I'm sure you could do it more gracefully than I did, but this worked and gave me some insight into the available tools in BeautifulSoup.
The data is also available for download via CSV or XML. Either of which would have been easier and more reliable. But again, I wanted an excuse to play with BS4.
Sending The Results
Sending the results is really easy. Pushover just needs a POST to its URL with the token, user, and message. Something like this:
data = { "token": PUSHOVER_APP_ID, "user": PUSHOVER_USER_KEY, "title": "Levy Results", "message": results } requests.post(PUSHOVER_URL, data=data)
There is more you can do with Pushover including HTML styling, sending links, and sending attachments including pictures which will be displayed in the notification. You can also set the priority and notification sound. Check out the API docs for details.
The Final Result
On my phone I get pushover notifications like this. There are still no results posted, once there are it will send both counts and percentages.
Because this is a throwaway script I'm just using a while loop and having it sleep every 5 minutes between checks.
import datetime import time import requests from bs4 import BeautifulSoup PUSHOVER_APP_ID = "your_app_key" PUSHOVER_USER_KEY = "your_user_key" PUSHOVER_URL = "https://api.pushover.net/1/messages.json" def get_current_page(): return requests.get("https://results.vote.wa.gov/results/current/skagit/") def get_last_update(html_doc): soup = BeautifulSoup(html_doc, 'html.parser') lu_label = soup.find(string="Last Tabulated") lu_text = lu_label.next_element.string try: """Format: 11/05/2019 1:18 PM""" return datetime.datetime.strptime(lu_text, "%m/%d/%Y %I:%M %p") except: return None def get_results(html_doc): soup = BeautifulSoup(html_doc, 'html.parser') race_title_tr = soup.find(string=" FIRE DISTRICT 11 Local Proposition No. 1 - Property Tax Levy for Fire Protection and Emergency Medical Services").parent.parent.parent table_rows = race_title_tr.next_siblings results = "Measure | Votes | %" for row in table_rows: columns = row.findAll('td') for column in columns: results += column.get_text() + " " results += "\n" return results def push_results(results): data = { "token": PUSHOVER_APP_ID, "user": PUSHOVER_USER_KEY, "title": "Levy Results", "message": results } requests.post(PUSHOVER_URL, data=data) if __name__ == "__main__": latest_results = datetime.datetime(1, 1, 1) last_tabulated = None while True: html = get_current_page() if html.status_code == 200: last_tabulated = get_last_update(html.text) if last_tabulated is not None and last_tabulated > latest_results: latest_results = last_tabulated results = get_results(html.text) push_results(results) print(datetime.datetime.now()) time.sleep(300)