如何在Golang中实现一个简单的Web爬虫并存入MySQL Go语言GORM入门

1次阅读

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

如何在Golang中实现一个简单的Web爬虫并存入MySQL Go语言GORM入门

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: %sn", 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.ClientTransport
    client := &http.Client{     Transport: &http.Transport{         MaxIdleConns:        100,         MaxIdleConnsPerHost: 100,         IdleConnTimeout:     30 * time.Second,     }, }
  • 别用无缓冲 channel 控制并发,容易死锁;用 semaphore.NewWeighted(5)(来自 golang.org/x/sync/semaphore)更稳

事情说清了就结束。真正的难点不在“怎么写”,而在“怎么让每次请求都可靠、每条数据都对得上、每个 goroutine 都收得回来”。这些细节不盯住,跑两天就崩。

text=ZqhQzanResources