首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >去吧,小工具,网络爬虫

去吧,小工具,网络爬虫
EN

Code Review用户
提问于 2014-11-19 02:33:58
回答 1查看 2.3K关注 0票数 15

围棋之旅中,您会遇到以下问题:

在本练习中,您将使用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/“,}

这就是我想出的解决方案

代码语言:javascript
复制
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的思维方式对我来说是新的,所以我主要关心的是

  • 这是习语吗?
  • 你知道一个熟悉围棋的人怎么了吗?
  • 以这种方式缓冲信道是否必要(甚至是有用的)?
EN

回答 1

Code Review用户

回答已采纳

发布于 2015-03-07 23:25:40

  • 您的地图是布尔图,所以您可以用if _, ok := seen[job.url]; ok || bar替换if seen[job.url] || barbool的零值(这是不存在的地图查找返回的值)是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(),以确保等待组计数不会过早意外下降到零。
  • 尽管进行深度检查可能仍然是个好主意,但在爬行功能中这样做很容易,而且节省了一些精力。

您可以使用上述点修改代码:

代码语言:javascript
复制
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

(比较而言:您的原始代码也是在操场上跑步。)

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/70263

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档