技術

A Tour of Goの69

A Tour of Goの69 Exercise: Web Crawlerをやってみました。
68をやった直後から色々考えたり試してみたりしてたんですが、うまく行かず、丸1日程度間を開けてから最初から考えなおしてみたらあっさりいきました。

効果を実感しやすくするため、Fetchでtime.Sleepするようにしてますが、オンラインのA Tour of Goではtime.Sleepは使えないみたいなので、ローカルのもので試してます。
というか、オンラインのA Tour of Goは制限があるし、ローカルのA Tour of Goは無限ループに陥ったりしたときにやり直しが面倒だしで、普通にテキストエディタでソース書いて、コマンドラインでコンパイル・実行したほうが、ちょっと変更しては試しを繰り返すときは便利です。

こういう書き方でいいんですかね。
サンプルデータがしょぼいからいいですけど、実際は同時に実行するスレッド数は制限したほうがいいはず。

追記

Next構造体はCrawlCommandみたいな名前にすべきだった。
chan CrawlResultじゃなくてchan *CrawlResultなチャンネルを使うと、結果の有無をnilとの比較で判定できるし、効率上も有利かもしれない。

package main

import (
    "fmt"
    "time"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, ch chan CrawlResult) {
    // This implementation doesn't do either:
    if depth <= 0 {
        ch <- CrawlResult{}
        return
    }
    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        ch <- CrawlResult{Err: err}
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
    ch <- CrawlResult{URLs: urls, Crawled: url, Depth: depth}
    return
}

type Next struct {
    URL string
    Depth int
}

type CrawlResult struct {
    URLs []string
    Crawled string
    Depth int
    Err error
}

func main() {
    ch := make(chan CrawlResult)
    nexts := []Next{Next{"http://golang.org/", 4}}
    crawleds := make(map[string]int)
    
    for len(nexts) > 0 {
        goroutines := 0
        for _, n := range nexts {
            if crawleds[n.URL] > 0 {continue}

            go Crawl(n.URL, n.Depth, fetcher, ch)
            goroutines++
            fmt.Printf("goroutine %d start\n", goroutines)
        }

        nexts = make([]Next, 0)
        for i := 0; i < goroutines; i++ {
            ret := <-ch
            if ret.Err != nil {fmt.Println(ret.Err)}
            if ret.Crawled == "" {continue}
            
            crawleds[ret.Crawled]++
            for _, url := range ret.URLs {
                nexts = append(nexts, Next{url, ret.Depth-1})
            }
        }
    }
}


// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls     []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {
    time.Sleep(1000 * time.Millisecond)
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください