題目
在這個練習中,我們將會使用 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