はじめに
Stay Home週間の期間で自分も昨今の状況に何か貢献したいと考え、
自身の地元である山梨県のコロナウィルス対策サイトのデータの自動更新にチャレンジしてみました。
作成したスクリプトのリポジトリはこちら↓
https://github.com/daisuke19891023/covid19-yamanashi-scraping
スクリプトの作成にあたり、以下の記事を参照させていただきました。
長野県版 新型コロナウイルス感染症対策サイト データ更新を自動化した話
※↑の記事を見かけ、自分も取り組もうと思いました・・・!この場を借りて感謝申し上げます!
スクリプトの概要
スクリプトの実行の流れは以下の通りです
- GitHub Actionsにて定期的にスクリプトを実行
- Pythonにて山梨県の新型コロナウイルス感染症に関する総合情報サイトから必要な情報をスクレイピング
- スクレイピングしたPDFからサイト表示用の
data.json
を作成 - 山梨県版感染症対策サイトのrepository宛てにdispatch eventを送信
- スクレイピングrepositoryから
data.json
を取得し、pull requestを作成
Pythonによるデータ取得
山梨県は各種情報が県のサイトに公開されているので、requests
とBeautifulSoup
でスクレイピングを行いました。
PDFデータの読み取り
山梨県では個々の患者情報がPDFで公開されているため、PDFから情報の読み取りを行います。
PDFMiner.sixを使用し、PDFをテキストに変換したのち、テキストの内容を読み取り、データを作成しました。
テキスト変換の際、不要なスペースが入ってしまったり、全角半角が入り混じってしまったので、正規表現でのスペース削除と、mojimojiというライブラリで全角半角の統一を行いました。
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
from io import StringIO
import glob
import os
import re
import mojimoji
for path in glob.glob('./pdf/*'):
input_path = path
output_file = os.path.splitext(os.path.basename(input_path))[0]
output_path = os.path.join('./text', output_file + '.txt')
rsrcmgr = PDFResourceManager()
codec = 'utf-8'
params = LAParams()
text = ""
with StringIO() as output:
device = TextConverter(
rsrcmgr, output, codec=codec, laparams=params)
with open(input_path, 'rb') as input:
interpreter = PDFPageInterpreter(rsrcmgr, device)
for page in PDFPage.get_pages(input):
interpreter.process_page(page)
text += output.getvalue()
device.close()
output.close()
text = re.sub(r' | ', '', text.strip())
text = mojimoji.zen_to_han(text)
# output text
with open(output_path, "wb") as f:
f.write(text.encode('utf-8', "ignore"))
HTMLテーブルデータの読み取り
疑似症例の検査件数、電話相談窓口の相談件数についてはHTMLのテーブル形式で記載されているので、そこから抽出しました。
難しい処理は不要で、BeautifulSoup
のfind
による要素抽出で必要な情報を取得することが出来ました。
jsonの作成
上記で取得したデータを加工し、サイト表示用のdata.json
を作成します。
※証跡管理のためdata.json
もGitHubの管理対象としています
データが更新されたときのみdispatch event
を送信したいため、データの更新有無をチェックした上で、差分があった場合のみdata.json
を更新するという仕様にしました。
注意点として、data.json
を作成する際、あまりに内容が変わりすぎるとmergeする際にConflictが起きてしまいます。
連想配列は基本的には順序は担保されない認識でしたが、python3.7以降は標準で連想配列の順序は担保されるとのことでしたので、Conflictが起きない程度の更新に留まるようでした。
ここまでがpythonスクリプトの内容になります
GitHubActionsによる自動更新
スクレイピングrepositoryのymlファイル
ベースはGitHub Actionsのテンプレートから作成します。
ActionsタブからNew workflowを選択し、
Python ApplicationのSet up this workflowを選択します。
python自体とライブラリのインストールが記述されたymlとなりますので、これを修正することでお望みのActionを実行させることが出来ます。
実行タイミング
一時間ごとにGitHubActionsを定期実行します。cron形式で実行タイミングを記述できます。
on:
schedule:
- cron: "0 * * * *"
pythonスクリプトの実行
pythonスクリプトを実行し、data.json
を更新します。
山梨県の公開情報はCSVではないため、規定外のデータが入りやすかったこともあり、そういったデータが入ってきた場合はここで失敗してくれます。
※間違った情報を出さないためには大事
DispatchイベントによるPull Requestの作成
以下のように記述することで、git status
で更新を確認し、更新があった場合にスクレイピングrepositoryへのコミットと情報サイトへのdispatch event
の送信を行います。
- name: Commit files
run: |
git config --local user.email secrets.EMAIL
git config --local user.name "Scraping Bot"
git status | grep modified && git add data.json && git commit -v -m "[Bot] GitHub Actions - auto run. Update at $(date +'%Y-%m-%d')" \
&& curl -X POST \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Content-Type: application/json" \
https://api.github.com/repos/{repositoryのパス}/dispatches --data '{"event_type": "{イベント名}"}' \
|| true
secrets
にはあらかじめメールアドレスとパーソナルアクセストークンを設定しておきます。
※上記手順は本家↓に詳しく記載されています
長野県版 新型コロナウイルス感染症対策サイト データ更新を自動化した話
なお、dispatchイベントを飛ばす際に、受信側repositoryのAdmin権限が必要とのことでしたが、今回はWrite権限のみでdispatch event
を飛ばすことが出来るようになりました。
参考:repository_dispatch response unpredictable
受信側のymlファイル
上記で発生させたdispatch event
を受信するために以下のようなymlを作成します。
起動条件のイベント名を送信側と合わせておけば、イベント送信を契機に起動させることが出来ます。
name: {Action Name}
on:
repository_dispatch:
types: [{イベント名}]
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Copy data.json
run: curl -o {保存先パス} https://raw.githubusercontent.com/{スクレイピングrepository}/master/{元データのパス}
- name: Commit files
run: |
git config --local user.name "Scraping Bot"
git status | grep modified && git add . && git commit -v -m "[Bot] GitHub Actions - update data.json at $(date +'%Y-%m-%d')"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2.7.1
このアクションが起動することでdata.json
が更新された状態で、defaultブランチ宛てにpull requestが作成されます。
後は、管理者側で内容を確認した上でmergeしてもらうことで、サイトにデータ更新を反映させることが出来ます。
終わりに
GitHub Actionsを活用することで感染症対策サイトのデータ更新の自動化(正確には更新データの自動取得)を行いました。
今回初めてGitHub Actionsを使いましたが、ここまで簡単に使えるとは思っていなくて、感動しました。(CI環境の用意もいらず、しかも無料)
新型感染症に対する情報開示は各自治体ごとで方針やフォーマットは異なっているので、今回のスクリプトをそのまま流用できるわけではありませんが、少しでも参考になればと思っています。
積み残し
公開にあたり、以下の点が不十分なままでしたので、順次直していけたらと思います・・・
- mypyによる型チェック
- BeautifulSoupやrequestsなどのライブラリを用いたテスト(mockなどテストダブルが必要になる)
- 命名など含めた全般的なリファクタリング