HerokuとPythonとSlackによるお手軽可視化システム

pandasやmatplotlib、他科学計算系のpackageを用いデータを定期的に可視化しチェックしたいケースがある。

この際特に大げさなシステムを作る必要がないし、金もねぇ、時間もねぇときがある。でもRedashじゃできねぇ...

そんな状況の中作ったHeroku、Python、Slackを用いたシステムについての備忘録。

目次

  1. Heroku Schedulerを設定
  2. pyplotでグラフを保存しSlackに通知
  3. 動的にmoduleをロードし、スクリプト追加を簡略化

1. Heroku Schedulerを設定

Heroku上で定期的にスクリプトを実行できるHeroku Schedulerというものがある。

devcenter.heroku.com

適当にHerokuでappを作成した後、appにスクリプトファイルを配置する。 あとはSchedulerの設定画面でpython3 スクリプトファイルを実行するよう設定すれば pythonスクリプトが定期的に実行される。便利。 この場合特にProcfileとか用意する必要はない。

Heroku CLIをインストールした後、以下のコマンドでappを作成

$ heroku create

以下URLに従いgitの設定をする

https://devcenter.heroku.com/articles/git

# heroku createした場合
$ git remove -v
or 
# すでにherokuのappが存在する場合
$ heroku git:remote -a app名

なにかしらgitが整ったらHerokuのgitにpushし

$ git push heroku master

以下コマンドでHeroku Schedulerをappに追加

$ heroku addons:create scheduler:standard

tips

# schedulerの設定を開きたいときは
$ heroku addons:open scheduler

# ログを見たいとき
$ heroku logs

Schedulerの設定では以下のようにpythonスクリプトを実行するように設定する。 f:id:koyamay:20190711235541p:plain

2. pyplotでグラフを保存しSlackに通知

必要なもの:

Slack Web Apiを使うためにSlackのBotが必要になる。 python-slackclientはファイルアップロード機能を使うために利用する。

※当初SlackのIncoming Webhoookでbase64の画像をアップロードしようと思っていたが無理でした。

以下のようなスクリプトでSlackに簡単に画像が投稿できる

from matplotlib import pyplot
matplotlib.use('Agg')
import io
import numpy
import os
import slack

def main(argv):
  filename = "result.png"
  plot(filename)
  post(filename)
  os.remove(filename)

def plot(filename):
    x = numpy.linspace(0, 10)
    y = numpy.sin(x)
    pyplot.plot(x, y)
    pyplot.savefig(filename, format="png")

def post(filename):
    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])

    response = client.files_upload(
        channels='#random',
        file=filename,
        title="test",
        filename=filename)

if __name__ == '__main__':
    main(sys.argv)

画像の生成はtempfile.NamedTemporaryFileを使う方が好ましく当初それで作成していた。 なぜかうまくSlackの投稿でサムネイルが表示されなくなったため、result.pngを保存するようにした。

SLACK_API_TOKEN環境変数を参考するようにしており(参考)、 開発時にはpython-dotenvを使うと良いと思われる。 Herokuには以下のコマンドで環境変数を設定する

$ heroku config:set SLACK_API_TOKEN=トークン

あとはHerokuになんのpackageを使っているか伝えるためrequirements.txtを配置する。

$ pip freeze > requirements.txt

これで下記画像のようなものがSlackに投稿される。

f:id:koyamay:20190712001256p:plain

3. 動的にmoduleをロードし、スクリプト追加を簡略化

今後も追跡したいデータとそれにまつわる可視化のグラフは増えそう。 簡単にグラフを追加できるようにする。

以下のようなディレクトリ構成

- app.py
- jobs
  |- some_job
  |   |- __init__.py
  |   |- analyzer.py
  |
  |- another_job
      |- __init__.py
      |- analyzer.py

app.pyは2.のものを改変し以下のような形にする

from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
import importlib
import os
imporo sys
import slack

def main(argv):
    jobs_dir = os.listdir('jobs')
    for job_dir in jobs_dir:
        m = importlib.import_module("jobs.%s.analyzer" % job_dir)
        filename = "result.png"
        try:
            if os.path.isfile(filename):
               os.remove(filename)
            m.plot(filename)
            post(job_dir, filename)
        finally:
            if os.path.isfile(filename):
               os.remove(filename)

def post(job_dir, filename):
    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])

    response = client.files_upload(
        channels='#random',
        file=filename, title="%s_%s.png" % (job_dir, datetime.now().strftime("%Y_%m_%d")),
        filename=filename)

if __name__ == '__main__':
    main(sys.argv)

ディレクトリ名、メソッド名はもうちょい良い感じにしていただければ。

importlibを用いてjobsディレクトリ配下のmoduleをimportする。そのためには空でもいいので__init__.pyが必要らしいので作成しておく(間違ってる可能性あるのでツッコミください)。

各moduleにはanalyzer.pyというファイルがあり、plotメソッドでresult.pngという画像を保存する。

以下例:

analyzer.py

import matplotlib
from matplotlib import pyplot
matplotlib.use('Agg')
import numpy
import io

def plot(filename):
    image = io.BytesIO()
    x = numpy.linspace(0, 10)
    y = numpy.sin(x)
    pyplot.plot(x, y)
    pyplot.savefig(filename, format="png")

このような各moduleのanalyzer.pyapp.pyから動的に読み込み、plotメソッドを随時呼び出して画像を作成、そしてSlackに投稿という流れになる。

新しく定期的にplotしたいものが増えた場合、jobsディレクトリにディレクトリを新規作成しanalyzer.pyを用意しplotメソッドを定義すればよい。

まとめ

HerokuとPythonとSlackによる可視化システムについて説明しました。時間がほぼないなかさっとつくったにしては結構便利です。 前々からRedashだけだと足りないが、壮大なシステムを作るほどでもないケースが多かったのでこれからも重宝しそう。 個人的な理想としてはRedashと定期的にJupyter Notebookが動作するようなシステムがあればそれで十分なんだけどなーと思うのですがあるのでしょうか。 あったら教えてください偉い人!

余談ですが、これまでpipenvをつかっていたが、venvが便利なことに気づく。

qiita.com

Pythonは四半期に1回ちょっと使う程度なので間違いがあればご指摘お願いします。