プロダクトマネージャーのKSKです。前回に引き続き、社内のSlackで使われた絵文字を調査する個人プロジェクトについて書いていきたいと思います。
(注意:今回の投稿にはコードが書いてありますが、本職でないため整ったコードにはなってないことを予めご承知ください。弊社エンジニアのコードが見たい場合は他のブログ記事をお読みください。なお、本記事においてのご意見・ご感想は大歓迎なので、弊社ホームページの「お問い合わせ」から連絡いただき、そのまま弊社に直接遊びに来てください。)
今回は、ダウンロードした Slack の投稿データをパースして、前回定義したスキーマの通りに整形してみようと思います。
Slackから落としてきた情報の構成、およびデータをどのように成形したいかについては前回の投稿を参照してください。ダウンロードされたzipファイルを解凍して、そのフォルダを指定することで、そのフォルダ内にある各チャンネルフォルダを見て、さらにそのフォルダ内の投稿情報が含まれている日々の .json ファイルをそれぞれ閲覧して、それぞれのファイルから情報を抽出してテキスト形式(今回はTSV形式にします)で出力していくプログラムを書きます。
さて、日常業務ではSQLはたたくものの、他プログラミング言語を扱うことはあまりないので、何の言語を使うか・・・使ったことのない難しい言語を使ってブログが書けなくなって挫折するのが怖かったので、今回はできるだけ楽にやりたいです。
ふと、浮かびました。
「日本語の言語処理ならRubyでしょ!」
自分がこの言葉が浮かぶに至った知識はもはや何年前の知識かはわかりません(Ruby がどうこうではなく、自分の記憶の問題)が、その言葉がよぎったので誰がなんと言おうと Ruby で書くことにします(実際には日本語の言語処理はなんにもしなかったのですが・・・)。
ここでやることは、
- 標準入力から入力フォルダと出力テキストファイル名を引っ張ってくる
- 入力フォルダの中のファイルを一つずつ見る
- ファイルがチャンネル(フォルダ)だったら、その中を見て「作業」をする
ことです。「作業」は parse_channel という関数で別で定義します。チャンネル名はファイル名から取得する必要があるので、ファイル名を parse_channel に渡す必要があります。
def main
input_dir = ARGV[0]
output_file = ARGV[1]
Dir::foreach(input_dir) do |file_name|
if input_dir =~ /\/$/
channel_path = input_dir + file_name
else
channel_path = input_dir + "/" + file_name
end
next if file_name == "." or file_name == ".." or !FileTest::directory?(channel_path)
parse_channel(channel_path, file_name, output_file) # file_name stands for channel
p "Finish: " + file_name
end
end
次は、parse_channel の中身です。ここでやることは、
- チャンネル(フォルダ)の中のファイルを一つずつ見る
- ファイルが .json 形式だったら、その中身を見て「作業」をする
ことです。「作業」はやることがいろいろあるので、parse_json という関数で別に定義することにしました。これまでに出力したい情報のうち「日付」「チャンネル名」は出てきているので、これらは parse_json に渡す必要があります。
def parse_channel(channel_path, channel_name, output_file)
Dir::foreach(channel_path) do |file_name|
next if file_name == "." or file_name == ".." or File::extname(file_name) != ".json"
if channel_path =~ /\/$/
file_path = channel_path + file_name
else
file_path = channel_path + "/" + file_name
end
date = file_name[0..9] # yyyy-mm-dd.json -> "yyyy-mm-dd"
parse_json(file_path, date, channel_name, output_file)
end
end
なんかやってることがほとんど同じですね。この時点で本職の人には冗長だと怒られそうですが、弊社のエンジニアはきっと怒らずに「どれだけ非効率か」を優しく伝えてくれると思います。
最後に parse_json です。ここでやることは、
- json 形式の内容から、投稿を1つずつ読み取る
- 投稿に絵文字のリアクションが付いているか確認する
- 「誰が」「誰の投稿に」「何の絵文字を」リアクションしたか取得する
- 「日付」「チャンネル名」と一緒に出力する
です。
json の中身を読み取るってどうやるんだ!?カッコの対応とか一個ずつ見ていくのか?と頭の中がピヨピヨしてましたが、JSONを扱うためのモジュールは当然のように存在していました。便利ですね。
require 'json'
さて、前回の投稿の最後に、同じ絵文字のバリエーションについて触れました。
絵文字には肌の色 (skin-tone) など同じ絵文字でもバリエーションを付けることができるのですが(下記例参照)、これはバラつきを避けたいので同じ絵文字として扱いたいです。データとして取得する際に注意したいと思います。
そういった絵文字は、clap::skin-tone-2 のように :: (コロン2つ) で区切られているようです。そのためここでは正規表現を使ってコロン2つの後ろを消すことにしました。
def parse_json(file_path, date, channel_name, output_file)
open(output_file, "a") {|of|
open(file_path) {|f|
JSON.load(f).each {|message|
next if message["reactions"] == nil
reacted = message["user"]
message["reactions"].each {|reaction|
emoji = reaction["name"].gsub(/::.*/, "")
reaction["users"].each {|react|
of.puts([date, channel_name, react, reacted, emoji].join("\t"))
}
}
}
}
}
end
さて、これでプログラムが完成しました!全部で60行程度の力作です。
まだまだ効率の良いプログラムではないと思いますが、まあこの規模の小さい会社で1回流すだけなら良いでしょう。きっと。
> ruby emojiChecker.rb [exported_directory] [output_text]
流して、しばらく待つと・・・終わった!何か、できた!
あれ・・・・?
「誰が」「誰の投稿に」がわけわからないIDになってて、誰かがわからない・・・・
うかつでした。USER IDと言っておきながら、頭の中では「ユーザーが誰か直感的にわかる」IDと思い込んでいました。今回取得したのは、Slack の USER ID であり、それだけでは誰が(例えば自分が)どれかがわかりません。これだと分析するのも大変!そこで、ユーザー名と USER ID の対応表を引っ張ってきてそれを紐付けるところまでやりましょう。
調べてみたら思ったより簡単で、Slackの「アナリティクス」から調べられるようです。
(公式ヘルプには項目一覧のようなものが見つからなかったので、少し苦労しました)
- Slackのメニューから「アナリティクス」を選択(ブラウザに遷移)
- 「メンバー」タブをクリックして移動
- 「列を編集する」から「ユーザーID」を選択
- メンバーの名前とユーザーIDの一覧が表示されるので、「エクスポート」をクリックしてダウンロード
あとは適当に Excel で vlookup したり SQL で JOIN したりすると、目指していたものができました!
※ 余談ですが、チャットの投稿者については json 形式の情報から「表示名」なども取得できるのですが、ユーザーの表示名は各自が結構変えることがあるのであまりおすすめしません。
※ 後になって、同じフォルダの中にあった users.json を読めばわかるということは気づいたのですが、ちょっと面倒になったのでサボりました。すみません。
次回は取得できたテーブルをいろいろいじっていきます!統計学などの勉強なども交えていきたいと思っているので、興味がある方は読んでいただけると嬉しいです。
〜 Part. 3 に続く 〜