Go 语言入门:从零开始构建一个高性能的并发 Web 爬虫

0 阅读1分钟

一、为什么选择 Go 来写爬虫?

在爬取成千上万个页面时,并发能力是关键。

  • Goroutine: 极轻量级的线程(通常只需几 KB 内存),单机支持数百万个并发。
  • Channel: 通过消息通信来共享内存(Don't communicate by sharing memory; share memory by communicating),让并发代码更安全、更易理解。
  • 标准库: 拥有非常强大的 net/http

二、基础版:顺序爬虫

func crawl(url string) {
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Printf("Crawled %s, Status: %s\n", url, resp.Status)
}

三、进阶版:并发爬虫 (Goroutine)

如果直接循环开启 Goroutine:

for _, url := range urls {
	go crawl(url)
}

问题: 可能会瞬间开启成千上万个请求,导致被目标服务器封禁或本地资源耗尽。

四、高手版:并发限制 (Worker Pool)

利用 Channel 实现一个 Worker Pool,精确控制并发数量。

func worker(id int, jobs <-chan string, results chan<- string) {
	for url := range jobs {
		fmt.Printf("Worker %d starting job %s\n", id, url)
		crawl(url)
		results <- url + " done"
	}
}

func main() {
	jobs := make(chan string, 100)
	results := make(chan string, 100)

	// 1. 开启 5 个并发 Worker
	for w := 1; w <= 5; w++ {
		go worker(w, jobs, results)
	}

	// 2. 发送任务
	for _, url := range urls {
		jobs <- url
	}
	close(jobs) // 发送完毕

	// 3. 收集结果
	for a := 1; a <= len(urls); a++ {
		<-results
	}
}

五、处理并发安全:Sync.WaitGroup

在实际项目中,我们通常配合 sync.WaitGroup 来等待所有任务完成。

var wg sync.WaitGroup
for _, url := range urls {
	wg.Add(1)
	go func(u string) {
		defer wg.Done()
		crawl(u)
	}(url)
}
wg.Wait()

六、总结

Go 的并发模型(CSP 模型)让高并发编程变得平民化。通过 Channel 和 Goroutine 的配合,我们可以非常简单地构建出高性能的后端服务。

你是否在项目中使用过 Go 的并发特性?欢迎分享你的实战技巧!