最近、Golangを書き始めたので勉強として、1年半ほど前にPythonで作っていたWebクローラと検索エンジンをGolangで実装してみた。WebフレームワークはFlaskの代わりにgin、Mecabの代わりにKagomeを使用、Datastoreは前回と同じくMognoDBを使ってます。
- GitHub - c-bata/gosearch: Web crawler and Search engine in Golang.
- 今回のコード
- Pythonでつくる検索エンジン(Webクローラ, Mecab, MongoDB, Flask) - c-bata web
今回はHTML書くの面倒だったので、フロントはginでJSON返すだけにしました。
以下使ったライブラリやGoに関するメモ
可変長引数の挙動
スライスを展開して渡すときは、他の引数もスライスにappendしてから展開して渡さないとToo many argumentsで落ちるみたいです。
gore> hoge := func(args ...string) {
..... for _, a := range args {
..... fmt.Println(a)
..... }
..... }
(func(...string))0x2610
gore> hoge("foo", "bar")
foo
bar
gore> hoge([]string{"foo", "bar"}...)
foo
bar
foo
bar
gore> hoge("foo", []string{"bar"}...)
# command-line-arguments
/var/folders/nc/jkpxzpsd6459zf5qb2stk1cwnk6_vq/T/364524589/gore_session.go:31: too many arguments in call to hoge
error: exit status 2
exit status 2
仕様だとは思いますが、少し面倒な気もします。
gore(GolangのREPL)の挙動
gore> :import fmt
gore> fmt.Println("foo")
foo
(int)4
(interface {})<nil>
gore> fmt.Println("bar")
foo
bar
(int)4
(interface {})<nil>
gore> fmt.Println("baz")
foo
bar
baz
(int)4
(interface {})<nil>
Goの勉強中はgoreというREPLをヨック使ってました。 標準出力に過去のprint内容が混ざっているので、最初バグかと思ったのですが、goreではそれまでに入力されてたものをすべて連結して毎回コンパイルするので実装的に仕方ないみたいです。
スライスがあるアイテムを持つかどうかの判定
pythonで言うところの 'baz' in ['foo', 'bar', 'baz'] のようなチェックをしたい場合、Golangではそういう演算子や構文が特に用意されていないみたい。少し泥臭いですが、以下のページのように対応するよりなさそうです。
analyzerについて
前回Pythonで実装したものもそうなのですが、N-gramは使わずにanalyzerは形態素解析エンジンのみです。 以前はMecabを使用していましたが、今回はPure Goな形態素解析エンジンであるKagomeを使用しました。 こちらも特にトラブルなく使えました。
GitHub - ikawaha/kagome: Self-contained Japanese Morphological Analyzer written in pure golang
Godep
GitHub - tools/godep: dependency tool for go
Goの依存関係を記述するツールとしてGodepを使いました。まだ godep save しただけなので特に問題は起きてないです。
envの扱い
MongoDBのDataBase URIとかは環境によって変更したいので、↓のようなファイルを用意して環境変数に応じて変えました。
デバッガ(delve)
delveをとりあえず入れて何度か動かしてみたんですが、まだ慣れなくてスムーズには扱えてないです。 結構assertionのレポートとかを頼りにデバッグしてます。 アサートが失敗したタイミングでカジュアルにdelveを起動できるといいのかも(ちゃんと調べてないだけです。ありそうな気もします)。
テスト
Goでは1テストメソッド複数アサーションが普通に行われるみたいです。 アサーションライブラリはtestifyを使用しました。GoConveyも良さそうだったのですが、個人的に書き方に慣れているので、今回はこれを使用しました。
GitHub - stretchr/testify: A sacred extension to the standard go testing package
// https://github.com/c-bata/gosearch/blob/master/crawler/crawl_test.go#L68
func TestRemoveTags(t *testing.T) {
assert := assert.New(t)
input := `<ul><li>item</li></ul>`
actual := RemoveTags(input)
expected := "item"
assert.Equal(expected, actual)
}
Webクローラについて
Webクローラ完成時のコードは↓です。これ実行すると結構スピード出るので使う際は気をつけてください。
GitHub - c-bata/gosearch at 57aa4107e247254e9de5e95590aa6b3ecd38d67d
mgo
MongoDBのドライバでは一番人気そうだったので使用。 Sessionの取得とかは この辺 参考にしてください。
GetCollection
c := Session.DB("database_name").C("collction_name")
Insert
type Index struct {
ID string `bson:"_id"`
Keyword string `bson:"keyword"`
Url []string `bson:"url"`
}
c := Session.DB("test").C("index")
index := &Index{"hoge", []string{"http://example.com"}}
c.Insert(index)
Update
err = c.Update(bson.M{"keyword": "keyword1"}, bson.M{"$push": bson.M{"url": "http://1.example.com"}})
err = c.UpdateId(result.ID, bson.M{"$push": bson.M{"url": "http://2.example.com"}})
- Update operator(
$setや$put、$pop)は基本的に使えるはず - https://docs.mongodb.org/manual/reference/operator/update-array/
UpdateAll
err = c.Update(bson.M{"keyword": "keyword1"}, bson.M{"$pushAll": bson.M{"url": []string{"http://hoge.example.com"}}})
err = c.Update(bson.M{"_id": result.ID}, bson.M{"$pushAll": bson.M{"url": []string{"http://6.example.com"}}})
Find
result := &Index{}
c.Find(bson.M{"keyword": "hoge"}).One(&result)
FindAll
var results []Index c.Find(nil).All(&results) results
Goの所感など
- 並行処理周り以外ではそれなりにすぐに理解できた
- 並行処理まわりは気づいたらブロックして出力が何もないことが多かったです。
- こちら Go の並行処理 - Block Rockin’ Codes を読んで理解していきました。ありがとうございます。
- デバッガはまだ慣れなくて、ついついテストとPrintデバッグに頼っちゃってます
- 個人的に一番期待しているのはクロスコンパイルとシングルバイナリなのですが、その辺の良さが一番出るのはコマンドラインツールな気がするので、次はその辺で何か作ってみます。github.com
- 作者: Mat Ryer,鵜飼文敏,牧野聡
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/01/22
- メディア: 大型本
- この商品を含むブログ (2件) を見る