当前位置:首页 > 文章列表 > Golang > Go问答 > Go Colly OnError 中如何区分传输失败与 HTTP 错误

Go Colly OnError 中如何区分传输失败与 HTTP 错误

来源:17golang原创 2026-10-13 15:54:40 0浏览 收藏

我在用 Colly 抓取列表页时,最容易误判的一类日志是“请求失败”:有时是真正的 DNS、连接或超时问题,有时只是服务端返回了 404、429 或 500。两者都可能进入 OnError,但处理方式完全不同。实用的判断顺序是:先看 Response 是否存在,再看 StatusCode 是否代表 HTTP 状态异常,最后用 errors.As 和 errors.Is 判断传输错误的具体原因。

官方地址:https://github.com/gocolly/colly

不要用“err != nil”直接等同于网络断线。对于 HTTP 错误,Colly 通常仍能把状态码放在 Response 中;对于传输失败,响应可能为空。默认配置下,非 2xx 会触发 OnError,如果启用 ParseHTTPErrorResponse,则可以把非 2xx 当作可解析响应继续走响应处理。
碰到这个场景你直接记这几个判断点就行
  • Response != nil 且 StatusCode >= 300,优先按 HTTP 错误处理。
  • Response == nil 或没有有效状态码时,再检查 *url.Error、超时和取消原因。
  • 不要把 4xx、5xx 无条件重试;先区分客户端请求、限流、服务端暂时故障和永久不存在。

先看 OnError 的两个参数

Colly 的错误回调类型是 func(*Response, error)。第一个参数不是“请求一定成功”的证明,而是 Colly 在能够拿到 HTTP 响应或已经建立部分响应上下文时提供的对象;第二个参数是当前阶段的错误。官方源码在错误处理时会把缺失的响应补成带有 Request 和 Context 的对象,但这不等于它拥有真实的状态码和响应体。

Colly OnError 中 Collector、Request、Response、error 以及传输失败和 HTTP 状态异常之间的静态关系说明图
图1:OnError 两个回调参数及错误来源的静态说明图,不是运行截图。

因此,日志代码不要只打印 err.Error()。先把 URL、方法、状态码和错误类型放到同一条记录里,后面才能回答“服务器拒绝了请求”还是“请求根本没有拿到响应”。

c := colly.NewCollector()

c.OnError(func(resp *colly.Response, err error) {
	// Response 可能没有真实的 HTTP 状态,默认值不能被当成成功响应。
	status := 0
	requestURL := ""
	if resp != nil {
		status = resp.StatusCode
		if resp.Request != nil && resp.Request.URL != nil {
			requestURL = resp.Request.URL.String()
		}
	}

	// 先记录原始 error,再按状态码决定它属于哪一类。
	log.Printf("colly error url=%s status=%d err=%v", requestURL, status, err)
})

这里的 status == 0 只是“当前没有可用 HTTP 状态码”的日志标记,不是 Colly 定义的状态码。它可能对应连接失败,也可能对应请求检查、重定向或回调阶段的其他错误,仍要结合 error 判断。

用状态码和错误包装区分来源

我更愿意把分类拆成两层:第一层只回答“有没有 HTTP 响应”,第二层才回答“没有响应时,传输失败的具体原因是什么”。这样不会因为某个库把底层错误包了一层,就误把所有情况归到同一类。

func classifyCollyError(resp *colly.Response, err error) string {
	// 只要拿到明确的非 2xx 状态,就先按 HTTP 错误归类。
	if resp != nil && resp.StatusCode >= 300 {
		return "http-error"
	}

	// url.Error 常见于 HTTP 客户端包装的请求失败,保留它便于日志展开。
	var urlErr *url.Error
	if errors.As(err, &urlErr) {
		if errors.Is(urlErr.Err, context.DeadlineExceeded) {
			return "transport-timeout"
		}
		return "transport-error"
	}

	// 未匹配到包装类型时仍保留一个通用类别,避免丢失未知错误。
	if errors.Is(err, context.DeadlineExceeded) {
		return "transport-timeout"
	}
	return "other-error"
}

这段判断的重点不是类别名称,而是顺序:HTTP 状态码优先于错误字符串;底层原因优先用 errors.Is 和 errors.As,不要依赖“timeout”“connection refused”这类可能变化的文本。对于 Response == nil 的情况,url.Error 往往能补充请求方法和目标地址。

还要留意一个容易忽略的边界:OnError 并不只可能接收网络阶段的错误。HTML/XML 处理回调返回错误时,Colly 也会调用错误处理路径;此时 Response 可能存在且状态码是 2xx。若业务要专门统计传输失败,应把“状态码分类”和“解析/业务回调错误”分开计数。

非 2xx 要不要继续读取响应体

默认情况下,Colly 只按成功响应继续处理;官方配置中的 ParseHTTPErrorResponse 用来允许解析非 2xx 响应。两种策略都合理,取决于目标站点把错误体当不当作业务数据。

// 需要读取 404/429/500 响应体时,显式开启非 2xx 解析。
c := colly.NewCollector(colly.ParseHTTPErrorResponse())

c.OnResponse(func(resp *colly.Response) {
	// 这里可以读取状态码和错误页返回的结构化内容。
	log.Printf("received status=%d url=%s", resp.StatusCode, resp.Request.URL)
})

c.OnError(func(resp *colly.Response, err error) {
	// 开启该选项后,单纯的 HTTP 状态异常不再自动走这里。
	log.Printf("request failed status=%d err=%v", resp.StatusCode, err)
})

开启后不要误解为“所有非 2xx 都成功了”。它只是改变 Colly 的回调路径,让响应可以进入 OnResponse、HTML 或 XML 处理;业务层仍然要检查 StatusCode。例如,429 需要保留限流信息并延迟,404 可能应该记录为资源不存在,500 才可能进入有限次数的重试。

Colly OnError 按状态码、url.Error、超时原因连接到重试和结构化日志的静态分类说明图
图2:错误分类、可恢复动作与诊断记录之间的静态关系说明图,不是监控截图。

把重试和日志决策放在分类之后

在我的抓取任务里,重试是分类之后的动作,而不是 OnError 一进来就调用 Retry。网络超时、临时连接失败可以有条件重试;参数错误、禁止访问、明显的 404 不应靠重试解决。HTTP 429 是否重试,则要结合响应头和站点约定,不能只看数字。

c.OnError(func(resp *colly.Response, err error) {
	category := classifyCollyError(resp, err)

	// 记录状态码和分类,便于把一次重试与原始请求关联起来。
	log.Printf("fetch category=%s status=%d err=%v", category, statusCode(resp), err)

	// 只对明确的传输类暂时故障重试,避免 404 或参数错误形成重试风暴。
	if category == "transport-timeout" || category == "transport-error" {
		if resp != nil && resp.Request != nil {
			_ = resp.Request.Retry()
		}
	}
})

func statusCode(resp *colly.Response) int {
	// 用 0 表示没有可用的真实 HTTP 状态码。
	if resp == nil {
		return 0
	}
	return resp.StatusCode
}

示例只展示决策位置,没有把重试次数、退避和幂等性藏起来。正式任务还应在 Context 或外部计数器中限制次数,并记录第一次失败的原因;如果站点对同一 URL 的重试有频率限制,还要把退避时间和域名级并发一起纳入设计。

异步模式和回调错误别混在一起

启用 colly.Async() 后,Visit 返回成功更多地表示任务已经提交,不能用它判断远端请求最终成功。要等 c.Wait() 让异步请求完成,并把最终结果交给 OnResponse、OnError 和 OnScraped 等回调。

c := colly.NewCollector(colly.Async())

c.OnError(func(resp *colly.Response, err error) {
	// 异步场景的最终错误在回调中处理,不依赖 Visit 的即时返回值。
	log.Printf("async error status=%d err=%v", statusCode(resp), err)
})

if err := c.Visit(targetURL); err != nil {
	// 这里主要处理任务提交阶段的错误,例如 URL 或访问规则不合法。
	log.Printf("submit visit failed: %v", err)
}

// 等待已经提交的网络任务完成,再结束本轮采集。
c.Wait()

最后可以用下面的速查表收束判断:

观察到的信号优先归类处理建议
Response 存在且状态码为 404/429/500HTTP 错误保留状态码;按业务决定记录、限流等待或有限重试
Response 为空,error 能展开为 *url.Error传输错误继续检查超时、取消、连接拒绝等原因
状态码为 2xx,但解析回调报错处理阶段错误单独统计解析问题,不算成网络断线
启用 ParseHTTPErrorResponse 后收到非 2xx可解析的 HTTP 响应在响应回调里检查状态码,不依赖 OnError

常见问题

OnError 的 Response 为 nil 就一定是网络错误吗?

不能绝对这样说。它只能说明当前没有可用的 HTTP 响应对象,仍需结合错误类型判断请求检查、取消或其他阶段错误。

为什么返回 404 时没有进入 OnResponse?

默认配置只按成功响应继续处理,非 2xx 会在错误处理路径中回调。若确实需要读取错误响应体,可以使用 colly.ParseHTTPErrorResponse(),但仍要在业务层检查状态码。

HTTP 500 是否应该自动 Retry?

不应该无条件自动重试。先确认接口是否幂等、服务端是否暂时故障、站点是否有频率限制,再设置次数和退避;4xx 通常更需要修正请求,而不是重复发送。

异步模式下如何知道所有请求结束?

调用 c.Wait() 等待已提交的异步任务,并在 OnError、OnResponse 等回调中收集最终结果。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go Colly 同一个地址第二次 Visit 为什么被拒绝Go Colly 同一个地址第二次 Visit 为什么被拒绝
上一篇
Go Colly 同一个地址第二次 Visit 为什么被拒绝
Go GJSON 怎么只提取 JSON 数组中的指定字段
下一篇
Go GJSON 怎么只提取 JSON 数组中的指定字段
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    460次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    521次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    539次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    488次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    307次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码