题目
在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
修改 Crawl 函数来并行地抓取 URL,并且保证不重复。
提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!
实现逻辑
- 采用锁实现互斥使用map
- 增加等待组实现等待线程结束
- 使用struct来组合map,锁,等待组三种数据
- 为上述struct增加一个exist方法,用于判断url是否存在,不存在则存入且增加等待数量
代码
package main
import (
"fmt"
"sync"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
type SafeMap struct {
v map[string]int
mux sync.Mutex // 访问互斥锁
wg sync.WaitGroup // 等待组
}
func (c *SafeMap) exist(url string) bool {
c.mux.Lock()
defer c.mux.Unlock()
_, ok := c.v[url]
if ok {
return true
} else {
c.v[url] = 1
c.wg.Add(1) // 没见过的url需要等待一个Craw去爬取
return false
}
}
var c = SafeMap{v: make(map[string]int)}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
defer c.wg.Done() // 一个Crawl对应一个waitgroup
if depth <= 0 {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
if c.exist(u) == false {
go Crawl(u, depth-1, fetcher)
}
}
return
}
func main() {
url := "https://golang.org/"
c.exist(url)
Crawl(url, 4, fetcher)
c.wg.Wait()
}
// fakeFetcher 是返回若干结果的 Fetcher。
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 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}
运行结果
found: https://golang.org/ “The Go Programming Language”
not found: https://golang.org/cmd/
found: https://golang.org/pkg/ “Packages”
found: https://golang.org/pkg/os/ “Package os”
found: https://golang.org/pkg/fmt/ “Package fmt”
Program exited.
遇到的问题
- 初始值未考虑,导致有两条数据重复
- exist方法应该绑定指针对象,否则main与Craw使用的SafeMap不是同一个
参考
Go 指南 – 练习:Web 爬虫 https://blog.csdn.net/u012439764/article/details/94589271