Skip to content

Commit e448633

Browse files
committed
Go: Add webcrawler example
1 parent e7a9b8a commit e448633

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

‎go/crawler.go‎

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
// From https://tour.golang.org/concurrency/10
4+
5+
import (
6+
"fmt"
7+
"sync"
8+
)
9+
10+
type Fetcher interface {
11+
// Fetch returns the body of URL and
12+
// a slice of URLs found on that page.
13+
Fetch(url string) (body string, urls []string, err error)
14+
}
15+
16+
// Crawl uses fetcher to recursively crawl
17+
// pages starting with url, to a maximum of depth.
18+
func Crawl(url string, depth int, fetcher Fetcher) {
19+
// TODO: Fetch URLs in parallel.
20+
// TODO: Don't fetch the same URL twice.
21+
// This implementation doesn't do either:
22+
if depth <= 0 {
23+
return
24+
}
25+
body, urls, err := fetcher.Fetch(url)
26+
if err != nil {
27+
fmt.Println(err)
28+
return
29+
}
30+
fmt.Printf("found: %s %q\n", url, body)
31+
for _, u := range urls {
32+
Crawl(u, depth-1, fetcher)
33+
}
34+
return
35+
}
36+
37+
// SafeMap includes a mutex for locking the map before reading or writing
38+
type SafeMap struct {
39+
mu sync.Mutex
40+
fetched map[string]bool
41+
}
42+
43+
// ConcurrentMutexCrawler does parallel crawl and uses a mutex for locking
44+
func ConcurrentMutexCrawler(url string, mp *SafeMap, fetcher Fetcher) {
45+
// Check whether url is already fetched or someone is doing it
46+
mp.mu.Lock()
47+
urlDone := mp.fetched[url]
48+
mp.fetched[url] = true // Mark that I'm doing it
49+
mp.mu.Unlock()
50+
51+
if urlDone {
52+
return
53+
}
54+
55+
body, urls, err := fetcher.Fetch(url)
56+
if err != nil {
57+
fmt.Println(err)
58+
return
59+
}
60+
61+
fmt.Printf("Found: %s %q\n", url, body)
62+
63+
var done sync.WaitGroup
64+
for _, u := range urls {
65+
// fmt.Printf("Urls: %s\n", u)
66+
done.Add(1)
67+
// Pass as arg otherwise it'll use a different value of u
68+
// Arg will be a private copy of the goroutine
69+
go func(url string) {
70+
ConcurrentMutexCrawler(url, mp, fetcher)
71+
done.Done()
72+
}(u)
73+
}
74+
75+
done.Wait()
76+
}
77+
78+
func main() {
79+
// Crawl("https://golang.org/", 4, fetcher)
80+
81+
mp := SafeMap{fetched: make(map[string]bool)}
82+
ConcurrentMutexCrawler("https://golang.org/", &mp, fetcher)
83+
}
84+
85+
// fakeFetcher is Fetcher that returns canned results.
86+
type fakeFetcher map[string]*fakeResult
87+
88+
type fakeResult struct {
89+
body string
90+
urls []string
91+
}
92+
93+
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
94+
if res, ok := f[url]; ok {
95+
return res.body, res.urls, nil
96+
}
97+
return "", nil, fmt.Errorf("not found: %s", url)
98+
}
99+
100+
// fetcher is a populated fakeFetcher.
101+
var fetcher = fakeFetcher{
102+
"https://golang.org/": &fakeResult{
103+
"The Go Programming Language",
104+
[]string{
105+
"https://golang.org/pkg/",
106+
"https://golang.org/cmd/",
107+
},
108+
},
109+
"https://golang.org/pkg/": &fakeResult{
110+
"Packages",
111+
[]string{
112+
"https://golang.org/",
113+
"https://golang.org/cmd/",
114+
"https://golang.org/pkg/fmt/",
115+
"https://golang.org/pkg/os/",
116+
},
117+
},
118+
"https://golang.org/pkg/fmt/": &fakeResult{
119+
"Package fmt",
120+
[]string{
121+
"https://golang.org/",
122+
"https://golang.org/pkg/",
123+
},
124+
},
125+
"https://golang.org/pkg/os/": &fakeResult{
126+
"Package os",
127+
[]string{
128+
"https://golang.org/",
129+
"https://golang.org/pkg/",
130+
},
131+
},
132+
}

0 commit comments

Comments
 (0)