きっかけ
私は図書館を利用する為に大学に通ってるのですが、たまに借りた本の返却期限を忘れて本当にごめんなさいという気持ちになるので、そうならない為に作成しました。
処理の流れ
1)Pythonのrequestsを使って大学図書館が運営しているLibrary Web Serviceにログインする。
2)現在借りている書籍とその返却期限がテーブルとして表示されるので、beautifulsoupで該当要素を取得する
3)返却期限が迫っている書籍があればタイトルと期限をLINE APIにPOSTする。
4)macのlaunchdを用いて上記1〜3を毎日定刻に実行する。
ソースコード
import datetime from enum import Enum, auto import sys import requests import requests.exceptions import keyring as kr from bs4 import BeautifulSoup class LoanPeriodState(Enum): """借りた本の期限が、まだ先/もうすぐ/過ぎてる のうちどの状態かを表す""" AWAY = auto() SOON = auto() OVERDUE = auto() def decide_loan_period_state(ret_date, _reminder_days=3): ret_date_formatted = datetime.datetime.strptime(ret_date, "%Y.%m.%d").date() now = datetime.date.today() delta = (ret_date_formatted - now).days if delta < 0: return LoanPeriodState.OVERDUE if 0 <= delta <= _reminder_days: return LoanPeriodState.SOON return LoanPeriodState.AWAY def download_my_page(_session): elms_id = kr.get_password('elmsid', 'elmsid') passwd = kr.get_password('elms', elms_id) payload = {'PSTKBN': '2', 'LOGIN_USERID': elms_id, 'LOGIN_PASS': passwd, } url = 'https://opac.lib.hokudai.ac.jp/opac-service/srv_odr_stat.php' response = _session.post(url, data=payload) response.raise_for_status() return BeautifulSoup(response.text, 'html.parser') def get_formatted_data(_book_data): title = ''.join(_book_data.find_all('td')[6].string.split('/')[:-1]) ret_date = _book_data.find_all('td')[4].string return title, ret_date def send_notify(message, *args): payload = {"message": message.format(*args)} auth_url = "https://notify-api.line.me/api/notify" headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer ' + kr.get_password('line_token', 'books'), } requests.post(auth_url, headers=headers, data=payload) def main(): with requests.Session() as session: soup = download_my_page(session) try: for book_data in soup.find_all('table')[3].find_all('tr')[1:]: title, ret_date = get_formatted_data(book_data) if len(sys.argv) == 2: state = decide_loan_period_state(ret_date, _reminder_days=int(sys.argv[1])) else: state = decide_loan_period_state(ret_date) if state == LoanPeriodState.AWAY: return elif state == LoanPeriodState.OVERDUE: message = "**書籍の返却期限が過ぎています!**。\n{}\n返却期限: {}" elif state == LoanPeriodState.SOON: message = "書籍の返却期限が迫っています。\n{}\n返却期限: {}" send_notify(message, title, ret_date) except IndexError: """何も借りていない時はここを通る""" pass except requests.exceptions.HTTPError as error: message = "データを取得できませんでした。\n{}" send_notify(message, error.__traceback__) if __name__ == '__main__': main()
launchdについて
https://qiita.com/rsahara/items/7d37a4cb6c73329d4683#fnref1qiita.com
上の記事の方が詳しく説明してくださっているので、それを元に上記スクリプトを毎日0時0分に定期実行するようなエージェントを作成します。
定刻にMacがスリープ状態ならスリープから復帰した際に直ちに実行されます。電源が切ってあると実行はされず、次回の実行を待ちます。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>hokudai_library</string> <key>Program</key> <string>/Path/to/the/exec/script</string> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>0</integer> <key>Minute</key> <integer>0</integer> </dict> <key>StandardErrorPath</key> <string>/Path/to/the/stderr</string> </dict> </plist>
おわりに
作りがシンプルなので割とすぐに完成しました。今回初めて使ったkeyring(https://pypi.org/project/keyring/)というライブラリが便利で、Macのキーチェーンからパスワードなどの情報を取得できます。テストコードを初めて書こうとして適当に終わってしまったのがやや残念ですが練習のためこれから追加していくかもしれません。