Go net/http GET请求失败主因是忽略客户端默认行为:无User-Agent致403、重定向自动跟随致获取跳转页、未读完响应体就关闭连接、正则匹配HTML时未处理大小写及贪婪问题。

Go 用 net/http 发起 GET 请求失败的常见原因
多数人卡在第一步:请求发不出去,或返回空内容、状态码异常。根本不是代码写得不对,而是忽略了 HTTP 客户端默认行为。
-
http.DefaultClient默认不带User-Agent,很多网站直接 403 拒绝——加一个简单的头就能过:req.Header.Set("User-Agent", "Mozilla/5.0") - 重定向被自动跟随(
CheckRedirect默认 nil),但某些反爬页面会用 302 + JS 跳转绕过,这时你拿到的是跳转页 HTML,不是目标页——可设Client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } - 响应体没读完就关闭连接,导致后续读取为空——必须调用
resp.Body.Close(),且要在ioutil.ReadAll或io.Copy之后
用 regexp 提取 HTML 内容时为什么总匹配不上
正则不是不能用,是 HTML 太“松散”:换行、空格、属性顺序、自闭合标签都会让模式失效。别硬刚复杂结构,只抓确定格式的片段。
- HTML 标签大小写不敏感,但
regexp默认区分——加(?i)标志,比如re := regexp.MustCompile(`(?i)<title>]*>(.*?)</title>`) - 贪婪匹配会吃掉太多内容,比如
.*可能跨多个<div>——改用非贪婪 <code>.*?,或更稳妥地限定边界字符(如[^)<li>原始 HTML 常含转义字符(<code>&,<),正则按字面匹配会失败——先用html.UnescapeString解码再提取 - HTTP 连接复用靠
http.Transport,但默认MaxIdleConnsPerHost = 2,100 个 goroutine 会排队等连接——设为20或更高,同时配IdleConnTimeout - 没加限速的并发请求,DNS 查询、TCP 握手、TLS 握手全堆在一起,本地端口耗尽或超时频发——用带缓冲的 channel 控制并发数,比如
sem := make(chan struct{}, 5),每次 go 前sem ,结束后 <code><-sem - 不设请求间隔,高频请求大概率被识别为扫描——在每个请求后加
time.Sleep(1 * time.Second),比全局 sleep 更可控 -
http.Client默认无超时,一旦卡住就永远等——必须显式设置Timeout或用context.WithTimeout - DNS 解析单独耗时,尤其在容器或内网环境——设
Transport.DialContext配置net.Dialer.Timeout和KeepAlive - 某些云服务商对出站 HTTP 有隐式限速或 QoS,
deadline exceeded实际是被中间设备中断,不是 Go 程序的问题——加重试逻辑(最多 2 次),用指数退避
并发抓取多个 URL 时 CPU 和连接数暴增怎么办
开 goroutine 很容易,但不控速等于主动触发封 IP 或服务端限流。Go 的并发不是越多越好,而是要平衡吞吐与稳定性。
为什么本地跑通了,部署到服务器就报 context deadline exceeded
不是网络问题,是环境差异导致的超时表现不同。本地 DNS 快、路由短、防火墙松;服务器可能走代理、DNS 缓慢、甚至被出口策略限速。
立即学习“go语言免费学习笔记(深入)”;
事情说清了就结束。真正难的不是写出能跑的爬虫,是让同一段代码在不同网络、不同目标站点、不同部署环境下都稳定吐出干净数据——那得靠日志、超时、重试、降级这四样东西反复调。










