Cloud FunctionsからSlackへmatplotlibで生成したグラフを投稿する
こんにちは、Miotavaです。
直近で引越しをすることとなり、どんな書斎にしようかなとイメージをStable Diffusionで生成しながら固めているところです。
今まではPinterestでインテリアの参考画像を探していたのですが、生成AI使えば割とドンピシャ(死語?)のイメージを作ってくれるので、チョベリグ(死語)です。
本記事について
さて、以前BigQueryから抽出したデータを表形式でSlack投稿する方法をまとめてみました。
本記事ではさらにリッチなデータ集計としてグラフデータをCloud Functionsで生成し、Slack通知する方法についてまとめたいと思います。
以前の記事内容と組み合わせれば、BigQueryのデータをいい感じに集計・グラフ化してSlackに定期投稿する、なんてことができます。
課題に感じていたこと
Cloud Functions上でグラフを生成するとなると、一時的にでもファイルとしてどこかに保存する必要があるから何らかのストレージサービス使わないといけないのかなー、ちょっとめんどくさいなー、と感じていました。
また、Slackへメッセージではなくファイルを投稿する、といったことについてもちょっとしたハードルでした。
課題の解決
生成したグラフデータはメモリ上に保存しておく
Pythonの標準ライブラリであるioの BytesIO
を用いてバイナリストリームを生成し、そこにグラフデータを保存するようにします。
# グラフにしたいデータを用意
df = pd.DataFrame(
{
"date": [
"2023-05-01",
"2023-05-02",
"2023-05-03",
"2023-05-04",
"2023-05-05",
"2023-05-06",
],
"value": [100, 105, 110, 103, 115, 125],
}
)
# データをグラフにする
plt.plot(df["date"], df["value"])
# バイナリストリームを用意
buff = io.BytesIO()
# バイナリストリーム(メモリ)上にグラフデータを保存
plt.savefig(buff, format="png")
# グラフ表示は不要なのでcloseする
plt.close()
Slack投稿する際は、バイナリストリームからバイナリデータを取得し、Slack APIにPOSTパラメータとしてバイナリデータをセットすればOKでした。
この方法であればグラフデータをいちいちストレージに保存したりする必要がなくCloud Functions上のメモリだけでデータが完結するので簡単です。
[ただの雑談]
調べてみたら、BytesIOを利用して標準入出力を高速化する手法なんてものが紹介されていました。面白い!
ファイル投稿もSlack APIを叩けばOK
files.uploadのSlack APIが用意されているので、その仕様に従って叩けば投稿可能です。
import requests
requests.post(
url="https://slack.com/api/files.upload",
data={
"title": title,
"token": <SLACK_ACCESS_TOKEN>,
"channels": <SLACK_CHANNEL_ID>,
},
files={"file": file},
)
上記の file
には前述のバイナリストリーム buff
から getvalue
メソッドでバイナリデータを取得しセットするようにします。(詳細は最終形の実装を参照ください)
最終形
実装
import io
import os
import matplotlib.pyplot as plt
import pandas as pd
from lib.slack import Slack
def main(events, context) -> None:
# Cloud Functionsの環境変数として入れているSlack認証用情報読み込み
SLACK_CHANNEL_ID = os.environ.get("SLACK_CHANNEL_ID")
SLACK_ACCESS_TOKEN = os.environ.get("SLACK_ACCESS_TOKEN")
if SLACK_CHANNEL_ID is None or SLACK_ACCESS_TOKEN is None:
raise Exception(
"SLACK_CHANNEL_ID と SLACK_ACCESS_TOKEN が環境変数としてセットされていることを確認してください。"
)
# グラフにしたいデータを作る
df = pd.DataFrame(
{
"date": [
"2023-05-01",
"2023-05-02",
"2023-05-03",
"2023-05-04",
"2023-05-05",
"2023-05-06",
],
"value": [100, 105, 110, 103, 115, 125],
}
)
# データをグラフにする
plt.plot(df["date"], df["value"])
# バイナリストリームを用意
buff = io.BytesIO()
# バイナリストリーム(メモリ)上にグラフデータを保存
plt.savefig(buff, format="png")
# グラフ表示は不要なのでcloseする
plt.close()
# buffに保存したデータをbytesデータに変換し、Slackに送信する
slack = Slack(SLACK_CHANNEL_ID, SLACK_ACCESS_TOKEN)
slack.post_file(buff.getvalue(), "グラフファイルのタイトル")
pandas
matplotlib
requests
import requests
class Slack:
def __init__(self, channel_id: str, access_token: str) -> None:
self.channel_id = channel_id
self.access_token = access_token
def post_file(self, file: bytes, title: str) -> None:
try:
requests.post(
url="https://slack.com/api/files.upload",
data={
"title": title,
"token": self.access_token,
"channels": self.channel_id,
},
files={"file": file},
)
except requests.exceptions.RequestException as e:
print("Slack投稿リクエスト時にエラー発生しました。詳細: ", e)
投稿してみた様子
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion