本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,实战演示如何使用 Goroutine 和 Channel,实现多协程并发抓取网页内容,提升网络请求效率,为构建爬虫、内容聚合器、API 批量采集器打下基础。
一、实战背景
在互联网项目中,我们常需要批量获取多个网页的内容,例如:
- • 爬虫程序抓取网页 HTML
- • 数据聚合服务请求多个 API
- • 批量检测多个 URL 的可用性
如果逐个请求(串行),效率将非常低下。Go 天生支持高并发,我们可以用 Goroutine 实现 多协程并发下载网页内容,显著提高吞吐能力。
二、实战目标
我们将构建一个小型并发网页下载器,具备以下能力:
- 1. 输入一组网址列表
- 2. 使用 Goroutine 并发请求多个网页
- 3. 使用 Channel 收集下载结果
- 4. 打印成功/失败状态与网页内容摘要
- 5. 支持 WaitGroup 等待所有任务完成
三、完整代码实现
package mainimport ("fmt""io""net/http""strings""sync""time"
)type Result struct {URL stringStatus stringLength intError error
}// 下载网页内容并写入结果通道
func fetchURL(url string, wg *sync.WaitGroup, resultCh chan<- Result) {defer wg.Done()client := http.Client{Timeout: 5 * time.Second,}resp, err := client.Get(url)if err != nil {resultCh <- Result{URL: url, Status: "请求失败", Error: err}return}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {resultCh <- Result{URL: url, Status: "读取失败", Error: err}return}resultCh <- Result{URL: url,Status: resp.Status,Length: len(body),}
}func main() {urls := []string{"https://example.com","https://httpbin.org/get","https://golang.org","https://nonexistent.example.com", // 故意的错误URL}var wg sync.WaitGroupresultCh := make(chan Result, len(urls))// 启动多个下载协程for _, url := range urls {wg.Add(1)go fetchURL(url, &wg, resultCh)}// 等待所有任务完成后关闭通道go func() {wg.Wait()close(resultCh)}()// 读取结果for res := range resultCh {if res.Error != nil {fmt.Printf("[失败] %s:%v\n", res.URL, res.Error)} else {snippet := fmt.Sprintf("%d 字节", res.Length)if res.Length > 0 {snippet = fmt.Sprintf("%s 内容预览:%s", snippet, strings.TrimSpace(string([]byte(res.URL)[:min(50, res.Length)])))}fmt.Printf("[成功] %s:%s\n", res.URL, snippet)}}fmt.Println("所有网页请求已完成。")
}func min(a, b int) int {if a < b {return a}return b
}
四、输出示例
[成功] https://example.com:1256 字节 内容预览:https://example.com
[成功] https://httpbin.org/get:349 字节 内容预览:https://httpbin.org/get
[成功] https://golang.org:3578 字节 内容预览:https://golang.org
[失败] https://nonexistent.example.com:Get "https://nonexistent.example.com": dial tcp: ...
所有网页请求已完成。
五、重点知识点讲解
1. 使用 Goroutine
启动并发请求
go fetchURL(url, &wg, resultCh)
每个网页请求都是一个轻量级的线程(协程),同时运行,最大化资源利用。
2. 使用 sync.WaitGroup
等待所有任务完成
WaitGroup
是 Goroutine 的最佳搭档,确保主线程不会提前退出。
wg.Add(1)
defer wg.Done()
3. 使用带缓冲的 Channel
收集结果
resultCh := make(chan Result, len(urls))
避免协程阻塞,收集所有结果后统一处理。
4. 设置请求超时
使用 http.Client{ Timeout: ... }
可防止因某个 URL 卡住导致整体阻塞。
5. 防止通道未关闭阻塞
一定要在所有任务完成后关闭结果通道:
go func() {wg.Wait()close(resultCh)
}()
六、可扩展方向
这个简单的并发网页下载器可以继续扩展为:
功能方向 | 实现建议 |
限制最大并发数 | 使用带缓冲的 chan struct{} 控制令牌 |
下载网页保存文件 | 使用 os.Create 写入 HTML 文件 |
支持重试机制 | 封装带重试的请求逻辑 |
使用 context 控制取消或超时 | 实现更复杂的任务调度系统 |
支持代理 | 设置 Transport.Proxy 实现 |
七、小结
通过本篇案例你掌握了:
✅ 使用 Goroutine 启动并发任务
✅ 使用 Channel 汇总任务结果
✅ 使用 WaitGroup 管理协程生命周期
✅ 网络请求的错误处理与超时机制
这为你实现一个功能完善的高并发爬虫、网页检测器或 API 批量处理工具奠定了基础。