Python 爬虫 requests 异常处理完全指南:超时、ConnectionError、HTTPError 怎么办

梳理 requests 异常的完整体系,按请求阶段逐一说明 URL 错误、连接失败、超时、状态码异常和重定向异常,帮你写出不容易崩的爬虫代码。

4 分钟阅读 requests异常处理RequestExceptiontimeout

为什么你的爬虫请求总是失败?

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

读完这一节,去靶场里验证一下。

去挑战广场练习