Go图片爬虫必须限流,因无节制启goroutine会迅速耗尽文件描述符(默认1024)、触发DNS阻塞或429错误;需用固定worker数+channel任务队列解耦生产与消费,并配置HTTP超时、URL补全、data-src兼容及gzip支持。

Go 图片爬虫用协程是可行的,但直接裸写 go download(url) 容易触发连接耗尽、DNS 阻塞或目标站反爬,必须加限流、超时和错误重试。
为什么不能无节制启动 goroutine
看似 go download(img_url) 一行就并发了,实际会快速打满系统文件描述符(Linux 默认常为 1024),http.Get 失败时返回 dial tcp: lookup xxx: no such host 或 too many open files。多个协程同时解析 HTML 也可能因 goquery.NewDocument 内部未设超时而卡死。
- 每个
http.Client默认复用连接,但没配Timeout或MaxIdleConns,容易堆积空闲连接 -
goquery的NewDocument底层调用http.Get,不显式传入带超时的 client,就会用默认无限等待的 client - 图片 URL 若含重定向或响应头缺失
Content-Length,ioutil.ReadAll可能阻塞直到连接超时(默认约 30 秒)
如何安全地控制并发数量
用固定数量 worker + channel 任务队列是最稳的方式,比 for range urls { go ... } 更可控。核心是把“发现 URL”和“下载 URL”解耦,避免瞬时爆发。
- 定义一个缓冲 channel:
imgChan := make(chan string, 1000),避免生产者阻塞 - 启动固定数目的下载协程,例如 5–10 个:
for i := 0; i - 爬取页面后,把提取到的
src往imgChan里塞,不直接go download - 所有生产者结束后
close(imgChan),worker 收到 closed channel 自然退出
下载函数必须带超时与重试
http.Get 默认无超时,且不处理 4xx/5xx 状态码,直接读 body 会 panic。要用自定义 http.Client 并检查响应状态。
立即学习“go语言免费学习笔记(深入)”;
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 20,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 30 * time.Second,
},
}
resp, err := client.Get(imgURL)
if err != nil {
log.Printf("GET %s failed: %v", imgURL, err)
return
}
if resp.StatusCode >= 400 {
log.Printf("HTTP %d for %s", resp.StatusCode, imgURL)
return
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
- 不要用
ioutil.ReadAll(已弃用),改用io.ReadAll - 文件名建议用
url.Path+ 时间戳,避免strings.LastIndex在 URL 无/时 panic - 写文件前确保目录存在:
os.MkdirAll(dir, 0755)
goquery 解析 HTML 时容易忽略的点
goquery.Find(".pic > a > img") 看似简洁,但若页面结构动态加载(如 JS 渲染)、class 名变化或嵌套层级不一致,会漏抓。别全信 selector,先 doc.Text() 看是否真有内容。
- 加
doc.Find("img").Each(...)做兜底,再用s.Attr("src")过滤非空值 - 有些图片用
data-src而非src,需额外判断:s.Attr("data-src") - 相对路径要补全:
absURL, _ := url.ParseRequestURI(src); fullURL := relURL.ResolveReference(absURL) - 注意
goquery不自动处理 gzip,若服务端返回压缩内容,需手动解压或换用支持的 client
真正难的不是启动多少 goroutine,而是让每个请求在失败时不拖垮整个流程,且资源可预测。worker 数量、channel 缓冲大小、HTTP 超时值——这三个参数调不好,爬虫跑一小时后就静默卡住,连日志都不吐。


















