Python 爬虫 requests 异常处理完全指南:超时、ConnectionError、HTTPError 怎么办
梳理 requests 异常的完整体系,按请求阶段逐一说明 URL 错误、连接失败、超时、状态码异常和重定向异常,帮你写出不容易崩的爬虫代码。
为什么你的爬虫请求总是失败?
requests 发出去的请求,一定会成功吗?
不一定。
服务器可能崩了,网络可能波动,代理可能失效,证书可能过期。 这些事情不在你代码的控制范围内。
不处理异常,程序就会在某个你没想到的时候直接崩掉。
requests 异常有哪些?一图梳理完整体系
Python 异常最顶层是 BaseException,日常能捕获到的运行时异常基本都在它的子类 Exception 下面。
requests 在 Exception 下面定义了一个 RequestException,它是所有 requests 异常的基类。
BaseException
└─ Exception
└─ RequestException # 所有 requests 异常基类
├─ HTTPError
├─ ConnectionError
│ ├─ ProxyError
│ └─ SSLError
├─ Timeout
│ ├─ ConnectTimeout
│ └─ ReadTimeout
├─ TooManyRedirects
├─ MissingSchema
├─ InvalidSchema
└─ InvalidURL
只要捕获 RequestException,requests 的全部异常都能兜住。
当然,你也可以细分捕获,不同的异常用不同的方式处理。
异常按阶段来理解更清楚
一次请求从发出到拿到结果,是分阶段的。 异常基本上就是某个阶段失败了。
URL 报错 MissingSchema/InvalidURL 怎么处理?
请求还没发出去就报错了,多半是 URL 写错了。
import requests
requests.get("example.com") # MissingSchema,缺少 http://
requests.get("ftp://example.com") # InvalidSchema,不支持 ftp
requests.get("https://example .com") # InvalidURL,格式不合法
这类问题如果 URL 是手写的,检查一遍就能解决。 如果 URL 来自上游爬虫,就要在拼接时做好校验。
连接失败 ConnectionError/ProxyError 怎么处理?
URL 没问题,进入连接阶段,可能遇到 ConnectionError。
常见场景:断网了、服务器 IP 不存在、端口没开、代理配置错、证书问题。
requests.get("https://not-exist-domain-xyz.com") # ConnectionError
两个常见子类:
ProxyError:代理连接失败SSLError:HTTPS 证书验证失败,前面讲过,可以传verify=False临时跳过
requests 超时怎么办?timeout 这样设置才正确
连上了,但服务器迟迟不响应。
这时候一定要设置 timeout,否则代码会一直卡着,永远等下去。
# 连接和读取共享 5 秒超时
requests.get(url, timeout=5)
# 分开设置:连接 3 秒,读取 10 秒
requests.get(url, timeout=(3, 10))
超时会抛 Timeout,子类是 ConnectTimeout(连接超时)和 ReadTimeout(读取超时)。
timeout 一定要带上,别省。
状态码报错 HTTPError 怎么处理?raise_for_status 用法
这种情况 requests 默认不抛异常。 状态码 404、500,你都能正常拿到 response,requests 不认为这是错误。
如果你希望状态码非 2xx 时抛异常,就调用 raise_for_status():
response = requests.get(url)
response.raise_for_status() # 非 2xx 抛 HTTPError
重定向过多 TooManyRedirects 怎么处理?
requests 默认会自动跟随重定向,比如 http 跳 https 就是这么处理的。
但如果重定向形成了死循环,超过限制(默认 30 次)就会抛 TooManyRedirects。
requests 异常处理代码怎么写?(完整示例)
不是每个异常都要单独捕获,判断标准只有一个:这个异常能不能影响我的下一步行为?
能影响就单独捕获,不知道怎么处理就交给父类统一兜底。
import requests
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
except requests.Timeout:
# 超时可能要重试,或切换代理
print("请求超时")
except requests.HTTPError:
# 状态码不对,业务层处理
print(f"HTTP 错误:{response.status_code}")
except requests.RequestException:
# 其他所有 requests 异常统一兜底
print("请求失败")
else:
# 没有异常,正常处理结果
print(response.text)
注意:RequestException 一定要写在最后,写在前面会直接拦截所有异常,子类捕获永远不会生效。
一句话记住异常处理精髓
异常按阶段理解,捕获按需细分,RequestException 最后兜底。
这套思路不只适用于 requests,写任何网络请求的代码都用得上。
常见问题
requests 超时了怎么办?
设置 timeout 参数。推荐分开设置连接超时和读取超时:requests.get(url, timeout=(3, 10)),连接超时 3 秒,读取超时 10 秒。如果没有设置 timeout,请求会无限等待直到服务器响应。
ConnectionError 和 Timeout 哪个先处理?
建议优先捕获 Timeout,因为超时更常见,而且可能是临时性网络问题,可以考虑重试。ConnectionError 通常意味着网络本身不通或者服务器不可达,重试的意义不大。但具体顺序可以根据业务需求调整。
requests 报 SSL 错误怎么解决?
如果是证书验证失败,可以传 verify=False 临时跳过验证(仅限开发环境):
requests.get(url, verify=False)
如果是证书过期或者域名不匹配,应该检查 URL 是否正确,或者更新本地的 CA 证书 bundle。生产环境不建议关闭证书验证。
为什么状态码 404 不会抛异常?
requests 默认不会对任何状态码抛异常,2xx 和 4xx/5xx 都会正常返回响应。如果希望在非 2xx 时自动抛异常,记得加上 response.raise_for_status()。
代理设置后报 ProxyError 怎么办?
先检查代理地址和端口是否写对(格式如 http://ip:port),确认代理本身是否可用。也可以先在本地终端用 curl 测试代理是否正常工作。如果代理需要认证,记得在 URL 里带上用户名和密码:http://user:pass@ip:port。
捕获所有异常用哪个?
用 requests.RequestException,它是所有 requests 异常的父类,捕获它就能兜住全部情况。注意一定要放在子类异常(Timeout、HTTPError 等)之后,否则子类永远不会被命中。
Practice