🌦

感情分析 Slack Bot を Amazon Comprehend API + Python3 で作る

2021/12/23に公開

はじめに

リモートワークで同僚の感情が読み取りづらい。
そんな悩みを解決するかも知れない感情分析 Slack Bot を Amazon Comprehend API + Python3 で開発しました。

特に難しいこと・すごいことはしていないので、簡単に感情分析 Slack Bot が作れるのだな、程度の感情で読んでいただければ嬉しいです。

指定したターゲットチャンネルの投稿を指定期間分読み込み、それぞれの投稿が

  • Positive(ポジティブ) 😄
  • Negative(ネガティブ) 😥
  • Neutral(どちらでもない) 😐
  • Mixed(複雑) 😵

のどの感情に当てはまるのか、および占める感情の割合(合計100%)を結果表示します。
感情分析結果はターミナルと指定したチャンネルに出力されます。

入力


ターゲットチャンネルの投稿

出力
ターミナルへの出力
$ py sentimentanalysis/sentiment_analysis.py
猫かわいい
😄
    Positive: 0.9965877532958984
    Negative: 4.90620186610613e-05
    Neutral: 0.003267574356868863
    Mixed: 9.563537605572492e-05
---
必須の対応となりますので、明日までに必ずこちらの修正リリースをお願いいたします。
😐
    Positive: 0.04049810767173767
    Negative: 0.0005108681507408619
    Neutral: 0.9588901400566101
    Mixed: 0.0001008079489110969
---
もうだめだ
😥
    Positive: 0.0019638880621641874
    Negative: 0.9888257384300232
    Neutral: 0.00919361412525177
    Mixed: 1.679336310189683e-05
---
嬉しいけど申し訳ない気持ち
😵
    Positive: 0.001254394417628646
    Negative: 0.0002121508732670918
    Neutral: 3.156524326186627e-05
    Mixed: 0.9985018968582153
---
I'm sick and tired
😥
    Positive: 0.0050583891570568085
    Negative: 0.9266681671142578
    Neutral: 0.0511237308382988
    Mixed: 0.017149705439805984
---
Hello world!
😄
    Positive: 0.6473181247711182
    Negative: 0.013637016527354717
    Neutral: 0.338534951210022
    Mixed: 0.0005099340341985226
---


指定チャンネルへの投稿

Slack App の準備

Slack App 作成

Slack App の作り方については、公式ドキュメント、世に出ているわかりやすい記事、よろしければ 私の備忘録 などをご参照ください。
https://slack.com/intl/ja-jp/help/articles/115005265703-ワークスペースで利用するボットの作成
https://zenn.dev/nyancat/articles/20211219-create-slack-app

Slack App に権限付与

Bot ユーザーとして、チャンネルの投稿を読み取る権限と、チャンネルに投稿する権限を使いたいので、Bot Scopes に channels:historychat:write を追加します。

Slack App を Slack Channel に追加

Slack App を Workspace にインストール後、感情分析の対象としたいチャンネルと、分析結果を投稿したいチャンネルに Slack App を追加します。
この追加を忘れると Slack API リクエスト時に not_in_channel というエラーが返ってきます。

Amazon Comprehend の準備

IAM ユーザー作成

AWS コンソールから IAM ユーザーを作成します。
このときアタッチするポリシーは ComprehendReadOnly を選択します。これにより Amazon Comprehend の読み取り権限が付与されます。

それ以外はデフォルトで "ユーザーの作成" をクリックします。

作成後に表示されるアクセスキーIDとシークレットアクセスキーを控えておきます。

AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx

Python3 で実装

python-slack-sdk をインストール

Slack API を簡単に利用するため SDK をインストールします。

初めは slack bot python で検索すると多くヒットする slackbot というパッケージを使おうとしたのですが、どうしても実行時に slacker.Error: invalid_auth エラーになってしまい使えませんでした。

slack 公式の python-slack-sdk パッケージを使ったところ問題なく動いたので、そちらで実装しました。

python-slack-sdk インストール

$ pip install slack_sdk

Boto3 をインストール

Amazon Comprehend API を簡単に利用するため、 AWS が推奨している SDK Boto3 をインストールします。

$ pip install boto3

あとは Boto3 の Comprehend ドキュメントを読んで実装していくのみです。今回は Client の detect_sentiment method を使用します。

python-dotenv をインストール

API TOKEN や ACCESS KEY など、非公開にしたい値を環境変数として持たせるため python-dotenv をインストールします。
あるいはローカルの環境変数に値を定義しても良いです。

$ pip install python-dotenv

以下のような .env ファイルをプロジェクトのルートディレクトリに作成します。(もちろん .env は GitHub などに安易に上げないようにします)

SLACK_API_TOKEN=xxxxx
CHANNEL_ID=xxxxx

AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx

実装

repository はこちらです。
https://github.com/nyancat3/slack-bot-sentiment-analysis

# TODO に変更すべき箇所を書きました。ご使用の場合は必要に応じて書き換えてください。

"チャンネルに参加しました" やアプリ追加削除などの自動メッセージは、感情分析の対象外としています。

sentiment_analysis.py
import os
import datetime

# Import boto3 for Amazon Comprehend
import boto3

# Import .env
from dotenv import load_dotenv

# Import python_slack_sdk
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

# Import .env values
load_dotenv()
SLACK_BOT_TOKEN = os.getenv('SLACK_API_TOKEN') # TODO: Slack App API Token beginning with xoxb-
CHANNEL_ID = os.getenv('CHANNEL_ID') # TODO: Set ID of the channel you'd like to get messages from.

# Set clients
boto3_client = boto3.client('comprehend', region_name = 'ap-northeast-1')
slack_web_client = WebClient(token=SLACK_BOT_TOKEN)

try:
    latest = datetime.datetime.now()
    oledest = datetime.datetime.now() - datetime.timedelta(days=1)
    conversation_history = slack_web_client.conversations_history(channel=CHANNEL_ID, latest=latest.timestamp(), oldest=oledest.timestamp())
    analysis_text = ''
    texts = [message.get('text') for message in conversation_history['messages']]

    for text in texts:
        if ('さんがチャンネルに参加しました' in text or
            'removed an integration' in text or
            'added an integration' in text):
            continue

        result = boto3_client.detect_sentiment(
            Text = text,
            LanguageCode = 'ja' # TODO: Target language
        )

        emoji = '😐'
        if result['Sentiment'] == 'POSITIVE':
            emoji = '😄'
        elif result['Sentiment'] == 'NEGATIVE':
            emoji = '😥'
        elif result['Sentiment'] == 'MIXED':
            emoji = '😵'

        analysis_text += f'{text}\n{emoji}\n'

        for sentiment, score in result['SentimentScore'].items():
            analysis_text += f'    {sentiment}: {score}\n'

        analysis_text += '---\n'

    print(analysis_text)
    slack_web_client.chat_postMessage(channel='#bot_test', text=analysis_text)  # TODO: Set a name of the channel you'd like to post the result to.

except SlackApiError as e:
    print('Error creating conversation: {}'.format(e))

実行

プログラムを実行します。

$ py sentimentanalysis/sentiment_analysis.py

指定したターゲットチャンネル内の投稿の感情分析がされて、ターミナルと指定した投稿先チャンネルに結果が出力されます。
私のコードでは、1日前から現在の #general 内投稿を感情分析して、 #bot_test に分析結果が投稿されます。

例えば #general チャンネルにこのように投稿がされていたとします。

入力


ターゲットチャンネルの投稿

プログラム実行後、ターミナルと #bot_test チャンネルに感情分析結果が出力されます。

  • Positive(ポジティブ)は 😄
  • Negative(ネガティブ)は 😥
  • Neutral(どちらでもない)は 😐
  • Mixed(複雑)は 😵

で表現しています。占める感情の割合(合計100%)もスコアで見られます。

出力
$ py sentimentanalysis/sentiment_analysis.py
猫かわいい
😄
    Positive: 0.9965877532958984
    Negative: 4.90620186610613e-05
    Neutral: 0.003267574356868863
    Mixed: 9.563537605572492e-05
---
必須の対応となりますので、明日までに必ずこちらの修正リリースをお願いいたします。
😐
    Positive: 0.04049810767173767
    Negative: 0.0005108681507408619
    Neutral: 0.9588901400566101
    Mixed: 0.0001008079489110969
---
もうだめだ
😥
    Positive: 0.0019638880621641874
    Negative: 0.9888257384300232
    Neutral: 0.00919361412525177
    Mixed: 1.679336310189683e-05
---
嬉しいけど申し訳ない気持ち
😵
    Positive: 0.001254394417628646
    Negative: 0.0002121508732670918
    Neutral: 3.156524326186627e-05
    Mixed: 0.9985018968582153
---
I'm sick and tired
😥
    Positive: 0.0050583891570568085
    Negative: 0.9266681671142578
    Neutral: 0.0511237308382988
    Mixed: 0.017149705439805984
---
Hello world!
😄
    Positive: 0.6473181247711182
    Negative: 0.013637016527354717
    Neutral: 0.338534951210022
    Mixed: 0.0005099340341985226
---


指定チャンネルへの投稿

分かりやすい文章が多いこともあるのですが、結果は概ね感覚的に正しそうです。
また日本語をターゲット言語と指定したにも関わらず、英文のニュアンスも汲み取ってくれている感じがします。

"必須の対応となりますので、明日までに必ずこちらの修正リリースをお願いいたします。" はネガティブな圧を汲み取ってくれるかと思いましたが、ネガティブ要素は 0.0005 (0.05%) と非常に低く、ニュートラルと判定されました。

Ideas 💡
  • 実際に運用する場合は、 crontab で定期実行するなり、 Slack API の Socket Mode を利用してチャットが投稿される度に実行するなりすると良さそうです。
  • 各投稿のスコアを合算して、この日は Positive が多い日、この日は Negative が多い日、というように時間単位で感情分析しても面白そうです。
  • Chatwork など他チャットツールの投稿でも同じことができそうですね。

おわりに

割と簡単に感情分析をする Slack Bot が作れました。

作った後 "これ作りようによっては クソアプリ Advent Calendar にもなり得たのでは" という思いが一瞬頭をよぎりました。ネガティブな発言をしたら Bot が慰めにくるなど。よろしければどなたか思いを継いで頂ければと思います。

Slack App の感想

初めて Slack App を開発したのですが、ざっくり表現すると "多機能だな" という印象です。
チャンネルへの投稿も API でなく webhook を使う方法もあります。
Socket Mode を利用すれば、チャンネルに投稿がある度にリアルタイムで感情分析をする、という実装もできそうです。

Amazon Comprehend の感想

準備が IAM ユーザー作成 & ComprehendReadOnly ポリシー適用 のみで済みます。簡単に API で感情分析ができるのでアイディア次第で色々遊べそうです。
今回は行いませんでしたが言語検出、キーフレーズ検出なども Comprehend で可能です。

今回行った感情検出では、現状ターゲットとする言語を1つしか選べないため、多言語が入り交じる文章よりも単一言語の文章が対象として想定されていると思います。
とはいえ実際試したところ、日本語指定であっても簡単な文章の他言語なら感情検出できそうだと分かりました。

Discussion