GO指南:练习-Web爬虫

原题目:Exercise: Web Crawler
中文原题目可以参看:练习:Web 爬虫

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
	// TODO: 并行的抓取 URL。
	// TODO: 不重复抓取页面。
        // 下面并没有实现上面两种情况:

这个题里需要处理的

  • 协程间同步的问题。使用无缓冲信道进行协程同步处理,进行并行抓取;
  • 协程安全sync.Mutex的使用,可以参考golang A Tour of Go:sync.Mutex

实现代码如下:

...
type safeState struct {
	v   map[string]bool
	mux sync.Mutex
}

func (c *safeState) setState(key string, state bool) {
	c.mux.Lock()
	c.v[key] = state
	c.mux.Unlock()
}

func (c *safeState) value(key string) (bool, bool) {
	c.mux.Lock()
	defer c.mux.Unlock()
	v, ok := c.v[key]
	return v, ok
}

var (
	// urlm     = make(map[string]bool)
	urlState = safeState{v: make(map[string]bool)}
)

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, ch chan int) {
	// TODO: 并行的抓取 URL。
	// TODO: 不重复抓取页面。
	// 下面并没有实现上面两种情况:

	defer func() {
		ch <- 1
	}()
	if depth <= 0 {
		return
	}

	// 并发不安全的实现
	// if _, ok := urlm[url]; ok {
	// 	return
	// }
	// urlm[url] = false
	// defer func() { urlm[url] = true }()

	// 并发安全的实现
	if _, ok := urlState.value(url); ok {
		return
	}

	urlState.setState(url, false)
	defer urlState.setState(url, true)
	// 并发安全的实现结束

	subCh := make(chan int)
	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 {
		go Crawl(u, depth-1, fetcher, subCh)
	}
	for i := 0; i < len(urls); i++ {
		<-subCh
	}
	return
}

func main() {
	// 使用信道进行同步控制
	ch := make(chan int)
	go Crawl("https://golang.org/", 4, fetcher, ch)
	<-ch
}
...

你可能感兴趣的:(GO指南:练习-Web爬虫)