Go语言如何开发命令行爬虫工具_Golang CLI爬虫项目实战

6次阅读

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

Go语言如何开发命令行爬虫工具_Golang CLI爬虫项目实战

为什么不用 net/http 直接发请求就出问题?

很多刚写 go 爬虫的人直接用 http.Get 抓网页,结果返回 403、空响应或重定向失败。根本原因是目标网站会检测 User-Agent、拒绝非浏览器请求,甚至校验 AcceptAccept-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:支持 jsoncsv,文件扩展名自动判断格式

别用 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.MaxIdleConnsMaxIdleConnsPerHost 到 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 打印底层连接细节。

text=ZqhQzanResources