Golang协程与网络爬虫的实现
Golang协程与网络爬虫的实现
以下示例将获取第一个URL后程序就会退出,因为我在运行goroutine go crawl()
如何等待爬虫完成工作(除了在最后使用fmt.Scanln())?
func Crawl(url string, depth int) {
if depth <= 0 {
return
}
urls, err := Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Found urls in ", url)
for _, u := urls {
go Crawl(u, depth-1)
}
return
}
func Fetch(url string) []string, error {
// ... 从URL获取/处理数据
if urls, ok := data[url]; ok {
return urls, nil
}
return nil, fmt.Errorf("error %s", url)
}
func main() {
Crawl("golang.org", 4)
}
注: 这是来自Go语言之旅:网络爬虫练习的练习题
更多关于Golang协程与网络爬虫的实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
使用 sync.WaitGroup。请参考此示例。
更多关于Golang协程与网络爬虫的实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
可以使用 sync.WaitGroup 来等待所有爬虫协程完成工作。以下是修改后的代码:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func Crawl(url string, depth int) {
defer wg.Done() // 协程结束时通知WaitGroup
if depth <= 0 {
return
}
urls, err := Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Found %d urls in %s\n", len(urls), url)
for _, u := range urls {
wg.Add(1) // 为每个新的爬虫协程增加计数
go Crawl(u, depth-1)
}
}
func Fetch(url string) ([]string, error) {
// 模拟获取URL数据
data := map[string][]string{
"golang.org": {"golang.org/pkg", "golang.org/doc"},
"golang.org/pkg": {"golang.org/pkg/fmt", "golang.org/pkg/os"},
"golang.org/doc": {"golang.org/doc/tutorial", "golang.org/doc/effective_go"},
}
if urls, ok := data[url]; ok {
return urls, nil
}
return nil, fmt.Errorf("error fetching %s", url)
}
func main() {
wg.Add(1) // 为主爬虫任务增加计数
go Crawl("golang.org", 4)
wg.Wait() // 等待所有爬虫协程完成
fmt.Println("All crawling tasks completed")
}
另一个更优雅的解决方案是使用带缓冲的channel来限制并发数量:
package main
import (
"fmt"
"sync"
)
var tokens = make(chan struct{}, 20) // 限制最多20个并发爬虫
func Crawl(url string, depth int, wg *sync.WaitGroup) {
defer wg.Done()
if depth <= 0 {
return
}
tokens <- struct{}{} // 获取令牌
urls, err := Fetch(url)
<-tokens // 释放令牌
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Found %d urls in %s\n", len(urls), url)
for _, u := range urls {
wg.Add(1)
go Crawl(u, depth-1, wg)
}
}
func Fetch(url string) ([]string, error) {
// 模拟获取URL数据
data := map[string][]string{
"golang.org": {"golang.org/pkg", "golang.org/doc"},
"golang.org/pkg": {"golang.org/pkg/fmt", "golang.org/pkg/os"},
"golang.org/doc": {"golang.org/doc/tutorial", "golang.org/doc/effective_go"},
}
if urls, ok := data[url]; ok {
return urls, nil
}
return nil, fmt.Errorf("error fetching %s", url)
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go Crawl("golang.org", 4, &wg)
wg.Wait()
fmt.Println("All crawling tasks completed")
}
关键修改:
- 使用
sync.WaitGroup来跟踪所有协程的完成状态 - 在每个协程开始时调用
wg.Add(1),结束时调用wg.Done() - 在主函数中调用
wg.Wait()阻塞直到所有协程完成 - 第二个示例还添加了并发限制,避免创建过多协程

