Go concurrent crawler

下面是a tour of go 中最后一个练习的解法,本文使用了真正的网络io来运行爬虫,由于没有对连接数做限制,所以当depth为3时会发生错误

package main

import (
    "fmt"
    "log"
    "strings"
    "sync"

    "github.com/PuerkitoBio/goquery"
)

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) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:
    if depth <= 0 {
        return
    }
    _, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    //fmt.Printf("found: %s %q\n", url, body)
    wg.Add(len(urls))
    for _, u := range urls {
        go func(u string) {
            defer wg.Done()
            Crawl(u, depth-1, fetcher)
        }(u)//note:can't use u directly because u can be changed in later loops
    }
    return
}

func main() {
    fetcher.result = make(map[string]*realResult)
    Crawl("http://golang.org/", 2, fetcher)
    wg.Wait()
    return
}

// realFetcher is Fetcher that returns canned results.
type realFetcher struct {
    result map[string]*realResult
    mux    sync.Mutex
}

type realResult struct {
    body string
    urls []string
}

func (f realFetcher) Fetch(url string) (string, []string, error) {
    f.mux.Lock()
    if res, ok := f.result[url]; ok {
        f.mux.Unlock()
        fmt.Printf("exist:%s------\n", url)
        return res.body, res.urls, nil
    }
    f.mux.Unlock()

    fmt.Printf("fetching:%s------\n", url)

    doc, err := goquery.NewDocument(url)
    if err != nil {
        log.Fatal(err)
        return "", nil, fmt.Errorf("Process error:%s", url)
    }

    f.mux.Lock()
    f.result[url] = &realResult{doc.Find("body").Text(), nil}
    f.mux.Unlock()

    // Find the review items
    doc.Find("a").Each(func(i int, s *goquery.Selection) {
        // For each item found, get the band and title
        href, _ := s.Attr("href")
        if strings.HasPrefix(href, "//") {
            href = "http://" + href[2:]
        } else if strings.HasPrefix(href, "/") {
            href = url + href[1:]
        } else if len(href) == 0 || strings.HasPrefix(href, "#") {
            href = ""
        } else if !strings.HasPrefix(href, "http") {
            href = url + href
        }

        if href != "" {
            f.mux.Lock()
            f.result[url].urls = append(f.result[url].urls, href)
            f.mux.Unlock()
        }
    })
    f.mux.Lock()
    defer f.mux.Unlock()
    return doc.Find("body").Text(), f.result[url].urls, nil
}

// fetcher is a populated realFetcher.
var fetcher = realFetcher{}
var wg sync.WaitGroup
### 回答1: Go 语言的标准库中提供了 `net/http` 包,支持 HTTP 协议的客户端和服务端编程。Go 语言的并发模型让它特别适合用来编写高效的 HTTP 服务器。 使用 Go 语言编写 HTTP 服务器通常可以通过以下步骤来实现: 1. 实现一个处理器函数,该函数接受一个 `http.ResponseWriter` 和一个 `*http.Request` 参数,并使用它们来响应请求。 2. 使用 `http.HandleFunc` 函数将该处理器函数注册到一个路由上。 3. 使用 `http.ListenAndServe` 函数启动服务器。 例如,以下代码演示了如何在 8080 端口上启动一个服务器,并在根路径("/")上注册一个处理器函数,用于响应 HTTP GET 请求: ```go package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } ``` 在这个例子中,当客户端发起一个 HTTP GET 请求到根路径("/")时,服务器会调用处理器函数 `handler` 并将其写入响应。 希望这个回答对你有帮助。 ### 回答2: Go语言是一种开发高并发应用程序的优秀选择,它内置了强大的并发支持。在Go语言中,可以使用Goroutine和Channel来实现并发处理HTTP请求。 Goroutine是轻量级线程,可以在一个Go程序中同时运行多个Goroutine。可以将HTTP请求处理逻辑封装在一个Goroutine中,每个请求都会启动一个新的Goroutine来处理,从而实现并发处理。 Channel是Goroutine之间的通信机制,可以用于在Goroutine之间传递数据。在HTTP请求处理逻辑中,可以使用Channel来传递处理结果,例如将响应数据发送到一个Channel中,然后通过另一个Goroutine将数据写回响应。 使用Golang的net/http包可以方便地创建HTTP服务器。可以通过调用http.ListenAndServe函数来启动一个HTTP服务器,然后使用http.HandleFunc函数来注册处理HTTP请求的函数。在这个处理函数中,可以启动Goroutine来处理每个请求,再通过Channel将处理结果返回给主Goroutine。 在多个Goroutine之间共享数据时,需要注意并发访问的安全性。可以使用Golang提供的sync包中的互斥锁来保证并发访问的安全性。通过在访问共享数据的代码块中使用互斥锁进行锁定操作,可以防止多个Goroutine同时访问修改共享数据。 总的来说,使用Goroutine和Channel可以方便地实现Golang并发处理HTTP请求。通过合理地使用这些特性,可以提高系统的并发性能和稳定性。 ### 回答3: Golang是一种支持并发编程的编程语言,因此在Golang中实现并发的HTTP请求是非常简单的。Golang的标准库中提供了一个名为"net/http"的包,该包包含了处理HTTP请求和响应的相关函数和结构体。 通过使用Golang的HTTP库,我们可以轻松地编写并发的HTTP请求代码。首先,我们可以使用"goroutine"来创建并发的执行环境,每个goroutine都可以处理一个HTTP请求。我们可以使用"net/http"包的"Get"函数创建一个HTTP请求,并将其放入goroutine中处理。例如,下面的代码展示了如何并发地向多个URL发送HTTP请求: ```go package main import ( "fmt" "net/http" ) func main() { urls := []string{"https://www.example.com", "https://www.google.com", "https://www.github.com"} for _, url := range urls { go func(u string) { resp, err := http.Get(u) if err != nil { fmt.Printf("Error fetching %s: %s\n", u, err.Error()) return } defer resp.Body.Close() fmt.Printf("Successfully fetched %s\n", u) }(url) } // 等待所有goroutine完成 for i := 0; i < len(urls); i++ { <-done } fmt.Println("All requests finished.") } ``` 在上面的例子中,我们使用一个包含三个URL的字符串切片作为输入,然后通过循环创建了三个goroutine,并发地向这些URL发送HTTP请求。每个goroutine都会调用"Get"方法发送请求,并在请求完成后打印相应的结果。 需要注意的是,我们使用了一个无缓冲的通道"done"来等待所有的goroutine结束。每个goroutine在完成处理请求后,都会通过"done"通道发送一个信号。通过在主函数中使用"<-"操作符从通道中接收信号,我们可以保证在所有的HTTP请求完成后再继续执行下面的操作。 通过上面的示例,我们可以看到,在Golang中实现并发的HTTP请求非常简单。通过使用goroutine和"net/http"包,我们可以轻松地编写高效且可扩展的并发代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值