使用Python requests模块时的Try/except

48

我正在进行一些API测试,尝试创建一个函数,根据输入的URL返回json响应,但如果响应是HTTP错误,则返回错误消息。

以前我使用urllib2,现在尝试改用requests。然而,似乎无论出现什么错误,我的except代码块都没有执行。

testURL = 'http://httpbin.org/status/404'


def return_json(URL):
    try:
        response = requests.get(URL)
        json_obj = response.json()
        return json_obj
    except requests.exceptions.HTTPError as e:
        return "Error: " + str(e)

我从运行上述内容得到的结果是...

<Response [404]>

你认为什么是HTTP错误?虽然有状态码,但不是所有除了“200”之外的状态码都意味着出现了某种错误。正如你所注意到的,request库将它们视为HTTP响应的另一个方面,并不会引发异常。因此,对于你的用例,你需要更具体地说明什么构成了“错误”。 - Lukas Graf
谢谢Lukas,我认为对于我的情况,除了200以外的任何代码都应该被视为错误。 - mroriel
3个回答

85

如果你想让响应在非200状态码时引发异常,请使用response.raise_for_status()。那么你的代码看起来会像这样:

testURL = 'http://httpbin.org/status/404'


def return_json(URL):
    response = requests.get(testURL)

    try:
        response.raise_for_status()
    except requests.exceptions.HTTPError as e:
        # Whoops it wasn't a 200
        return "Error: " + str(e)

    # Must have been a 200 status code
    json_obj = response.json()
    return json_obj

您可以看出,这显然比这里提供的其他解决方案更简单,不需要您手动检查状态代码。您也仅需捕获一个 HTTPError, 因为这就是 raise_for_status 将引发的异常。捕捉 RequestsException 是一个不好的主意,因为这会捕捉像 ConnectionErrorTimeoutError 这样的问题,等等。这些都不意味着你要捕捉的东西。

19

注意:建议使用 response.raise_for_status(),就像Ian 的回答中所描述的那样(他是requests模块的维护者之一)。


如何处理这些问题完全取决于您认为 HTTP 错误是什么。虽然有状态码,但除了 200 以外的一切并不一定意味着有某种错误。

正如您所注意到的,请求库将它们视为 HTTP 响应的另一个方面,并且不会引发异常。例如,HTTP 状态码 302 意味着“找到了”,但响应没有包含响应正文,而是包含一个 Location 头,您需要遵循它才能到达实际想要的资源。

因此,您需要查看response.status_code,并进行处理,同时使用 try..except 来捕获实际的协议错误。在捕获这些错误时,您应该实际上捕获 requests.exceptions.RequestException,因为这是 requests 模块引发的所有其他异常的基类

下面是一个演示所有三种情况的示例:

  • 成功的200 OK响应
  • 成功的请求和响应,但状态码为 200 以外的其他值
  • 协议错误(无效模式)
import requests

test_urls = ['http://httpbin.org/user-agent',
             'http://httpbin.org/status/404',
             'http://httpbin.org/status/500',
             'httpx://invalid/url']


def return_json(url):
    try:
        response = requests.get(url)

        # Consider any status other than 2xx an error
        if not response.status_code // 100 == 2:
            return "Error: Unexpected response {}".format(response)

        json_obj = response.json()
        return json_obj
    except requests.exceptions.RequestException as e:
        # A serious problem happened, like an SSLError or InvalidURL
        return "Error: {}".format(e)


for url in test_urls:
    print "Fetching URL '{}'".format(url)
    print return_json(url)
    print

输出:

Fetching URL 'http://httpbin.org/user-agent'
{u'user-agent': u'python-requests/2.1.0 CPython/2.7.1 Darwin/11.4.2'}

Fetching URL 'http://httpbin.org/status/404'
Error: Unexpected response <Response [404]>

Fetching URL 'http://httpbin.org/status/500'
Error: Unexpected response <Response [500]>

Fetching URL 'httpx://invalid/url'
Error: No connection adapters were found for 'httpx://invalid/url'

response.json()可能会引发异常,即使您收到了一个成功的响应,但它不是JSON格式 - 因此您可能也需要考虑这种情况。


注意if not response.status_code // 100 == 2 的作用如下: // 运算符执行所谓的 向下取整除法 ,因此它将结果向下舍入到最接近的整数(这是 Python 2.x 中默认行为的 /,但在 Python 3.x 中已改变为执行浮点数除法)。 因此,对于所有 2xx 状态码,status // 100 == 2 都成立。


太棒了,谢谢Lukas。我稍微编辑了一下,以考虑所有2xx代码……如果不是str(response.status_code)[0] == 2。 - mroriel
对于非JSON成功响应的观点也很好。我会去研究一下。 - mroriel
1
@obrienmorgan int(response.status_code / 100) == 2 已经包含了 2xx 的情况。在 Python 2.x 中,除法 / 操作符会执行地板除法(向下取整到最近的整数),因此对于所有的 2xx 状态码,status / 100 == 2 都是成立的。在 Python 3.x 中,除法已经改为浮点除法,这就是为什么需要使用 int(...) 来使其在 Python 2.x 和 3.x 中都能正常工作的原因。 - Lukas Graf
啊,好的,我的经验不足显露出来了!Lukas,非常感谢你的帮助,这个完美地运作了。 - mroriel
@obrienmorgan 实际上,在Python 2.x和3.x中进行地板除法的干净方法是使用//运算符-我更新了我的答案并解释了这一点。 - Lukas Graf

1
你可以检查response.status_code的值。如果不是200,那么可以视为错误条件并抛出自己的异常。

哎呀,你说得对,@PadraicCunningham。谢谢,我现在正在修复它。 - austin
1
谢谢大家,这个可行。添加了一个if语句来检查响应。但这放弃了使用Try/Except的方法。 - mroriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接