package main import ( "fmt" ) 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) } type request struct { url string depth int } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Fetch(req request, fetcher Fetcher, queue chan []request, finished chan bool) { defer func() { finished <- true }() var links []request body, urls, err := fetcher.Fetch(req.url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", req.url, body) for _, u := range urls { link := request{ url: u, depth: req.depth + 1, } links = append(links, link) } queue <- links return } func Crawl(url string, depth int) { queue := make(chan []request) finished := make(chan bool) lookup := make(map[string]bool) seed := request{ url: url, depth: 1, } go Fetch(seed, fetcher, queue, finished) lookup[seed.url] = true outstanding := 1 for outstanding > 0 { select { case newRequests := <-queue: for _, r := range newRequests { if _, ok := lookup[r.url]; ok || r.depth >= depth { continue } else { go Fetch(r, fetcher, queue, finished) outstanding++ } } case <-finished: outstanding-- } } } func main() { Crawl("http://golang.org/", 4) } // 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) { 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/", }, }, }