Go语言适合写爬虫,因其并发模型、内存效率和静态编译能力保障中等规模抓取的稳定性与易部署性;需显式配置http.Client超时、User-Agent、连接复用及Cookie管理,优先使用goquery解析HTML,规避正则风险,并严格控制并发数。

Go 语言适合写爬虫,但不是因为它“自带爬虫库”,而是因为它的并发模型、内存效率和静态编译能力让中等规模的抓取任务既稳定又易部署。直接上 net/http + goquery 就能开工,但绕不开反爬、超时控制和 HTML 解析容错——这些才是实际卡点。
用 http.Client 控制请求行为,别依赖默认配置
默认的 http.Client 没有超时,遇到挂掉的站点会永久阻塞 goroutine;也没有 User-Agent,容易被秒封。
实操建议:
- 始终显式设置
Timeout(比如 10 秒),并区分ConnectTimeout和ReadTimeout(用http.Transport配置) - 给每个请求加
User-Agentheader,值选常见浏览器(如"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"),避免用 Go 默认标识 - 启用连接复用:设置
MaxIdleConns和MaxIdleConnsPerHost(例如都设为 100),否则高频请求会反复建连 - 若目标站有登录态或 Cookie 依赖,用
http.CookieJar管理,别手动拼Cookie字符串
解析 HTML 别硬刚正则,优先用 goquery + CSS 选择器
正则匹配 HTML 是高危操作,标签嵌套、属性换行、注释都会让表达式失效。而 goquery 基于 html.Parse,能处理真实浏览器渲染前的 DOM 结构。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 用
doc.Find("a[href]").Each(...)提取链接,比strings.Contains扫全文安全得多 - 注意
Find返回的是集合,取单个元素要调Eq(0)或First(),否则Text()会拼接所有匹配项 - 属性值用
Attr("href")获取,返回(string, bool),务必检查第二个返回值,避免空值 panic - 如果目标页面大量使用 JS 渲染(如 React/Vue),
goquery拿不到最终内容,得换 Puppeteer 或直接调 API
并发控制必须做,不然容易被封 IP 或压垮自己
开 1000 个 goroutine 直接发请求,看着快,实则触发 TCP 端口耗尽、DNS 查询阻塞、目标站限流甚至本地文件描述符溢出。
实操建议:
- 用带缓冲的 channel 控制并发数(如
sem := make(chan struct{}, 10)),每次请求前sem ,结束后 - 别用
time.Sleep粗暴限频,改用rate.Limiter(golang.org/x/time/rate),它能平滑分配请求窗口 - 对同一域名做请求间隔(如 1 秒/次),可用 map 记录上次请求时间,结合
time.Since判断是否可发 - 错误响应(如 429、503)要主动退避,用指数退避重试(
time.AfterFunc+ 递增 delay)
数据落地别只写文件,要考虑结构化与扩展性
把抓到的标题、URL、时间全塞进一个 CSV 或 JSON 文件,短期可行,但字段增减、去重、增量更新、后续查重都会变麻烦。
实操建议:
- 哪怕不用数据库,也先用
struct定义数据模型(如type Article { Title string `json:"title"`; URL string `json:"url"` }),方便序列化和后期迁移 - 去重靠唯一键(如 URL 的 SHA256),存内存 map 只适用于小量数据;量大就该上 SQLite(
mattn/go-sqlite3)或 Redis - 写文件时用
os.O_APPEND | os.O_CREATE打开,别每次覆盖;日志单独走log.SetOutput到文件,和业务数据分离 - 如果目标站有分页或时间范围,把当前页码或时间戳作为状态保存下来,下次启动可续爬
真正难的从来不是“怎么拿到 HTML”,而是怎么在目标站策略变化、网络抖动、HTML 结构微调时让爬虫不崩、不漏、不重复——这些细节藏在超时设置、selector 容错、状态持久化里,而不是某一行代码里。










