Diary

@ssig33

30 Sep 2017 Sat 16:06

Go で並列数をうまいところ制御しながら並列に動くクローラー というもののサンプル。

並列に HTTP アクセスしてきてパースして title 要素を表示するというものです。 title 要素をパースしてくるのに使っているパッケージはこちらです。

package main
import (
"errors"
"fmt"
"io"
"net/http"
"sync"
"crawler-sample/title"
)
type WebPage struct {
URL string
Body io.Reader
Title string
}
func download(webPage *WebPage) error {
resp, err := http.Get(webPage.URL)
if err == nil {
webPage.Body = resp.Body
}
return err
}
func parse(webPage *WebPage) error {
htmlTitle, ok := title.GetHtmlTitle(webPage.Body)
if ok {
webPage.Title = htmlTitle
return nil
} else {
return errors.New("fail on parse")
}
}
func present(webPage *WebPage) {
fmt.Println(webPage.Title)
}
func main() {
urls := []string{
"https://www.google.co.jp/",
"https://www.google.co.jp/intl/ja/policies/privacy/",
"http://ssig33.com/",
"http://ssig33.com/text",
"http://ssig33.com/text/%E6%97%A5%E8%A8%98%E3%81%A7%E3%81%99",
"https://qiita.com/",
"https://qiita.com/ss_watanabe/items/dd1bdd9b01ee9a8c9eaa",
"https://www.2nn.jp/",
"https://golang.org/pkg/errors/",
"https://qiita.com/jpshadowapps/items/463b2623209479adcd88",
"http://diary.app.ssig33.com/",
}
c := make(chan bool, 5)
var wg sync.WaitGroup
for _, url := range urls {
c <- true
wg.Add(1)
go func(url string) {
defer func() { <-c }()
defer wg.Done()
webPage := WebPage{URL: url}
err := download(&webPage)
if err == nil {
err := parse(&webPage)
if err == nil {
present(&webPage)
}
}
}(url)
}
wg.Wait()
}
view raw main.go hosted with ❤ by GitHub

今回は HTML のダウンロード => パース => 画面への出力処理 までを一塊として並列処理の単位にしています。

ですが、ここをさらに gorutine と channel で制御してダウンロードは 5 並列でやってパースは 20 並列でやって出力は 1 並列でやる、みたいなふうにもできるでしょう。今回は channel を単なるセマフォのように使っていますが、その場合は gorutine 間のやり取りに channel を使う方がシンプルに書けるでしょう(そしてそれがより Go らしいやり方でしょう)。

またダウンロード制御するにあたってホスト名ごとに並列数制御するみたいのもそんなに難しくない(とはいえ実務の上ではこのホスト名とこのホスト名は実は同じインフラに乗ってるので並列数カウントする上で一緒のものとして数えないといけない、とかあったりするのでどうやるにせよ難しさはつきまとうのだが)。

こういうことをやるのに古代人たちはいろいろと苦労したものですが、現代人は結構簡単に並列数を稼ぎつつマルチコアの限界まで遣ってHTML をパースしつつうんたらかんたらやるみたいなクローラーを書けるようになっています。

古代のコード引き継ぐべきかどうかはこういう点についても顧慮してもいいのではないかと思う。

ただ Go はあくまでマルチコアの限界まで使い切るみたいなことは簡単ですが、スケールアウトするという話になってくると途端にどうにもならないのでまあその場合は erlang なんですかね。ただまあクローラーだったら実行ホスト事にクロール対象割り当てればいいだけなので Go でもよさそう。