こんにちは、フィッシュです🐟業務や趣味でGCPを触っている釣り好きエンジニアです。GCPと釣りの融合で Cloud AutoML Visionを使ってカレイの種類を識別 とかやってます。最近Goを触りはじめたので表題のスクリプトをGoで書いてみました。
やったこと
- Slack APIからチャンネルの会話ログを取得する
- 会話のログを品詞分解し単語を取り出す
- 同じ単語の数を数えて配列を作る
- その配列を並び替えてログ出力する
Slack APIからチャンネルの会話ログを取得する
まずはこちらからチャンネルの会話ログを取得してみましょう。
取得方法
- ワークスペースを選択する
- 取得したいチャンネルを選択する 例 #general
- countを1000にする
- 上記の入力が終えたら Test Methodをクリックする
- リクエストURLとレスポンスが表示されます
- リクエストURLをどこかに控えておきます
Goで上記のURLを叩く
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Message struct {
Text string `json:"text"`
}
type Channel struct {
Messages []Message `json:"messages"`
}
func main() {
// 控えたURLを入れる
url := "https://slack.com/api/channels.history?token="
res, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
if res.StatusCode != 200 {
return
}
defer res.Body.Close()
var c Channel
decoder := json.NewDecoder(res.Body)
err = decoder.Decode(&c)
if err != nil {
log.Fatal(err)
}
fmt.Println(c)
}
会話のログを品詞分解し単語を取り出す
上記で取得したログは文章になっているので、そこから単語だけを抽出します。品詞分解には mecabを使います。Go向けにラッピングされたソースがあるのでこちらを使います。ありがたやー
https://github.com/bluele/mecab-golang
mecabを準備
$ brew insatll mecab mecab-ipadic
$ which mecab-config
/usr/local/bin/mecab-config //こんな感じになればOK
// 環境変数を作成
$ export CGO_LDFLAGS="`mecab-config --libs`"
$ export CGO_CFLAGS="-I`mecab-config --inc-dir`"
Goでmecabを使って品詞分解
APIでテキストを取得し、その中から3文字以上の名詞だけを取り出します。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"unicode/utf8"
"github.com/bluele/mecab-golang"
)
type Message struct {
Text string `json:"text"`
}
type Channel struct {
Messages []Message `json:"messages"`
}
func main() {
...中略
m, err := mecab.New("-Owakati")
if err != nil {
fmt.Printf("Mecab instance error. err: %v", err)
}
defer m.Destroy()
decoder := json.NewDecoder(res.Body)
err = decoder.Decode(&c)
if err != nil {
log.Fatal(err)
}
var tmp []string
for _, msg := range c.Messages {
parseToNode(m, msg.Text, &tmp)
}
fmt.Println(tmp)
}
func parseToNode(m *mecab.MeCab, s string, t *[]string) {
tg, err := m.NewTagger()
if err != nil {
panic(err)
}
defer tg.Destroy()
lt, err := m.NewLattice(s)
if err != nil {
panic(err)
}
defer lt.Destroy()
node := tg.ParseToNode(lt)
for {
features := strings.Split(node.Feature(), ",")
if utf8.RuneCountInString(features[6]) > 2 {
if features[0] == "名詞" {
*t = append(*t, features[6])
}
}
if node.Next() != nil {
break
}
}
}
こんな感じのログが出力される
$ [こちら 結婚式 土曜日 日曜日 結婚式 チャンネル]
同じ単語の数を数えて配列を作る
このままだと普通の文字配列なので同じ単語をカウント付きでまとめます
...中略
type Buzzword struct {
Word string
Count int
}
func main() {
...中略
var buzzwords []Buzzword
for word, count := range wordCount(tmp) {
buzzwords = append(buzzwords, Buzzword{word, count})
}
fmt.Println(buzzwords)
}
func wordCount(a []string) map[string]int {
c := make(map[string]int)
for _, word := range a {
c[word]++
}
return c
}
結果例
$ [{お答え 1} {大丈夫 10} {どちら 2} {どっち 1} {タイミング 3} {マグロ 2}]
どの単語がいくつ入っているところまで分かりました!
並び替えをして完成
並び替えはこんな感じで実装しました
package main
import (
"log"
"sort"
)
type Buzzword struct {
Word string
Count int
}
type Buzzwords []Buzzword
func (p Buzzwords) Len() int {
return len(p)
}
func (p Buzzwords) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
type ByCount struct {
Buzzwords
}
func (b ByCount) Less(i, j int) bool {
return b.Buzzwords[i].Count > b.Buzzwords[j].Count
}
func main() {
var people Buzzwords = []Buzzword{
{"お答え", 1},
{"大丈夫", 10},
{"どちら", 2},
{"どっち", 1},
{"タイミング", 3},
{"マグロ", 2},
}
sort.Sort(ByCount{people})
log.Println(people)
}
多い順に並び替わります
$ [{大丈夫 10} {タイミング 3} {どちら 2} {マグロ 2} {お答え 1} {どっち 1}]
よくうろうろしているSlackの結果はこんな感じ
チャンネル => 92
お願い => 58
イベント => 57
こちら => 24
オンライン => 22
申し込み => 16
みたい => 16
ページ => 14
スピーカー => 13
大丈夫 => 10
コード => 9
みんな => 9
レポート => 7
カレー => 7
フォーム => 7
スライド => 7
セッション => 7
みなさん => 7
トピック => 6
参考になったリンク
全部入りのソースコード
上記のソースコードを突っ込んだ main.go をGitHubにあげました。とくにリファクタリングなどしていないので、変なところがあったらIssueとかプルリクエストを貰えると嬉しいです:)