Redash×PythonでSlackのコメント一覧を取得する

はじめましてエンジニアの村上です。

今回の内容は表題の通りなんですが、Slackの分析タスクをやる際に動作環境や結果の共有をどうしようか考えていて、「RedashでPythonかけるじゃん!」「実際に使ってみて便利!」となったので記事にしようと思います。

同じようにSlackのコメント抽出をしたいという方は、サンプルコードも載せておいたので参考にしてください。

*Redashの構築まわりに関しては他のエンジニアに譲ることにして、ここでは触れません

何を分析しようとしたか

「定期的にSlackに通知されているコメントを抽出し、期間ごとのコメント数を集計したい」という要望があり、そのためにコメント投稿日とその内容をCSVで抽出するのが今回のタスクになります。

例えば、以下のようなSlackのコメントがあったとき、

次のように、表形式で出すのがゴールです。

なぜRedashを選択したか

Redashとは、クエリを書いてその結果を可視化し、共有するためのオープンソースのツールです。

ビザスクではKPI管理にRedashを使っており、非エンジニアでもRedash上でクエリを書いて分析を行っています。

Slack API の詳細は後述しますが、レスポンスはJSON形式なので、JSONをパースするプログラムを書く必要があります。また、期間やSlackのチャネル指定などは、非エンジニアも行うことが想定されます。CSVをダウンロードするのに毎回エンジニアが対応するのは避けたいところです。

Pythonがかけて、ソースや動作環境ごと共有しやすいものを考えたとき、Google Colaboratoryなどいくつかやり方はあったのですが、普段からRedashでデータ分析する習慣があり、権限の設定なども改めて気にしなくてよいのでRedashで分析することにしました。

Redash×Python

設定で必要なのは、Data SourceでPythonを選択するだけです!
新しく覚える記法は、add_result_row()add_result_column() くらいで、これを使うことでRedash上でSQL実行時と同じように表形式で見ることができます。

また、今回は使いませんでしたがget_query_result(id)でクエリidを指定することで、そのクエリ結果を取得することができます。

サンプルコード

TOKENを作成し、調べたいチャネルのIDを指定すれば以下のソースで動くと思います。

from datetime import datetime, timedelta
import json, urllib2
import time

URL_PATTERN = 'https://slack.com/api/conversations.history?token={token}&channel={channel_id}&latest={latest}&oldest={oldest}&limit={limit}pretty=1'
TOKEN = 'xxxsecretxxx'

CHANNEL_ID = 'C*******'

LATEST = '2019-09-01'
OLDEST = '2019-08-01'
LIMIT = 1000 # 1000より大きい値を指定するとデフォルトの100になるみたい。

latest_dt = datetime.strptime(LATEST, '%Y-%m-%d') - timedelta(hours=+9)
latest_ts = time.mktime(latest_dt.timetuple())
oldest_dt = datetime.strptime(OLDEST, '%Y-%m-%d') - timedelta(hours=+9)
oldest_ts = time.mktime(oldest_dt.timetuple())

url = URL_PATTERN.format(token=TOKEN, channel_id=CHANNEL_ID, latest=latest_ts, oldest=oldest_ts, limit=LIMIT)

r = urllib2.urlopen(url)
x = json.loads(r.read())
r.close()

result = {}

for m in x['messages']:
    if not m.get('attachments'):
        continue

attachments = m['attachments'][0]
dt = datetime.fromtimestamp(float(m['ts'])) + timedelta(hours=+9)

add_result_row(result, {
    'date': dt.strftime('%Y-%m-%d %H:%M'),
    'title': attachments['title'].encode('utf-8'),
    'title_link': attachments.get('title_link'),
    'pretext': attachments['pretext'].encode('utf-8'),
    'text': attachments['text'].encode('utf-8')
})

add_result_column(result, 'date', '', 'string')
add_result_column(result, 'title', '', 'string')
add_result_column(result, 'title_link', '', 'string')
add_result_column(result, 'pretext', '', 'string')
add_result_column(result, 'text', '', 'string')

Slack API

Conversations API

今回はSlackのメッセージ一覧を取得したいのでconversations.history APIを使いました。
Testerのタブを開けば、ブラウザ上でパラメーターを指定してGETが試せます。

Slack API のはまりどころ

limitが1000までしか指定できず、1000より大きい値を指定した場合にはデフォルトの100に勝手に設定されます。
これだと期待した期間指定で結果が得られないので、複数回GETする必要があります。

幸いにも、has_moreという値がレスポンスに入っており、これでリトライすべきかを判断できるので、残りがまだある場合は、最後のコメントの時刻から取得しなおすようなプログラムを書くことでなんとか欲しい結果は得られました。コードにすると以下のようなイメージです。


has_more = True
result = {}

while has_more is True:
    url = URL_PATTERN.format(token=TOKEN, channel_id=CHANNEL_ID, latest=start_ts, oldest=end_ts, limit=LIMIT)
    x = get_conversations(url)
    result = build_result(result, x)

    start_ts = float(x['messages'][-1]['ts'])
    start_dt = datetime.fromtimestamp(start_ts) + timedelta(hours=+9)
    has_more = x['has_more']

ここまでやろうとすると、メソッドわけたりコードも少し複雑になってくるので、Redash上ではかきづらかったです。

まとめ

これまでRedashでSQLやBigQueryを書くことはあってもPythonを書くことはなく、個人でアドホックに分析するときはlocalでJupyter Notebookを使っていたのですが、Redash×Pythonの選択肢が増えたのは良かったです。

コード量が増えてくると書きづらく、より高度な分析やライブラリのインストールを考えると限界はありそうですが、
今回のように外部のAPIをたたいてその結果を解析できるのは夢が広がりますし、実行環境ごと簡単に共有できるので便利でした。

一緒に働くエンジニアを募集中!

ビザスクではエンジニアを大募集中です!

ビザスクに少しでも興味のあるWebエンジニアの方がいましたら、ぜひお話を聞かせてください!詳しい募集要項は下記リンクからアクセスしてください。

エンジニアを募集しています

ビザスクでは、エンジニアとして働きたい方を募集しています。
ご興味のある方は下記よりお気軽にご連絡ください。