Попробуйте/за исключением случаев использования модуля запросов Python

Выполнение некоторого тестирования API и попытка создать функцию, которая с учетом введенного URL-адреса возвращает ответ json, однако, если ответом является ошибка HTTP, будет возвращено сообщение об ошибке.

Раньше я использовал urllib2, но теперь вместо этого пытаюсь использовать запросы. Однако похоже, что мой блок исключений никогда не выполняется, независимо от ошибки.

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]>

person mroriel    schedule 01.07.2014    source источник
comment
Что именно вы считаете ошибкой HTTP? Есть коды состояния, но не все, кроме 200, обязательно означает наличие какой-то ошибки. Как вы заметили, библиотека request рассматривает их как еще один аспект HTTP-ответа и не вызывает исключения. Поэтому вам нужно быть немного более конкретным в отношении того, что представляет собой ошибку для вашего варианта использования.   -  person Lukas Graf    schedule 02.07.2014
comment
Спасибо Лукас, я думаю, что в моем случае все, кроме 200, следует считать ошибкой.   -  person mroriel    schedule 02.07.2014


Ответы (3)


Если вы хотите, чтобы ответ вызывал исключение для кода состояния, отличного от 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 — плохая идея. Это будет ловить такие вещи, как ConnectionErrors или TimeoutErrors и т. д. Ни одно из них не означает то же самое, что вы пытаетесь поймать.

person Ian Stapleton Cordasco    schedule 02.07.2014

Примечание. Лучше использовать response.raise_for_status(), как описано в ответе Яна выше (он один из сопровождающих модуля requests).


То, как вы справляетесь со всем этим, зависит от того, что вы считаете ошибкой HTTP. Есть коды состояния, но не все, кроме 200, обязательно означает наличие какой-то ошибки.

Как вы заметили, библиотека запросов рассматривает их как еще один аспект HTTP-ответа и не вызывает исключения. Статус HTTP 302, например, означает Found, но ответ содержит не тело ответа, а заголовок 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, где / было изменено для работы с плавающей запятой). разделение). Таким образом, status // 100 == 2 верно для всех кодов 2xx.

person Lukas Graf    schedule 01.07.2014
comment
Это потрясающе, спасибо, Лукас. Я немного отредактировал его, чтобы учесть все коды 2xx... если не str(response.status_code)[0] == 2 - person mroriel; 02.07.2014
comment
Хороший момент также об успешном ответе, отличном от JSON. Я посмотрю на это. - person mroriel; 02.07.2014
comment
@obrienmorgan int(response.status_code / 100) == 2 уже составляет 2xx ;-) В Python 2.x оператор деления / выполняет деление на пол (округляет до следующего целого числа), поэтому status / 100 == 2 верно для всех кодов 2xx. В Python 3.x деление было изменено на деление с плавающей запятой, поэтому int(...) необходимо, чтобы оно работало как для Python 2.x, так и для 3.x. - person Lukas Graf; 02.07.2014
comment
Ааа ладно, моя неопытность просвечивается! Огромное спасибо за вашу помощь, Лукас, все работает отлично. - person mroriel; 02.07.2014
comment
@obrienmorgan на самом деле чистый способ разделить пол в Python 2.x, а также 3.x - это использовать оператор // - обновил мой ответ и объяснил этот бит. - person Lukas Graf; 02.07.2014

Вы можете проверить значение response.status_code. Если это не 200, вы можете считать это состоянием ошибки и создать собственное исключение.

person austin    schedule 01.07.2014
comment
Упс. Вы правы, @PadraicCunningham. Спасибо, сейчас исправлю. - person austin; 02.07.2014
comment
Спасибо, ребята, это работает. Добавлен оператор if для проверки ответа. Но это отказывается от использования Try/Except. - person mroriel; 02.07.2014