コネヒト開発者ブログ

コネヒト開発者ブログ

サービス内のトレンドを把握するために、テキストデータを可視化・通知してくれるslack botを作った話

本記事はコネヒト Advent Calendar 2019 17日目の記事です。

こんにちは!MLエンジニアの野澤(@takapy0210)です!

今回は、ママリ内に日々蓄積されているテキストデータを良い感じに可視化して、定期的にslack通知する仕組みを実装したお話です。

※下記で使用している画像やデータに関しては、あくまでママリ内での傾向を算出しており、個人を特定できるものではございません。また、画像に関しては一部加工しております。

目次

どんなもの?

毎朝9時頃に、前日投稿されたテキストデータを元に、word cloudやN-gramによる単語の出現頻度を可視化してslackに投稿しています。

下記は12月のとある日の投稿ですが、インフルエンザやワクチンといった単語が目立っていることが分かります。

f:id:taxa_program:20191216143918p:plain
word cloudの例

thread内には、uni-gram、bi-gram、tri-gramの画像が投稿されます。

f:id:taxa_program:20191216144041p:plainf:id:taxa_program:20191216144621p:plainf:id:taxa_program:20191216144742p:plain
N-gramの例

なぜやったのか

現在のコネヒトではデータの民主化が進んでおり、多くの社員がredashなどのBIツールを用いて、各々にデータ分析をすることができます。

しかし、テキストデータに関しては、曖昧検索や投稿に紐づくタグ*1などでしか検索することができず、日々蓄積されるデータに対して有効なインサイトを得るのはなかなか難しい状態です。
ママリはママがお互いに悩みを相談し合うことのできるQ&Aサービスなので、日々蓄積されるテキストデータの量も多く、そこから得られるインサイトはとても大切です)

そこで、定期的にママリ内のテキストデータを可視化・共有することで、誰かが何かに気づいてポジティブな行動が生まれたり、有効なインサイトが見つかれば良さそう!ということで実装しました。

社内の方からも嬉しいお言葉が....!

f:id:taxa_program:20191216160324p:plain
社内の方のお声

アーキテクチャ

アーキテクチャは下記のようになっています。

f:id:taxa_program:20191216153950p:plain
アーキテクチャ

各サービスでどんなことをやっているのか簡単に触れます。

CloudWatch

  • StepFunctionsを毎朝9時に起動するためのトリガーとして使用しています。

StepFunctions

  • 「GlueでのETL」と「Fargateでのバッチ処理」を管理しています。

Glue

  • DBから対象のデータを取得し、簡単な前処理を行ったあと、S3に日付ごとのディレクトリを作成して保存しています。

Fargate

  • 分かち書き、word cloud作成、N-gram作成、slack投稿までを行っています。(pythonを使用)

word cloudは例えば下記のように記述することで画像ファイルとして保存することができます。
詳細は公式API Referenceをご参照ください。

# 保存
def save_image(img, file_name):
    Image.fromarray(img).save(file_name)


# wordcloudの作成
def create_wordcloud(text, file, mask=None, max_words=100, max_font_size=80, colormap=None):

    # 日本語に対応させるためにフォントのパスを指定
    f_path = TTF_FILE_NAME

    # wordcloudの生成
    wordcloud = WordCloud(
                    background_color='white',
                    font_step=1,
                    contour_width=0,
                    contour_color='steelblue',
                    font_path=f_path,  # 日本語対応
                    stopwords=stopwords,  # ストップワード(set型)
                    max_words=max_words,
                    max_font_size=max_font_size,
                    random_state=42,
                    width=800,  # 幅
                    height=600,  # 高さ
                    mask=mask,
                    collocations=False,  # bigramを考慮するかどうか
                    prefer_horizontal=1,  # 文字の向き 1だと全部横向き、0だと全部縦向き
                    colormap=colormap
    )
    wordcloud.generate(str(text))

    img = wordcloud.to_array()
    save_image(img, file)


# 呼び出し
create_wordcloud(text=df['text'], file=file_name, mask=ellipse_mask, colormap='tab20_r')

また、slack botで画像を投稿する際は下記のように記述することで実現可能です。
こちらも詳細はAPI Referenceをご参照ください。

# slack通知
def slack_notify(file, comment, ts=None):

    # ファイル名
    filename = file.split('.')[0]

    # 実際に投稿したい画像ファイル
    files = {'file': open(file, 'rb')}

    param = {
        'token': TOKEN,
        'channels': CHANNEL,
        'initial_comment': comment,
        'title': filename,
        'thread_ts': ts
    }

    res = requests.post(url="https://slack.com/api/files.upload", params=param, files=files)
    json_data = res.json()
    return json_data

thread内に続けて投稿したい場合は、tsパラメーターに値を指定することで実現可能です。

# 上記slack通知関数を呼ぶ(初回)
res = slack_notify(file_name, '初回投稿')

# 初回投稿のtsを取得
channel = res['file']['channels'][0]
ts = res['file']['shares']['public'][channel][0]['ts']

# 初回投稿のthread内に投稿する
_ = slack_notify(file_name, 'thread内投稿', ts)

終わりに

今回は、なるべくコストを掛けずにサクッと実装してみました。
このような取り組みを行いデータをより身近におくことで、全社的なデータ活用の促進にも繋がるのではないかと考えております。

今後は社員からのフィードバックをもらいつつ、より良いものにアップデートしていければと思っています!


【ちょっと宣伝】

コネヒトではサービス改善を行うエンジニアを募集しています!
少しでも興味もたれた方は、是非気軽にオフィスに遊びにきてください!

www.wantedly.com

*1:正規表現によって保存される特定のワード