直接用 net/http 默认请求易被封,因缺少浏览器头、自动重定向失控、HTTPS证书校验失败;应手动配置Client、设置UA、禁用自动跳转、谨慎绕过证书。

为什么不用 net/http 直接发请求就出问题?
很多刚写 Go 爬虫的人直接用 http.Get 抓网页,结果返回 403、空响应或重定向失败。根本原因是目标网站会检测 User-Agent、拒绝非浏览器请求,甚至校验 Accept、Accept-Language 等 header。更麻烦的是,有些页面依赖 JavaScript 渲染,纯 HTTP 请求拿不到真实内容。
实操建议:
- 务必手动设置
http.Client并带上常见浏览器 header,比如User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 - 禁用自动重定向(
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }),自己控制跳转逻辑,避免丢失 cookie 或状态 - 对 HTTPS 站点,若遇到证书错误(如自签名),临时绕过需显式设置
Transport.TLSClientConfig.InsecureSkipVerify = true,但上线前必须删掉
如何解析 HTML 并提取字段而不被结构变化搞崩?
用 golang.org/x/net/html 手动遍历 DOM 太重,而正则匹配 又太脆弱——只要 HTML 多个空格、换行或属性顺序一变就失效。稳定做法是结合 CSS 选择器 + 健壮的容错提取逻辑。
推荐用 github.com/PuerkitoBio/goquery(类 jQuery):
立即学习“go语言免费学习笔记(深入)”;
doc.Find("div.post-title a").Each(func(i int, s *goquery.Selection) {
title := strings.TrimSpace(s.Text())
href, _ := s.Attr("href")
// 注意:href 可能是相对路径,要用 base URL 拼接
fullURL := resolveURL(baseURL, href)
})
关键点:
- 永远用
s.Text()而不是s.Nodes[0].FirstChild.Data,前者自动处理文本节点合并与空白清理 - 用
s.Attr("href")获取属性,它比s.Get(0).Attr更安全(后者 panic 当属性不存在) - 别硬编码索引如
doc.Find("li").Eq(2),优先用语义化 selector,比如"article .content p:first-of-type"
命令行参数怎么设计才不被用户骂?
用户不会记 flag 含义,./crawler -u https://example.com -d 3 -o out.json 看似简洁,但漏了并发控制、超时、重试、User-Agent 自定义等刚需。Go 自带的 flag 包够用,但得把常见配置全暴露出来,且提供合理默认值。
核心参数建议:
-
-url:必填,目标起始 URL(校验是否以http://或https://开头) -
-depth:默认 2,避免无限爬;设为 0 表示只抓当前页 -
-concurrency:默认 3,防止被封;超过 10 需配合-delay使用 -
-delay:单位毫秒,默认 1000,每次请求后 sleep,模拟人工间隔 -
-output:支持json和csv,文件扩展名自动判断格式
别用 flag.String("ua", "", "user agent") 这种裸参数——加一行 flag.StringVar(&ua, "ua", defaultUA, "custom User-Agent string"),并内置一个主流 UA 字符串作为默认值。
为什么本地跑通了,部署到 Linux 服务器就卡住或报错?
最常见两个坑:DNS 解析超时和文件描述符耗尽。本地开发通常用 localhost 或 hosts 绑定,而线上环境走真实 DNS,若没设 http.Client.Timeout,可能卡在 lookup example.com 上几十秒;另外,默认每个 goroutine 开一个连接,concurrency=10 且深度为 3 时,瞬间可能打开上千个 socket,触发系统 ulimit -n 限制。
解决方法很具体:
- 给
http.Client显式设超时:Timeout: 10 * time.Second,同时设置Transport.MaxIdleConns和MaxIdleConnsPerHost到 20–50 - 用
runtime.GOMAXPROCS(2)限制并发协程数(尤其在低配 VPS 上),避免调度风暴 - Linux 下启动前执行
ulimit -n 65535,或在 systemd service 文件里加LimitNOFILE=65535 - 日志别只打
fmt.Println,用log.Printf("[INFO] %s", msg),否则重定向输出时时间戳和级别全丢
真正难调试的,往往不是语法错误,而是网络中间件(CDN、WAF、代理)对 TCP 连接复用、TLS 版本、SNI 的隐式要求——这时候得开 curl -v 对比请求头,再用 Go 的 http.Transport.DialContext 打印底层连接细节。










