1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
package main
import (
"fmt"
"sync"
)
type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {
// TODO: 并行的抓取 URL。
// TODO: 不重复抓取页面。
// 下面并没有实现上面两种情况:
if depth <= 0 {
return
}
// 避免重复抓取页面
if checkUrl(url) {
return
}
// 标记已抓取页面
maskUrlCount(url, 1)
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
// 题目:修改 Crawl 函数来并行地抓取 URL,并且保证不重复。
// FIXME: 抓取失败是否需要标记未抓取?
// maskUrlCount(url, -1)
return
}
fmt.Printf("found: %s %q\n", url, body)
// 并行的抓取URL
ch := make(chan int)
for _, u := range urls {
go func(url string) {
Crawl(url, depth-1, fetcher)
ch <- 1
}(u)
}
// 等待抓取完成
for range urls {
<-ch
}
return
}
var urlCounter UrlCounter
func checkUrl(url string) bool {
urlCounter.mux.Lock()
defer urlCounter.mux.Unlock()
_, ok := urlCounter.v[url]
// if ok && c == 0 {
// return false
// }
return ok
}
func maskUrlCount(url string, count int) {
urlCounter.mux.Lock()
defer urlCounter.mux.Unlock()
urlCounter.v[url] += count
}
func main() {
urlCounter = UrlCounter{v: make(map[string]int)}
Crawl("http://golang.org/", 4, fetcher)
}
// UrlCounter 的并发使用是安全的。
type UrlCounter struct {
v map[string]int
mux sync.Mutex
}
// 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{
"http://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"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 fmt",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
"http://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
}
|