在围棋之旅中,您会遇到以下问题:
在本练习中,您将使用Go的并发特性来并行化web爬虫。修改
Crawl函数,以并行方式获取URL,而不需要获取相同的URL两次。
这是你得到的密码
( "fmt“)类型的Fetcher接口{ // Fetch返回在该页面上找到的URL正文和//一个URL片段。Fetch(url字符串) (body string,url []string,err error) } // Crawl使用提取器递归地爬行//页面,以url开头,达到最大深度。功能爬行(url字符串、深度int、获取器Fetcher) { // TODO:并行获取url。// TODO:不要获取相同的URL两次。//此实现也不起作用:如果深度为<= 0{返回}主体、url、err := fetcher.Fetch(url)如果err != nil { fmt.Println(err)返回}fmt.Printf(“发现:%s %q\n",url,_ ),则u :=范围url{克雷格(u,深度-1,{http://golang.org/“,4,fakeFetcher } // Fetcher是返回罐装结果的Fetcher。键入fakeFetcher map字符串*fakeResult类型fakeResult struct { body string url []string } func (f fakeFetcher) Fetch(url string) (string,[]string,error) { if res,ok := furl;ok {返回res.body,res.urls,nil }返回“,nil,fmt.Errorf("not found:%s",url) } //取器是一个填充的fakeFetcher。fakeFetcher{ "http://golang.org/“:&fakeResult{“围棋编程语言”,[]字符串{ "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“,[]string{ "http://golang.org/“,"http://golang.org/pkg/“,},},"http://golang.org/pkg/os/“:&fakeResult{ "Package”,[]字符串{ "http://golang.org/“,"http://golang.org/pkg/“,}
这就是我想出的解决方案
type Job struct {
url string
depth int
done chan bool
}
func dedup(fetcher Fetcher, ch chan Job) {
seen := make(map[string]bool)
for job := range ch {
if _, ok := seen[job.url]; ok || job.depth <= 0 {
job.done <- true
continue
}
seen[job.url] = true
go Crawl(job, fetcher, ch)
}
}
func Crawl(job Job, fetcher Fetcher, q chan Job) {
body, urls, err := fetcher.Fetch(job.url)
if err != nil {
fmt.Println(err)
job.done <- true
return
}
fmt.Printf("found: %s %q\n", job.url, body)
ch := make(chan bool, len(urls))
for _, u := range urls {
u := u
go func() { q <- Job{u, job.depth - 1, ch} }()
}
for i := 0; i < len(urls); i++ {
<-ch
}
job.done <- true
return
}
func main() {
done := make(chan bool, 1)
q := make(chan Job)
go dedup(fetcher, q)
q <- Job{"http://golang.org/", 4, done}
<-done
}所有的工作都要经过dedup的S通道,所以一个给定的URL只能获取一次。dedup为它看到的每个唯一的URL打开一个大写,然后将URL从该页面添加回该通道。
Go的思维方式对我来说是新的,所以我主要关心的是
发布于 2015-03-07 23:25:40
if _, ok := seen[job.url]; ok || bar替换if seen[job.url] || bar。bool的零值(这是不存在的地图查找返回的值)是false。Crawl只立即执行一次像defer (){ job.done <- true }()这样的操作,而不是在所有正确的地方洒上job.done <- true,并希望您(和任何未来的编辑器)永远不会错过任何东西。for循环在Crawl中要么只需要对通道进行足够的缓冲(就像目前的情况一样),而不需要使用goroutines,要么整个for循环应该放到一个goroutine中(然后您就不需要任何通道缓冲了)。for i := 0; i < len(urls); i++应该是for i := range urls (或者因为您不需要i,只需要for _ = range urls)。Jobs.done一样使用sync.WaitGroup。您可以只需要一个等待组来跟踪优秀的工作。您的main将确保在发送第一个任务之前调用wg.Add(1),然后再调用wg.Wait()。大多数情况下,无论你做什么,job.done <- true都只是wg.Done()。您只需要确保您的爬虫在执行wg.Add(len(urls))之前执行了wg.Done(),以确保等待组计数不会过早意外下降到零。您可以使用上述点修改代码:
type Job struct {
url string
depth int
}
func dedup(fetcher Fetcher, ch chan Job, wg *sync.WaitGroup) {
seen := make(map[string]bool)
for job := range ch {
if seen[job.url] || job.depth <= 0 {
wg.Done()
continue
}
seen[job.url] = true
go Crawl(job, fetcher, ch, wg)
}
}
func Crawl(job Job, fetcher Fetcher, q chan Job, wg *sync.WaitGroup) {
defer wg.Done()
body, urls, err := fetcher.Fetch(job.url)
if err != nil {
log.Println("Crawl failure:", err) // TODO: something better with errors
return
}
log.Printf("Crawl: found %s\t%q\n", job.url, body)
if job.depth <= 1 {
return
}
wg.Add(len(urls))
for _, u := range urls {
q <- Job{u, job.depth - 1}
}
}
func main() {
wg := new(sync.WaitGroup)
wg.Add(1)
q := make(chan Job)
go dedup(fetcher, q, wg)
q <- Job{"http://golang.org/", 4}
wg.Wait()
// We close q here so that the dedup goroutine
// will finish. This isn't strictly needed here
// since we're main and everything gets stopped
// when we return; but that wouldn't be the case
// if we were some other function in a long lived
// program.
close(q)
}在操场上跑步(包括围棋):http://play.golang.org/p/cuY3PZdQmI
(比较而言:您的原始代码也是在操场上跑步。)
https://codereview.stackexchange.com/questions/70263
复制相似问题