Slack発言を解析するツールを作ってみた話

こんにちは、アスクルの いのだい です。

アスクルでは社内のチャットツールとしてSlackを利用しており、分報チャンネルで日々思ったことをつぶやいています。2019年が終わり、自分がたくさん発言したことはなんだろう??と思い、分報チャンネルの発言を集計してみることにしました。 Slack API が json 形式でレスポンスを返してくることを知っていたので、Python 3 で実装にチャレンジします。

実装!

どのような機能を使ったのか、実装のポイントを書いていきます。

Slack Web API

Slack から API が提供されており、チャンネルやユーザの情報や発言内容を取得できます。Slack Web API を呼び出すために必要な token と、今回コールした API について触れます。

Legacy Token

Slack には Legacy Token とそうでない token があります。Legacy Token は全般的にアクセス権がついていて、Legacy ではない方は app ごとにアクセス権をつけて発行する必要があります。今回は Legacy Token を使用しました。ちなみにここから取得できます https://api.slack.com/custom-integrations/legacy-tokens

channels.list

所属するワークスペースのチャンネル情報一覧を取得できます。 https://api.slack.com/methods/channels.list

import requests
import json

url = "https://slack.com/api/channels.list"
token = "xxxxxxxxxxxx"
payload = {
    "token": token,
    "exclude_archived": "true", # アーカイブ済みチャンネルを含めない
    "exclude_members": "true"   # 個人アカウントチャンネルを含めない
}
response = requests.get(url, params=payload)

json_data = response.json()
channels = json_data['channels']
for channel in channels:
    # チャンネル名で検索して該当するチャンネルIDを返す
    if channel['name'] == channel_name:
        return channel['id']
実装のポイント
  • 後述の発言を取得する API ではチャンネル名ではなく内部的な ID(以後、チャンネルID)を指定する必要があるため、それを得るためにコールする実装としています。
  • exclude_archivedexclude_members によって現在有効かつ、アカウント開設時にデフォルトで用意される個人チャンネルを除外したものに絞っています。

channels.history

チャンネルの発言を取得できます。 channel にはチャンネルIDを指定する必要があり、channels.listで得たものを使用します。 https://api.slack.com/methods/channels.history

import json
import requests
from datetime import datetime

url = "https://slack.com/api/channels.history"
token = "xxxxxxxxxxxx"

# unixtimeに変換
start_ts = datetime.strptime("2019/1/1 0:00:00.000000","%Y/%m/%d %H:%M:%S.%f").strftime('%s')
end_ts = datetime.strptime("2019/12/31 23:59:59.999999","%Y/%m/%d %H:%M:%S.%f").strftime('%s')

payload = {
    "token": token,
    "channel": "C1234567890",
    "count": "1000",
    "oldest": start_ts,
    "latest": end_ts
}
response = requests.get(url, params=payload)

json_data = response.json()
messages = json_data["messages"]
実装のポイント
  • channes で対象チャンネルを指定しますが、チャンネル名ではなく ID を指定する必要があります。
  • latestoldest を指定することで発言した日時を指定することもでき、実行時の引数から渡す実装としています。unixtime を指定する必要があるため変換してあげる必要があります。
  • 発言の取得件数はデフォルト100件であるため、count で最大値である1000を指定します。

以上で Slack の発言を取得することができました。そのままでは単なるテキストに過ぎないため、ここから形態素解析に少しだけ足を踏み入れます。

MeCab

MeCabとはオープンソースの形態素解析エンジンで、今回の肝となるモジュールです。単体でインストールして使用できますが、Pythonモジュールとしても提供されているので活用しました。

準備

pip でモジュールをインストールします。

$ pip install mecab-python3

解析実行

import MeCab

me = MeCab.Tagger()
print(me.parse("Slackの発言を解析するよ")
実装のポイント
  • MeCab.Tagger()
    • 引数でフォーマットを指定します。引数なしのデフォルトは mecabrc という形式で、以下のようなフォーマットです。
      表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
    • 例えば "Slackの発言を解析するよ" を解析した結果はこんな出力になります
      Slack 名詞,固有名詞,組織,*,*,*,*
      の 助詞,連体化,*,*,*,*,の,ノ,ノ
      発言 名詞,サ変接続,*,*,*,*,発言,ハツゲン,ハツゲン
      を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
      解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
      する 動詞,自立,*,*,サ変・スル,基本形,する,スル,スル
      よ 助詞,終助詞,*,*,*,*,よ,ヨ,ヨ
      EOS
    • 単語で分ける、いわゆる "分かち書き" で出力したい場合は -Owakati を指定します。先ほどと同じテキストを解析すると以下のような出力になります。 Slack の 発言 を 集計 する よ その他、MeCabの詳しい説明は こちら を参照ください。
  • parse()
    • 解析結果を得ることができます。

解析結果のノイズを除去する

発言を形態素解析した状態では句読点や記号、また joined #チャンネル名 といった Slack が自動で出力するものも含まれてしまうため、絞り込みを行います。

nounAndVerb = [] #絞り込んだ結果を格納するリスト
for line in lines:  #MeCabの解析結果を格納したリスト
    feature = line.split('\t')
    if len(feature) == 2: #'EOS'と''を省く
        info = feature[1].split(',')
        hinshi = info[0]
        # 品詞を指定し、未登録単語を除いてリストに追加する
    if hinshi in ('名詞') and info[6] != "*":
        nounAndVerb.append(info[6])
実装のポイント
  • 品詞 が 名詞 であるものを対象とすることで、英数字・記号をすべて除外します(かなり乱暴)
  • 原形 が * であるものを除くことでMaCabに登録されていない単語を除外します

単語の出現回数を集計する

MeCab で得られた単語リストから出現回数を集計します。

words = {}
for word in target: #集計したい単語を格納したリスト
    words[word] = words.get(word, 0) + 1  #

# リストに取り出して単語の出現回数でソート
d = [(v, k) for k, v in words.items()]
d.sort()
d.reverse()

rank = 1
result = ''
for count, word in d[:50]:
    result += (str(rank)+'位 '+str(word)+' '+str(count)+'回\n')
    rank += 1

print(result)

Slack に通知する

最後に、集計結果を Slack の webhook を使って通知します (slackweb は未使用です)

import requests

web_hook_url = "xxxxxxxxxxxxxxxxxxxx"
requests.post(
    web_hook_url,
    data = json.dumps({
        'text': "Slack に通知する",  #通知内容
        'username': u'通知する人',  #ユーザー名
    })
)

実際に集計してみた

集計結果

  • こと など、ノイズと見なせる単語が多いです。
  • コーヒー が上位に来ていることから、コーヒー好きであることが伺えます。
  • 今日 という単語から、過去未来よりも今日のことをつぶやく傾向があるようです。
  • 自分 が多いことから、分報という特性が表れています。

今後の課題

1,000件の壁

Slack API の仕様上、channels.history では1回のリクエストで1,000件までしか発言を取得できません。そのため1,000件以上の場合に複数回発言を取得するなどの工夫が必要です。

フィルターの精度向上

日本語で名詞として判別される単語しか集計しておらず、英数字や記号は集計対象外です。フィルターの仕方に改善の余地があります。

形態素解析エンジンの見直し

MeCab よりも辞書の更新頻度の高いエンジンがあるようで、導入してみようと思います。

最後に

課題が多くありますが、定期的に実行することで簡単な振り返りに活用できそうです。
また今回は1つのチャンネルを集計しましたが、集計範囲を広げたり精度を上げたりすることで社内コミュニケーションの分析に応用できるかもしれません。

ASKUL Engineering BLOG

2020 © ASKUL Corporation. All rights reserved.