正确做法是读完响应体后立即调用 resp.body.close(),避免连接泄漏;automigrate 不会自动更新字段,需手动验证表结构;goquery 查不到元素优先排查 http 层问题;并发数应根据网络和目标站调整,配合限流与连接池配置。

用 net/http 发起请求时,别直接忽略 response.Body
Go 的 HTTP 客户端不会自动关闭响应体,不手动调用 resp.Body.Close() 会导致连接泄漏,爬虫跑一会儿就卡死或报 too many open files 错误。
常见错误是只读取内容就完事:io.ReadAll(resp.Body) 后没关;或者用 defer resp.Body.Close() 却忘了它在函数返回时才执行——如果中间 panic 或提前 return,照样漏关。
- 正确做法:读完立刻关,不要 defer(除非你 100% 确保函数只有一处 return)
- 更稳妥写法:
resp, err := http.Get(url) if err != nil { return err } defer func() { if resp.Body != nil { resp.Body.Close() } }() body, _ := io.ReadAll(resp.Body) - 注意:如果用
http.Client自定义超时或重试,Body关闭逻辑不变
用 GORM 插入结构体前,先检查 AutoMigrate 是否真生效了
很多人以为调一次 db.AutoMigrate(&Article{}) 就万事大吉,其实 GORM 不会自动加字段、改类型、删列,也不会报错提示“这个字段我跳过了”。表结构和 struct 对不上时,插入可能静默失败,或存空值。
典型场景:开发中加了个 PublishedAt time.Time 字段,但数据库表没更新,GORM 插入时既不报错也不写入该字段,查出来就是零值。
立即学习“go语言免费学习笔记(深入)”;
- 每次改 model 后,手动确认数据库实际字段:用
DESCRIBE articles或工具看 - 生产环境禁用
AutoMigrate,改用迁移脚本;开发期可加日志:db.Debug().AutoMigrate(&Article{})看 GORM 实际执行的 SQL - 注意字段标签:
gorm:"not null"和gorm:"default:0"必须显式写,否则 GORM 可能按零值处理而非数据库默认值
解析 HTML 用 goquery 时,Find 返回空不等于页面没加载成功
doc.Find("h1.title") 返回空结果,大概率不是 selector 写错了,而是 HTML 结构根本没按预期加载——比如页面是 JS 渲染的,或者用了反爬 data-* 动态属性,又或者你没处理重定向后的最终 URL。
真实爬虫里,80% 的“找不到元素”问题出在 HTTP 层,而不是 selector 层。
- 先打日志:
fmt.Printf("status: %d, url: %s\n", resp.StatusCode, resp.Request.URL),确认是不是 302 跳转或 403 被拦 - 检查 Content-Type 是否为
text/html,有些站点返回 JSON 或登录页却给 200 - 用
doc.Find("title").Text()打个桩,确认 goquery 至少能读到基础节点;如果连 title 都空,说明 HTML 解析失败(比如编码不对,试试charset.NewReaderLabel)
并发抓取多个 URL 时,goroutine 数量不能只靠 runtime.NumCPU() 猜
本地 CPU 是 8 核,不代表你能开 8 个 goroutine 去发 HTTP 请求。网络 IO 是瓶颈,不是 CPU;开太多反而触发 TCP 连接池耗尽、DNS 超时、目标站限流,甚至被封 IP。
真正有效的并发控制,得结合目标站点响应时间、连接池设置、以及你的带宽/出口 IP 能力来调。
- 起步设成 3–5 个 goroutine,观察平均响应时间和错误率;再逐步加到 10–20
- 必须配
http.Client的Transport:client := &http.Client{ Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 30 * time.Second, }, } - 别用无缓冲 channel 控制并发,容易死锁;用
semaphore.NewWeighted(5)(来自golang.org/x/sync/semaphore)更稳
事情说清了就结束。真正的难点不在“怎么写”,而在“怎么让每次请求都可靠、每条数据都对得上、每个 goroutine 都收得回来”。这些细节不盯住,跑两天就崩。










