必须手写*http.Client:设10秒超时、连接池、UA头、禁自动跳转;HTML解析用goquery而非正则;并发用信号量限流;URL去重+深度控制保命;响应体及时关闭并处理中文编码。
别用
,必须手写
直接调
看似省事,但上线两小时就挂——不是代码错,是默认客户端没超时、没连接池、没 UA,目标站一拦一个准,本地还容易耗尽文件描述符。
显式构造
,至少设
加
控制连接复用:
和
是安全起点
必须设,值用常见浏览器字符串,比如
,别留空或用 Go 默认标识
遇到跳转页被重定向吃掉原始响应(比如 302 后返回登录页),要禁自动跳转:
解析 HTML 别碰正则,
是底线选择
用
提取
或链接?页面多一个空格、换一行、属性顺序一变,就全失效。这不是写得不够巧,是根本不在同一抽象层上。
用
,它底层调的是
,能处理真实 DOM 结构
比
安全得多——前者自动合并文本节点、清理空白,后者一碰到注释或 CDATA 就 panic
返回
,务必检查第二个值;别用
,属性不存在时直接崩溃
相对 URL 要转绝对地址:
,否则存下来全是
这种无效链接
并发不是开越多越快,
才管用
起 100 个 goroutine 直接
,看着快,实则三秒后卡死:DNS 查询阻塞、TCP 端口占满、目标站返回 429、本地
全来了。
用带缓冲的 channel 当信号量:
,每次发请求前
,结束后
别用
模拟人工节奏——它不防并发突增;真要限频,用
对同一域名做请求间隔,可用
记录上次时间,配合
判断是否可发
Colly 用户注意:
只是开关,并发数由
控制,不设就是串行
去重和深度控制不是可选项,是保命线
没 URL 去重?爬虫三分钟内就会在两个页面之间反复横跳;不限制深度?从首页开始,
一递归,几万链接瞬间进队列,内存爆掉只是时间问题。
立即学习
“
go语言免费学习笔记(深入)
”;
去重 key 必须标准化:
,去掉 fragment,统一大小写
用
内存去重最快;如果需跨进程或持久化,再考虑 Redis 或 Bloom Filter
参数建议默认为
,设为
表示只抓当前页;Colly 用户记得配
发现新链接时,先查重再入队,BFS 模式比 DFS 更可控
最常被忽略的不是怎么写,而是怎么收尾:所有
必须
,所有
不用关,但它的源
如果是文件或自定义流,就得你来关。还有,中文乱码不是编码错了,是没用
套一层响应体。
http.Get*http.Clienthttp.Get*http.ClientTimeout: 10 * time.SecondTransportMaxIdleConns: 100MaxIdleConnsPerHost: 100User-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 }goqueryregexpgithub.com/PuerkitoBio/goquerygolang.org/x/net/htmls.Text()s.Nodes[0].FirstChild.Datas.Attr("href")(string, bool)s.Get(0).Attrurl.Join(baseURL, href)/post/123sem := make(chan struct{}, N)c.Visit()too many open filessem := make(chan struct{}, 4)sem <- struct{}{}<-semtime.Sleepgolang.org/x/time/rate.Limitersync.Maptime.Sincec.Async = truec.Limit(&colly.LimitRule{Parallelism: 4})a[href]strings.ToLower(u.Scheme + "://" + u.Host + u.EscapedPath())map[string]struct{}-depth20c.MaxDepth = 2resp.Bodydefer resp.Body.Close()goquery.Documentio.Readercharset.NewReaderLabel