wordcloudでslackのみんなの発言をまとめてみた(Python)


これはjsys19AdventCalender(https://adventar.org/calendars/4301) の12/8の記事です。

はじめに

こうして自分のコードを文章とともに発信するのは初めてで、拙い文章、コードになりますが見守っていただけると、また「こここうした方がええで!」と思うところがあったら伝えていたけると幸いです。

slackのみんなの発言を解析してまとめてみた

突然ですがみなさんはワードクラウドというものをご存知でしょうか?

文章中で出現頻度が高い単語を複数選び出し、その頻度に応じた大きさで図示する手法。ウェブページやブログなどに頻出する単語を自動的に並べることなどを指す。文字の大きさだけでなく、色、字体、向きに変化をつけることで、文章の内容をひと目で印象づけることができる。
https://kotobank.jp/word/%E3%83%AF%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89-674221

こんな感じのもので、実際のものは以下の画像のようなものです

これはtypescript-eslintのgithubのページのりどみをワードクラウドにかけた画像です

単語をちょっと面白く表現できるこの方法を以前にネットで見て、「slackのログでこれやったら面白くね?」と思い記事を書くまでに至りました。

wordcloudに渡す文章を作る

wordcloudはスペースごとに区切られたものしか受け取れません。みんなの発言はそんなことはないので、MeCabを利用してわかち書きをします。その前に全ての発言を一つにまとめる作業を入れました。

まずslackのみんなの発言のアーカイブをワークスペースのオーナーの局長よりいただき、文章の抜き出しを試みます。
ファイルを開くと各チャンネルごとにフォルダがあり、その中にjson形式で発言が送信者やリアクションなどの情報が保存されています。(この時点でbotの発言が多いようなチャンネルのフォルダは消しておくと楽です)

ex-2020-6-31.json
[
    {
        "client_msg_id": "hoge",
        "type": "message",
        "text": "ハタチになりました",
        "user": "hogee",
        "ts": "hooge",
        "team": "foo",
        "user_team": "foo",
        "source_team": "foo",
        "user_profile": {
            "avatar_hash": "bar",
            "image_72": "https:\/\/avatars.slack-edge.com\/ore.png",
            "first_name": "Murakami",
            "real_name": "Murakami ore",
            "display_name": "むらかみ",
            "team": "piyo",
            "name": "s31051315",
            "is_restricted": false,
            "is_ultra_restricted": false
        },
    }
]

アーカイブフォルダ内の全てのjsonファイルを走査して、発言を示すtextプロパティの内容を一つの変数に収めるためのコードが以下になります。


from pathlib import Path
import glob
import json
import re

main_text = ""

json_path=Path("src/jsys_archive")
dirs=list(json_path.glob("**/*.json"))
for i in dirs:
    json_open = open(i)
    json_text = json.load(json_open)
    json_dicts = len(json_text)
    for j in range(json_dicts):
        json_text_fixed = re.sub("<.*?>|:.*?:","",json_text[j]["text"])
        main_text += json_text_fixed

調べたいフォルダのパスをPath()にわたしてpathオブジェクト化し、glob()に任意のjsonファイルを探すように"**/*.json"を渡しています。

pa_th=Path("src/jsys_archive")
dirs=list(pa_th.glob("**/*.json"))

そして、みんなの発言には<>で囲われた色々なslack上で扱うであろうデータやメンション情報, ::で囲われたリアクション情報などの純粋なテキストではないノイズが混ざっています。
これらも含んでしまうと出力されるワードクラウドがシステムメッセージばかりになってしまうので、正規表現を用いて文字列操作をしています。

json_text_fixed = re.sub("<.*?>|:.*?:","",json_text[j]["text"])
#<>、または::とその内部のテキストを消去

これで変数main_textにみんなの発言が集まりました(膨大)。あとはMeCabにかけていきます。

wordcloudはスペースごとに区切られたものしか受け取れません。みんなの発言はそんなことはないので、MeCabを利用してわかち書きをします。

これをします。

import MeCab
words = MeCab.Tagger("-Owakati")
nodes = words.parseToNode(main_text)
s = []
while nodes:
    if nodes.feature[:2] == "名詞":
        s.append(nodes.surface)
    nodes = nodes.next

そのためにはMeCab.Tagger()"-Owakati" をわたして分かちます。Taggerオブジェクトは主に以下の四つの引数を取れます。

1, "mecabrc" (引数なし)
2,"-Ochasen" (ChaSenの互換形式)
3,"-Owakati" (分かち書きを出力)←
4,"-Oyomi" (読みを出力)
今回は3の"お分かち"を利用します
(MeCabの引数日本語チックなのおもしろいけどお分かちとは言わないよね)

次に(Taggerインスタンス).parseToNode("文字列")でパースされて返されるNodeオブジェクトは.surface.featureの二つのプロパティがあります。
surfaceにはNodeオブジェクトの文字列データが、featureには[品詞,品詞分類1,品詞分類2,品詞分類3,活用形,活用型,原形,読み,発音]が入っています。
下はプログラム例です。

feature_example

import MeCab
mecab = MeCab.Tagger()
nodes = mecab.parseToNode("情報メディアシステム局")
while nodes:
    print(nodes.feature)
    nodes = nodes.next

↓実行結果

名詞,一般,*,*,*,*,情報,ジョウホウ,ジョーホー
名詞,一般,*,*,*,*,メディア,メディア,メディア
名詞,一般,*,*,*,*,システム,システム,システム
名詞,接尾,一般,*,*,*,局,キョク,キョク

図に表示させるのは名詞だけで良いので、名詞であるものだけをifで通し、その文字列データを用意しておいた空リストに追加していきます。そして完成したリストを半角空白区切りで文字列化して、ようやく準備終了です。

s = []
while nodes:
    if nodes.feature[:2] == "名詞":
        s.append(nodes.surface)
    nodes = nodes.next
parsed_main_text = " ".join(s)

wordcloudで画像出力

やっと画像が作れます。
wc = WordCloud()に各種画像の設定をわたしてwordcloudオブジェクトを生成します。
画像の縦横を設定するheight,width、背景のbackground_colorなどはスタイルチックでわかりやすいかと思います。他には同じワードの出現を回避するcollocation,出現させたくないワードを設定するstopwordsなど様々ですが今回はここにあるだけのものだけ用います。
出力する画像の形を決めるmaskは後述します。

import numpy
from PIL import Image
from wordcloud import WordCloud

mask_jsys = numpy.array(Image.open("jsys.jpeg"))
wc = WordCloud(width=1200, height=800,
                background_color="black",
                collocations = False,
                mask=mask_jsys,
                stopwords={"もの","これ","ため","それ","ところ",
                          "よう","から","さん","けど","こと","そう"},
                font_path="/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc")

一行目では画像の形を決めています。今回は下の画像を使いました。フォントは自分の好みですがImpactを使っています。

これでこの画像のjsysの文字の部分にのみワードクラウドの文字が配置されます。

wc.generate()に先ほど作ったparsed_main_textを渡して画像を生成し、wc.to_file("ファイル名")で保存されます。

wc.generate(parsed_main_text)
wc.to_file('jsys_wordcloud.png')

こうしてようやく完成です。長かった、、

完成!

良さげでは?(自画自賛)
こんなこと言ったっけ?と思うような発言も、確かに言ったなーこれーみたいな発言もあるんじゃないでしょうか?個人的には「お願い」とか「大丈夫」が大きくなるのが面白いですね。団体名のjsysも出てきてくれてよかったです。

参考にしたWebページ

https://oku.edu.mie-u.ac.jp/~okumura/python/wordcloud.html
https://qiita.com/sea_ship/items/7c8811b5cf37d700adc4
https://www.pynote.info/entry/python-wordcloud#%E3%83%9E%E3%82%B9%E3%82%AF%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B
https://takaxtech.com/2018/11/03/article271/
https://qiita.com/amowwee/items/e63b3610ea750f7dba1b