この記事の目的
厚生労働省が公開しているオープンデータをフェッチして、今日のPCR
PCR検査陽性者数をTwitterにツィートするDockerコンテナを作成します。
開発・実行環境
- Windows ホストPC
- エディション:Windows 10 Home
- バージョン:20H2
- OS ビルド:19042.867
- エクスペリエンス:Windows Feature Experience Pack 120.2212.551.0
- Docker Desktop
- 3.2.2
- Docker
- 20.10.5 build 55c4c88
厚生労働省のオープンデータをフェッチする
データをフェッチするスクリプトを開発する
厚生労働省のオープンデータの保存場所を調査します。このページを眺めるとオープンデータは"https://www.mhlw.go.jp/content/"にcsv形式で保存されていることがわかります。
Pythonのrequestsモジュールを使ってオープンデータをフェッチしましょう。今回はこんなコードを書きました。
# coding: UTF-8
uri_list = [
'pcr_positive_daily.csv',
'pcr_tested_daily.csv',
'cases_total.csv'
]
if __name__ == '__main__':
from sys import stdout
for uri in uri_list:
stdout.write(f'{uri}\n')
オープンデータはもっとあるのですが、今回はuri_list
で格納した3ファイルをフェッチします。実際に使うのはpcr_positive_daily.csv
だけです。
# coding: UTF-8
if __name__ == '__main__':
from sys import argv, path
from urllib.parse import urljoin
path.append('http')
import http_client
endpoint_url = argv[1]
uri_list = argv[2:]
for uri in uri_list:
url = urljoin(endpoint_url, uri)
http = http_client.HttpClient(url)
res = http.sync_get()
with open(uri, 'w') as f:
f.write(res)
# coding: UTF-8
class HttpClient:
def __init__(self, url):
self._url = url
def sync_get(self):
import requests
self._res = requests.get(self._url)
body = None
if self._res.status_code == 200:
body = self._res.text
else:
raise RuntimeError('The http request is failed.')
return body
次のように実行しました。lsコマンドを実行すると3つのcsvファイルがフェッチできていることがわかります。
(base) root@251c769e696d:/mnt/docker/anaconda3/covid-19_mhlw_open_data# python mhlw/uri_list.py | xargs python mhlw/fetch.py "https://www.mhlw.go.jp/content/"
(base) root@251c769e696d:/mnt/docker/anaconda3/covid-19_mhlw_open_data# ls
http mhlw pcr_positive_daily.csv pcr_tested_daily.csv
もともとのcsvの文字コードがutf-8ではないため(たぶんShift-JISかな)、ヘッダの日本語が文字化けしちゃいました。今回は気にせずこのまま進めます。
実行するDockerfileを作成する
次に開発したスクリプトを動作させるDockerコンテナを作ります。
FROM python:3.9.4-slim-buster
ENV WORKDIR_NAME "/app"
ENV DIR_HTTP "http"
ENV DIR_MHLW "mhlw"
ENV ENDPOINT_URL "https://www.mhlw.go.jp/content/"
WORKDIR $WORKDIR_NAME
RUN mkdir $DIR_HTTP
RUN mkdir $DIR_MHLW
COPY ./${DIR_HTTP}/*.py ${WORKDIR_NAME}/${DIR_HTTP}/
COPY ./${DIR_MHLW}/*.py ${WORKDIR_NAME}/${DIR_MHLW}/
RUN pip install requests
CMD python ${DIR_MHLW}/uri_list.py | xargs python ${DIR_MHLW}/fetch.py ${EHLW_ENDPOINT_URL}
ビルドして実行します。今回は期待値が得られているか確認したいので、次のコマンドでイメージのビルド、コンテナの作成・実行を行います。
PS > docker build --tag=covid19 .
PS > docker run covid19
残念ながらこのままだとPythonスクリプトは期待通りに動作しません。なぜならDockerコンテナからエンドポイントのドメイン mhlw.go.jp
の名前解決ができないからです。この辺はDockerコンテナ・ネットワークを解説しないとうまく説明できないので別記事で解説しようと思いますが、一言で説明するとDockerコンテナが属しているネットワーク・サブネットとホストPCが属しているネットワーク・サブネットが異なります。なのでコンテナが属しているネットワークからインターネットに直接アクセスできません。
余談ですが、デバッグしたい場合はコンテナにインタラクティブモードで接続するのがおススメです。
PS > docker run --interactive --tty covid19 bash
コンテナからホスト経由でインターネットにアクセスする
真面目に対応するのであれば、ホストPCのNATの設定を変更したりするのでしょうが(すみません、ちゃんと調査していません)、今回は開発環境兼実行環境のWindowsホストPCで実行するので、Dockerコンテナをhost
ネットワークに紐づけて実行します。
こんな感じで実行しました。
PS > docker run --net host covid19
cases_total.csv
fetch.py
pcr_positive_daily.csv
pcr_tested_daily.csv
uri_list.py
PS >
host
ネットワークって何ぞや、ということですが、簡単に説明しますとdockerをインストールするとデフォルトで3つの仮想ネットワークを作成します。そのうちの1つがhost
ネットワークでDockerエンジンが動作しているホストPCと同じサブネットの仮想ネットワークにコンテナが接続されます。Dockerの仮想ネットワークはdocker network ls
コマンドで確認できます。
PS > docker network ls
NETWORK ID NAME DRIVER SCOPE
e9f868af4a34 bridge bridge local
4b2fd62d429a host host local
b475f904ff07 none null local
詳しくは公式ページをご覧ください。僕もそのうち解説します。(3年くらいにすごくまじめに調べた記憶があるのですが、今はうるおぼえ故に。すみません。)
補足
オープンデータのフェッチのためにPythonスクリプト書いたけれども、curlでもよかったなぁ。
今日のPCR検査陽性者数を求める
PCR検査陽性者数を分析するスクリプトを開発する
まずは分析のスタートポイントを実装します。
# coding: UTF-8
scripts = {
'pcr_positive_daily.csv': 'analysis_pcr_positive_daily.py',
'pcr_tested_daily.csv' : None,
'cases_total.csv' : None
}
if __name__ == '__main__':
from sys import argv, path
from os.path import dirname, join
from subprocess import Popen, PIPE
import binascii
file_name = argv[1]
script = scripts.get(file_name)
if script != None:
data = None
with open(argv[1], 'rb') as input:
script = join(dirname(argv[0]), script)
with Popen(['python', script], stdin=input, stdout=PIPE) as pipe:
data = pipe.stdout.read()
res = data.decode(encoding='utf-8')
print(f'\"{res}\"')
次にpcr_positive_daily.csv
を解析して、本日のPCR検査陽性者数を分析するスクリプトを作成します。Pandaを使えば簡単に詳細な解析できると思いますが、今回は割愛します。(Pandaはちょっと触ったぐらいなので今後調査したいです。)
# coding: UTF-8
if __name__ == '__main__':
from sys import argv, path
from sys import stdin, stdout
from csv import reader
from datetime import datetime
data = list(reader(stdin))
header = data[:1][0]
body = data[1:]
now = datetime.now()
key = f'{now.year}/{now.month}/{now.day}'
item = [ item for item in body if item[0] == key ]
stdout.write(f'As of {key}, the number of positive PCR tests in Japan is {item[0][1]}.')
開発環境で実行します。こんな感じです。
(base) root@251c769e696d:/mnt/docker/anaconda3/covid-19_mhlw_open_data# ls -1 *.csv | xargs -l python mhlw/analysis.py
"As of 2021/4/15, the number of positive PCR tests in Japan is 4570."
(base) root@251c769e696d:/mnt/docker/anaconda3/covid-19_mhlw_open_data#
Dockerfileを更新する
開発環境で動作したのでDockerfileを更新して動作確認をします。
FROM python:3.9.4-slim-buster
ENV WORKDIR_NAME "/app"
ENV DIR_HTTP "http"
ENV DIR_MHLW "mhlw"
ENV ENDPOINT_URL "https://www.mhlw.go.jp/content/"
WORKDIR $WORKDIR_NAME
RUN mkdir $DIR_HTTP
RUN mkdir $DIR_MHLW
COPY ./${DIR_HTTP}/*.py ${WORKDIR_NAME}/${DIR_HTTP}/
COPY ./${DIR_MHLW}/*.py ${WORKDIR_NAME}/${DIR_MHLW}/
RUN pip install requests
CMD python ${DIR_MHLW}/uri_list.py | xargs python ${DIR_MHLW}/fetch.py ${EHLW_ENDPOINT_URL} && ls *.csv | xargs -l python ${DIR_MHLW}/analysis.py
CMD
が長くなってきましたね。シェルスクリプトにまとめたほうが良いかもしれません。イメージからコンテナを作成して実行します。
PS > docker build --tag=covid19 .
PS > docker run --net host covid19
"As of 2021/4/15, the number of positive PCR tests in Japan is 4570."
PS >
ツィートする
本日の陽性者数が分析できたので最後にツィートします。Python Twitter Toolsを使います。
pip
でインストールできるようです。
# pip install twitter
今回はこんなコードを書きました。
# coding: UTF-8
if __name__ == '__main__':
from sys import argv, path
from datetime import datetime
from requests_oauthlib import OAuth1Session
from twitter import *
path.append('.')
import runtime_env
tweet = (argv[1]).replace('"', '')
endpoint_url = runtime_env.twitter_endpoint_url()
token = runtime_env.twitter_access_token()
token_secret = runtime_env.twitter_access_token_secret()
consumer_key = runtime_env.twitter_api_key()
consumer_secret = runtime_env.twitter_api_secret_key()
t = Twitter(auth=OAuth(token, token_secret, consumer_key, consumer_secret))
t.statuses.update(status=tweet)
Twitterにツィートするにはアカウントのトークンが必要になりますが、スクリプトにコーディングするのは危険なので環境変数から読み込むようにしています。環境変数を読みだすスクリプトを記述しました。
# coding: UTF-8
def twitter_endpoint_url():
import os
return os.environ['TWITTER_POST_ENDPOINT_URL']
def twitter_access_token():
import os
return os.environ['TWITTER_ACCESS_TOKEN']
def twitter_access_token_secret():
import os
return os.environ['TWITTER_TOKEN_SECRET']
def twitter_api_key():
import os
return os.environ['TWITTER_API_KEY']
def twitter_api_secret_key():
import os
return os.environ['TWITTER_API_SECRET_KEY']
Dockerfileを更新します。こんな感じで記述しました。
FROM python:3.9.4-slim-buster
ENV WORKDIR_NAME "/app"
ENV DIR_HTTP "http"
ENV DIR_MHLW "mhlw"
ENV DIR_TWITTER "twitter"
ENV EHLW_ENDPOINT_URL "https://www.mhlw.go.jp/content/"
ENV TWITTER_POST_ENDPOINT_URL "https://api.twitter.com/1.1/statuses/update.json"
ENV TWITTER_ACCESS_TOKEN "<アクセストークン>"
ENV TWITTER_TOKEN_SECRET "<アクセストークン・シークレット>"
ENV TWITTER_API_KEY "<APIキー>"
ENV TWITTER_API_SECRET_KEY "<APIキー・シークレット>"
WORKDIR $WORKDIR_NAME
RUN mkdir $DIR_HTTP
RUN mkdir $DIR_MHLW
RUN mkdir $DIR_TWITTER
COPY ./*.py ${WORKDIR_NAME}/
COPY ./${DIR_HTTP}/*.py ${WORKDIR_NAME}/${DIR_HTTP}/
COPY ./${DIR_MHLW}/*.py ${WORKDIR_NAME}/${DIR_MHLW}/
COPY ./${DIR_TWITTER}/*.py ${WORKDIR_NAME}/${DIR_TWITTER}/
RUN pip install requests
RUN pip install twitter
RUN pip install requests-oauthlib
CMD python ${DIR_MHLW}/uri_list.py | xargs python ${DIR_MHLW}/fetch.py ${EHLW_ENDPOINT_URL} && ls *.csv | xargs -l python ${DIR_MHLW}/analysis.py | xargs -0 python ${DIR_TWITTER}/twitter_client.py
イマージからコンテナを作成して実行します。こんな感じです。
PS > docker build --tag=covid19 .
PS > docker run --net host covid19
PS >
Twitterのタイムラインを見るとツィートできていました。
まとめ
結構おおらかなスクリプトになってしまったので真面目に作るならもうちょいちゃんとしなきゃな、と思います。Dockerfile
の CMD
行が長くなってしまったのでシェルスクリプトかPythonスクリプト化してもよいかもですね。
やっぱり厚生労働省の csv
ファイルは curl
使えばよかったと思います。