在使用 Python 进行网络编程时,requests 模块无疑是我们手中的利剑。它简单、优雅,让发起 HTTP 请求变得像呼吸一样自然。然而,网络世界充满了不确定性——服务器可能会宕机,链接可能会失效,甚至是一次简单的超时都可能导致我们的脚本崩溃。因此,掌握 requests 模块的异常处理机制,不仅仅是为了防止程序报错,更是为了构建健壮、可靠的应用程序。
在这篇文章中,我们将深入探讨如何使用 INLINECODEc725f521 和 INLINECODEa5cd6c89 块来优雅地处理请求过程中可能遇到的各种“坑”。我们将从最基础的概念开始,逐步深入到处理复杂的网络错误和超时问题。通过一系列实用的代码示例,我们将学会如何识别、捕获并响应这些异常,确保我们的程序在面对不可预测的网络环境时依然能够从容应对。
核心概念回顾
在开始“抓虫”之前,我们需要先了解一下 requests 模块中用于检查响应状态的关键属性和方法。这是判断请求是否成功的第一道防线。
-
r.status_code: 这是一个数字,它告诉我们服务器返回的状态。比如 200 表示一切正常,404 表示页面未找到,500 则通常是服务器内部出了问题。 -
r.url: 这是最终返回响应的 URL。有时候,请求可能会经过重定向,这个属性能让我们看到最终的落脚点。 - INLINECODEb6de2fa8: 这是一个非常有用的方法。如果请求成功(状态码 200),它什么也不会做;但如果请求出现了 4xx 或 5xx 错误,它会抛出一个 INLINECODE764c333f 异常。这在结合
try...except使用时特别强大。 -
r.request: 这个属性包含了我们发出的原始请求对象,方便我们在出错时回溯查看我们到底请求了什么。
成功的连接:理想情况
首先,让我们看看最理想的情况。当我们向一个稳定的服务器(比如 Google)发起请求时,一切都会很顺利。
import requests
# 发起一个简单的 GET 请求
try:
r = requests.get("https://www.google.com/")
# 检查状态码,如果是 200 则表示成功
print(f"连接成功,状态码:{r.status_code}")
except Exception as e:
print(f"发生未知错误:{e}")
在这个例子中,如果网络通畅,你将看到状态码 200。这是所有 HTTP 客户端最期待看到的数字。
处理 HTTP 错误 (HTTPError)
现实往往不那么完美。很多时候,我们会遇到 404(页面不存在)或者 500(服务器错误)。如果我们不处理这些错误,程序可能会继续运行并产生不可预知的结果,或者直接打印出一堆丑陋的 Traceback。
让我们尝试访问一个亚马逊上不存在的页面,并使用 raise_for_status() 来触发异常。
import requests
url = "https://www.amazon.com/nothing_here"
try:
# 发起请求,设置超时时间为 1 秒
r = requests.get(url, timeout=1)
# 这一行会检查状态码,如果是 4xx 或 5xx,则抛出 HTTPError
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
print("捕获到 HTTP 错误:")
# 打印详细的错误信息,比如 404 Not Found
print(f"错误详情:{errh.args[0]}")
except Exception as e:
print(f"其他错误:{e}")
print("
程序继续运行...")
代码解析:
在这个例子中,当 INLINECODE104a23da 发现状态码是 404 时,它会立即停止 INLINECODE77c6240f 块的执行,并跳转到 except requests.exceptions.HTTPError 块。这样,我们就可以优雅地记录错误,或者给用户展示一个友好的提示,而不是让程序崩溃。
通用异常处理
在 INLINECODE5ab053e1 库中,有一个基类异常叫做 INLINECODEf554346a。几乎所有 requests 引发的异常都继承自它。这意味着,如果你暂时不想区分具体的错误类型(是连接超时?还是 DNS 解析失败?),你可以直接捕获这个通用异常。这在编写脚本快速原型时非常有用。
url = "https://www.google.com/"
try:
r = requests.get(url, timeout=1)
r.raise_for_status()
print("请求成功!")
except requests.exceptions.RequestException as errex:
# 这里会捕获几乎所有与 requests 相关的错误
print("Oops, 请求过程中发生了一个异常:")
print(errex)
深入理解超时
在网络请求中,超时 是一个必须重视的话题。想象一下,你的程序在等待一个永远都不会到来的响应。如果没有设置超时,你的程序可能会永远卡在那里。
INLINECODE7649cfc3 允许我们通过 INLINECODE1648c193 参数来设定等待时间。我们可以捕获 ReadTimeout 异常来处理服务器响应太慢的情况。
import requests
url = "https://www.google.com/"
try:
# 设置超时为 1 秒
# 注意:即使是 Google,在某些网络环境下 1 秒也可能不够,这取决于你的网络速度
r = requests.get(url, timeout=1)
r.raise_for_status()
except requests.exceptions.ReadTimeout as errrt:
print("读取超时:服务器响应太慢了。")
except requests.exceptions.RequestException as errex:
print("其他请求错误:")
print(errex)
else:
print(f"请求完成,状态码:{r.status_code}")
实战建议:
在生产环境中,我们通常会将 timeout 设置为一个稍大一点的值(比如 3 到 10 秒),或者将其作为配置文件中的一个参数,方便随时调整。如果我们把上面的代码 timeout=0.0001,几乎肯定会触发超时异常,因为没有人能在那微秒级的时间内完成 TCP 握手和数据传输。
捕获 URL 格式错误
有时候,错误是由于我们自己手滑造成的。比如,忘记在 URL 前面加上 INLINECODEfe76e765 或 INLINECODEafa6edf5 协议头。
url = "www.google.com" # 这是一个错误的 URL,缺少协议头
try:
r = requests.get(url, timeout=1)
r.raise_for_status()
except requests.exceptions.MissingSchema as errmiss:
print("URL 格式错误:")
print("请确保 URL 以 http:// 或 https:// 开头。")
except requests.exceptions.ReadTimeout as errrt:
print("请求超时")
这种错误通常发生在配置文件读取 URL 或者用户输入 URL 的场景下。提前捕获 MissingSchema 可以给用户非常明确的反馈。
处理连接错误
还有一种情况是物理层面的连接问题。比如,我们要访问的域名根本不存在(DNS 解析失败),或者服务器拒绝了连接,或者是你的网络断了。这时候会抛出 ConnectionError。
# 假设我们要访问一个根本不存在的本地地址,或者被防火墙拦截的地址
url = "http://thisdefinitelydoesnotexist12345.com"
try:
r = requests.get(url, timeout=5)
r.raise_for_status()
except requests.exceptions.HTTPError as errh:
print("HTTP 错误")
except requests.exceptions.ConnectionError as conerr:
print("连接错误:")
print("无法连接到服务器。请检查您的网络连接,或确认域名是否正确。")
except requests.exceptions.RequestException as errex:
print("其他异常")
整合:构建健壮的网络请求客户端
现在,让我们把学到的知识整合起来。在实际开发中,我们通常会有一个封装好的请求函数,它能够处理各种常见的异常。
下面的代码展示了一个完整的异常处理流程,遵循了“从具体到一般”的原则。我们先捕获具体的异常(如超时、HTTP错误),最后捕获通用的异常。这样做的好处是,我们可以针对不同的错误采取不同的恢复策略。
import requests
def make_robust_request(url):
print(f"正在尝试访问:{url}")
try:
# verify=True 启用 SSL 证书验证(默认行为,这里是显式声明)
# timeout=1 设置连接和读取的超时时间
r = requests.get(url, timeout=5, verify=True)
# 如果状态码不是 200,这里会抛出 HTTPError
r.raise_for_status()
# 1. 处理 HTTP 协议层面的错误 (404, 500 等)
except requests.exceptions.HTTPError as errh:
print(" -> [HTTP 错误]")
print(f" 状态码:{errh.response.status_code}")
print(f" 详情:{errh.args[0]}")
return None
# 2. 处理连接超时或读取超时
except requests.exceptions.Timeout as errt:
print(" -> [超时错误]")
print(" 服务器响应时间过长,连接已断开。")
return None
# 3. 处理连接错误 (DNS 失败, 连接被拒绝等)
except requests.exceptions.ConnectionError as conerr:
print(" -> [连接错误]")
print(" 网络似乎有问题,或者服务器不存在。")
return None
# 4. 处理 requests 库抛出的所有其他异常
except requests.exceptions.RequestException as errex:
print(" -> [严重异常]")
print(f" 意外的错误:{errex}")
return None
# 5. 如果一切顺利
else:
print(" -> [成功]")
print(f" 返回数据大小:{len(r.content)} 字节")
return r
# --- 测试我们的函数 ---
# 测试用例 1: 一个正常的网站
print("
测试用例 1 (正常):")
make_robust_request("https://www.google.com/")
# 测试用例 2: 一个不存在的页面 (404)
print("
测试用例 2 (404):")
make_robust_request("https://www.google.com/asdfasdf")
# 测试用例 3: 一个无效的 URL
print("
测试用例 3 (无效 URL):")
make_robust_request("google.com")
最佳实践与性能优化建议
在结束这篇文章之前,我想分享几点在实际开发中非常有用的经验:
- 始终设置
timeout:这是最重要的一条。如果不设置 timeout,你的线程可能会无限期地挂起。根据你的业务需求,通常建议将 timeout 设置在 3 到 10 秒之间。
- 使用会话对象:如果你需要向同一个主机发起多个请求(比如爬取一个网站的多个页面),使用
requests.Session()。它会复用底层的 TCP 连接,这被称为 HTTP Keep-Alive。这能显著提高性能并减少网络延迟。
- 重试机制:网络是不稳定的。有时候,一个请求仅仅是因为瞬间的网络抖动就失败了。在生产环境中,不要直接放弃,而是实现一个重试机制(比如使用
retrying库或者编写自己的循环逻辑),特别是在遇到 5xx 服务器错误或连接超时的时候。
- 关闭连接:当你使用了 Session 对象时,记得在任务完成后使用 INLINECODE913c4eec,或者使用 INLINECODEfd60f91d 上下文管理器(
with requests.Session() as session:)来自动管理资源的释放。
总结
处理异常并不是为了掩盖错误,而是为了让我们有机会去修正错误或者优雅地通知用户。通过合理运用 requests.exceptions 下的各种异常类,我们可以将脆弱的脚本转化为稳健的网络服务。
现在,你已经掌握了如何应对网络编程中最常见的挑战。试着把你现有的项目拿出来检查一下,看看那些裸露的 requests.get() 是不是也该穿上“盔甲”了呢?