17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Go言語+kagome+Ginで単語カウントツール(Webアプリ)を作成してみる

Posted at

はじめに

形態素解析を利用して単語(名詞のみ)の出現数をカウントする簡単なWebアプリを作成してみたので、その作成手順を紹介していきます。
同様のWebアプリをPython+MeCabで実装したことがあったので、今回はこれまで触れたことがなかったGo言語で挑戦してみました。

開発環境

開発環境は以下の通りです。

  • OS: macOS Sierra 10.12.6
  • Homebrew: 1.4.1
  • goenv: 1.0.0
  • Go言語: 1.9.2
  • kagome
  • gin

Go言語のインストール

まずはGo言語をインストールするところからはじめます。
こちらの記事
を参考に、goenvを利用してGo言語をインストールしていきます。

まずはHomebrewでgoenvをインストールします。

$ brew install goenv

goenvを利用するため、~/.zshrcの末尾に以下の設定を追加します。(bashを利用している場合は~/.bashrcに読み替えてください)

~/.zshrc
+
+export PATH="$HOME/.goenv/bin:$PATH"
+eval "$(goenv init -)"

設定を追加したらsourceコマンドを実行して変更内容を反映させます。

$ source ~/.zshrc

その後、goenvを利用してGo言語(バージョン1.9.2)をインストールしていきます。

$ goenv install 1.9.2

goenvでインストールしたGo言語を使用するためには、以下のコマンドを実行して使用するGo言語のバージョンを設定する必要があります。

$ goenv global 1.9.2

go versionを実行して、インストールしたバージョン(1.9.2)が表示されればOKです。

$ go version
go version go1.9.2 darwin/amd64

Go言語でHello worldを試す

Go言語のインストールが完了したところで、まずはmain.goというファイルを作成してHello worldを試していきます。

main.go
package main

import "fmt"

func main() {
    fmt.Printf("Hello, world!\n")
}

mainは特別なパッケージ名で、main()関数を定義することでエントリポイントとして扱われるようです。

$ go run main.go
Hello, world!

また、Go言語はコンパイル言語でありソースコードをコンパイルして実行可能ファイルを生成することもできるので、こちらも試してみます。

# main.goをコンパイル
$ go build main.go

# 実行可能ファイルが生成されたことを確認
$ ls
main    main.go

# 実行可能ファイルを実行
$ ./main
Hello, world!

kagomeのインストール

次に、こちらの記事を参考にしてkagomeというGo言語製の日本語形態素解析器をインストールしていきます。

$ go get github.com/ikawaha/kagome/...

上記コマンド実行後、環境変数$GOPATHで設定しているディレクトリ以下にkagomeがインストールされます。($GOPATHを設定していない場合、デフォルト値として$HOME/goを使用するようです)

kagomeで形態素解析を実行

kagomeをコマンドとして実行

まずはターミナル上でコマンドとしてkagomeを実行してみます。

$ ~/go/bin/kagome
すもももももももものうち
すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

kagomeをライブラリとして使用

ほぼこちらの記事に記載されているサンプルコードのコピペとなりますが、main.goを修正してkagomeを利用した形態素解析を実行できるように修正していきます。
tokenizerインスタンス生成周りのコードについてはsrc/github.com/ikawaha/kagome/cmd/kagome/tokenize/cmd.gocommand()関数を参考に実装しました。

main.go
 package main

-import "fmt"
+import (
+    "fmt"
+    "strings"
+    "github.com/ikawaha/kagome/tokenizer"
+)

 func main() {
-    fmt.Println("Hello, world!\n")
+    dic := tokenizer.SysDicSimple()
+    t := tokenizer.NewWithDic(dic)
+    tokens := t.Tokenize("すもももももももものうち")
+    for _, token := range tokens {
+        features := strings.Join(token.Features(), ",")
+        fmt.Printf("%s\t%v\n", token.Surface, features)
+    }
 }

実行結果は以下の通りです。

$ go run main.go
BOS
すもも	名詞,一般,*,*,*,*
も	助詞,係助詞,*,*,*,*
もも	名詞,一般,*,*,*,*
も	助詞,係助詞,*,*,*,*
もも	名詞,一般,*,*,*,*
の	助詞,連体化,*,*,*,*
うち	名詞,非自立,副詞可能,*,*,*
EOS

これで形態素解析が実行できるようになりました!

形態素解析の実行結果から名詞の単語のみ出現回数をカウント

形態素解析の結果から名詞の単語のみを抽出し、各単語の出現回数をカウントできるように修正します。

main.go
 import (
     "fmt"
-    "strings"
     "github.com/ikawaha/kagome/tokenizer"
 )

 ...

     dic := tokenizer.SysDicSimple()
     t := tokenizer.NewWithDic(dic)
     tokens := t.Tokenize("すもももももももものうち")
+    words := map[string]int{}
     for _, token := range tokens {
-        features := strings.Join(token.Features(), ",")
-        fmt.Printf("%s\t%v\n", token.Surface, features)
+        features := token.Features()
+        // 名詞以外ならスキップ
+        if len(features) == 0 || features[0] != "名詞" {
+            continue
+        }
+
+        // マップに既にキーが存在する場合はインクリメント、存在しない場合は1で初期化
+        _, ok := words[token.Surface]
+        if ok {
+            words[token.Surface]++
+        } else {
+            words[token.Surface] = 1
+        }
+    }
+
+    for word, count := range words {
+        fmt.Printf("%s\t%d\n", word, count)
     }
 }

実行結果は以下のようになります。

$ go run main.go
すもも	1
もも	2
うち	1

※map(連想配列)をrangeでイテレーションする際に要素を取り出す順序は保証されない(という仕様になっている)ようなので、実行するごとに上記の実行結果(単語・出現回数の表示順序)は変わります。

Ginのインストール

単語の出現回数をカウントする機能をWebアプリとして使用できるようにしていきます。
使用するWebフレームワークについては、こちらの記事]を参考にして比較的軽量かつレスポンスが高速ということでGinを選択しました。

まずは以下のコマンドを実行してGinをインストールします。

$ go get github.com/gin-gonic/gin

形態素解析の実行結果をブラウザで表示

形態素解析の実行結果をブラウザ上で表示できるようにするため、まずはテンプレートファイル(index.tpl)を作成していきます。

index.tpl
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>go-word-counter</title>
</head>
<body>
    <ul>
        {{range $word, $count := .wordCount}}
            <li>{{$word}}:{{$count}}回</li>
        {{end}}
    </ul>
</body>
</html>

上記のテンプレートファイルを読み込み、形態素解析の実行結果を埋め込んだレスポンスをブラウザ上(デフォルトではlocalhost:8080)で表示するようにmain.goを修正します。

なお、単語の出現回数をカウントする処理はgetWordCount()関数を作成してそちらに移動しています。

main.go
 import (
-    "fmt"
+    "net/http"
     "github.com/ikawaha/kagome/tokenizer"
+    "github.com/gin-gonic/gin"
 )

-func main() {
+func getWordCount(text string) (map[string]int) {
     dic := tokenizer.SysDicSimple()
     t := tokenizer.NewWithDic(dic)
-    tokens := t.Tokenize("すもももももももものうち")
+    tokens := t.Tokenize(text)
     words := map[string]int{}
     for _, token := range tokens {
         features := token.Features()

 ...

         }
     }

-    for word, count := range words {
-        fmt.Printf("%s\t%d\n", word, count)
-    }
+    return words
+}
+
+func main() {
+    router := gin.Default()
+    router.LoadHTMLFiles("index.tpl")
+
+    router.GET("/", func(context *gin.Context) {
+        context.HTML(http.StatusOK, "index.tpl", gin.H{
+            "wordCount": getWordCount("すもももももももものうち"),
+        })
+    })
+
+    router.Run()
 }

これでmain.goを実行してlocalhost:8080にアクセスすると、以下のような画面が表示されます。

スクリーンショット 2018-01-01 23.05.07.png

任意のテキストを形態素解析できるように修正

今までは固定されたテキストに対して形態素解析を実行した結果を表示していましたが、任意のテキストに対して実行できるように修正していきます。

まずは、任意のテキストを入力して送信ボタンを押下したらテキストを/に対してPOST送信するようにindex.tplを修正します。

index.tpl
<body>
+    <form action="/" method="post">
+        <div>
+            <textarea name="text" placeholder="テキストを入力してください"></textarea>
+        </div>
+        <div>
+            <button type="submit">送信</button>
+        </div>
+    </form>
+    <hr>
    <ul>

そして、/にPOSTでリクエストされたら送信されたテキストに対して形態素解析を実行し、その結果を画面上に表示できるようにmain.goを修正します。

main.go
     router.GET("/", func(context *gin.Context) {
+        context.HTML(http.StatusOK, "index.tpl", gin.H{})
+    })
+
+    router.POST("/", func(context *gin.Context) {
+        text := context.PostForm("text")
         context.HTML(http.StatusOK, "index.tpl", gin.H{
-            "wordCount": getWordCount("すもももももももものうち"),
+            "wordCount": getWordCount(text),
         })
     })

これで任意のテキストに対して形態素解析が実行できるようになりました!

  • 送信ボタン押下前

スクリーンショット 2018-01-01 23.14.19.png

  • 送信ボタン押下後

スクリーンショット 2018-01-01 23.23.48.png

ソースコード

最終的なソースコードは以下の通りです。

index.tpl

index.tpl
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>go-word-counter</title>
</head>
<body>
    <form action="/" method="post">
        <div>
            <textarea name="text" placeholder="テキストを入力してください"></textarea>
        </div>
        <div>
            <button type="submit">送信</button>
        </div>
    </form>
    <hr>
    <ul>
        {{range $word, $count := .wordCount}}
            <li>{{$word}}:{{$count}}回</li>
        {{end}}
    </ul>
</body>
</html>

main.go

main.go
package main

import (
    "net/http"
    "github.com/ikawaha/kagome/tokenizer"
    "github.com/gin-gonic/gin"
)

func getWordCount(text string) (map[string]int) {
    dic := tokenizer.SysDicSimple()
    t := tokenizer.NewWithDic(dic)
    tokens := t.Tokenize(text)
    words := map[string]int{}
    for _, token := range tokens {
        features := token.Features()
        // 名詞以外ならスキップ
        if len(features) == 0 || features[0] != "名詞" {
            continue
        }

        // マップに既にキーが存在する場合はインクリメント、存在しない場合は1で初期化
        _, ok := words[token.Surface]
        if ok {
            words[token.Surface]++
        } else {
            words[token.Surface] = 1
        }
    }

    return words
}

func main() {
    router := gin.Default()
    router.LoadHTMLFiles("index.tpl")

    router.GET("/", func(context *gin.Context) {
        context.HTML(http.StatusOK, "index.tpl", gin.H{})
    })

    router.POST("/", func(context *gin.Context) {
        text := context.PostForm("text")
        context.HTML(http.StatusOK, "index.tpl", gin.H{
            "wordCount": getWordCount(text),
        })
    })

    router.Run()
}

おわりに

Go言語+kagome+Ginで単語(名詞のみ)の出現回数をカウントするWebアプリを作成する手順を紹介していきました。

同様のWebアプリを実装した経験はあったものの、Go言語をはじめ今回使用したkagomeやGinといったライブラリ自体は初めて触ったので非常に良い勉強になりました!
今回はとりあえず動くコードを書くことで精一杯でしたが、今後はよりGo言語らしいコードを書けるように勉強していきたいと思います。

参考記事

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?