本文是《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 批量處理工具奠定了基礎。