Исключения в запросах Python requests: как правильно их обрабатывать

Раздел: Python -> Requests

Основные исключения библиотеки requests и их обработка

При выполнении HTTP-запросов через библиотеку requests могут возникать различные сбои: проблемы с сетью, таймауты, неверные URL или ошибки сервера. Библиотека предоставляет собственное дерево исключений, унаследованное от requests.exceptions.RequestException. Грамотная обработка этих исключений позволяет сделать код устойчивым и предсказуемым.

Универсальный способ: перехват базового исключения RequestException

Как обеспечить базовую защиту от любых ошибок при выполнении запроса?

Самый простой и часто используемый подход - обернуть вызов requests.get() (или другого метода) в блок try/except и перехватывать исключение requests.exceptions.RequestException. Оно является родительским для всех остальных исключений библиотеки, поэтому ловит любую ошибку, связанную с запросом.

import requests
from requests.exceptions import RequestException

try:
    response = requests.get('https://httpbin.org/delay/5', timeout=3)
    response.raise_for_status()  # проверит статус-код
except RequestException as e:
    print(f'Ошибка при запросе: {e}')

Python 2 requests (библиотека requests в python 2)

В этом примере перехватываются и сетевые сбои (например, таймаут), и ошибки HTTP (если статус-код 4xx или 5xx).

Проблема: слишком общий перехват может скрыть детали ошибки. Например, невозможно отличить ошибку соединения от ошибки авторизации.

Решение: если требуется разная реакция на разные типы сбоев, следует перехватывать конкретные подклассы исключений (см. варианты ниже).

Варианты обработки специфических исключений

Как перехватить ошибку соединения (DNS-ошибка, отказ в соединении)?

Исключение requests.exceptions.ConnectionError возникает, когда не удалось соединиться с сервером: неправильный домен, отсутствие сети, порт закрыт.

from requests.exceptions import ConnectionError

try:
    r = requests.get('https://nonexistent.domain.example', timeout=5)
except ConnectionError:
    print('Не удалось установить соединение. Проверьте URL или сеть.')

Python 3 requests (библиотека requests в python 3)

Ошибка: иногда ConnectionError может быть вызвана временной недоступностью сервера. Логично повторить запрос с задержкой.

Как обработать таймаут ожидания ответа?

Исключение requests.exceptions.Timeout (подкласс ConnectionError) возникает, если запрос не уложился в заданные лимиты по времени (параметры timeout).

from requests.exceptions import Timeout

try:
    r = requests.get('https://httpbin.org/delay/10', timeout=2)
except Timeout:
    print('Сервер не ответил в течение 2 секунд.')

Python requests url (выполнение запроса по url с помощью requests в python)

Примечание: можно отдельно задавать таймаут на соединение (connect) и на чтение (read), передав кортеж timeout=(2, 5).

Частая ошибка: не указывать timeout вообще. Запрос может зависнуть на неопределённое время. Рекомендуется всегда задавать таймаут.

Как перехватить ошибку HTTP (статус-код 4xx или 5xx)?

Исключение requests.exceptions.HTTPError обычно возникает после вызова raise_for_status(), если ответ содержит код ошибки. Без raise_for_status() запрос не вызовет исключения при плохом статусе.

from requests.exceptions import HTTPError

try:
    r = requests.get('https://httpbin.org/status/404')
    r.raise_for_status()
except HTTPError as e:
    print(f'HTTP-ошибка: {e.response.status_code} - {e.response.reason}')

Python requests headers (заголовки запросов в python requests)

Если нужно обработать только некоторые статусы, можно проверить e.response.status_code.

Проблема: raise_for_status() не различает временные (5xx) и постоянные (4xx) ошибки. Иногда нужно разное поведение.

Решение: анализировать код ответа вручную после перехвата или перед вызовом raise_for_status().

Как обработать слишком много перенаправлений?

Исключение requests.exceptions.TooManyRedirects возникает, когда цепочка редиректов превышает лимит (max_redirects по умолчанию 30).

from requests.exceptions import TooManyRedirects

try:
    r = requests.get('https://httpbin.org/redirect/50', allow_redirects=True)
except TooManyRedirects:
    print('Слишком много перенаправлений. Возможно, циклический редирект.')

Python requests exceptions (исключения в python requests)

Можно изменить лимит через параметр max_redirects в сессии.

Как распознать некорректный URL (пропущенная схема, неверный синтаксис)?

Исключения URLRequired, InvalidURL, MissingSchema, InvalidSchema помогают точно определить, что не так с URL.

from requests.exceptions import MissingSchema, InvalidSchema

try:
    r = requests.get('example.com')  # нет схемы
except MissingSchema:
    print('В URL отсутствует схема (http:// или https://).')

try:
    r = requests.get('ftp://example.com')  # неподдерживаемая схема
except InvalidSchema:
    print('Указанная схема не поддерживается requests.')

Python requests codes (коды ответов http в python requests)

Ошибка: InvalidURL часто возникает при недопустимых символах в URL. Рекомендуется использовать urllib.parse.quote() для экранирования параметров.

Что делать с ошибками при потоковой загрузке или декодировании содержимого?

Исключения StreamConsumedError, ChunkedEncodingError, ContentDecodingError возникают при работе с потоковыми данными (stream=True) или при неверно закодированном ответе.

from requests.exceptions import ChunkedEncodingError

try:
    r = requests.get('https://httpbin.org/stream-bytes/1000', stream=True)
    for chunk in r.iter_content(chunk_size=128):
        # обработка chunks
        pass
except ChunkedEncodingError:
    print('Ошибка фрагментированного кодирования. Сервер прервал поток.')

Python module requests (модуль requests в python)

Как отловить ошибки повторных попыток (retry) в адаптере?

Если используется HTTPAdapter с политикой повторных попыток, исключение RetryError выбрасывается, когда все попытки исчерпаны.

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from requests.exceptions import RetryError

session = requests.Session()
retry = Retry(total=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)

try:
    r = session.get('https://httpbin.org/status/500')
except RetryError as e:
    print(f'Исчерпаны все попытки: {e}')

Подводя итог, для надёжного кода стоит комбинировать общий перехват RequestException с обработкой специфических исключений, если логика приложения требует разной реакции на разные сбои.

- Requests python скачать (скачивание файлов с помощью requests в python)
- Python types requests (типы запросов в requests python)
- Python requests post (отправка post-запроса с помощью requests python)

Расширенные примеры обработки исключений в requests

1. Комбинированный перехват нескольких типов исключений

Как различать таймаут, ошибку соединения и HTTP-ошибку в одном запросе?

Пример
import requests
from requests.exceptions import Timeout, ConnectionError, HTTPError

try:
    r = requests.get('https://httpbin.org/delay/1', timeout=3)
    r.raise_for_status()
except Timeout:
    print('Таймаут: сервер не ответил вовремя.')
except ConnectionError:
    print('Ошибка соединения: недоступен хост.')
except HTTPError as e:
    print(f'HTTP-ошибка: {e.response.status_code}')
except requests.exceptions.RequestException as e:
    print(f'Другая ошибка: {e}')
Таймаут: сервер не ответил вовремя.

Порядок блоков except важен: сначала идут более конкретные исключения, затем базовое.

2. Повторные попытки с задержкой и обработкой исключений

Как реализовать автоматический повтор запроса при временных сбоях?

Пример
import requests
from requests.exceptions import ConnectionError, Timeout
import time

def request_with_retry(url, max_retries=3, delay=2):
    for attempt in range(1, max_retries + 1):
        try:
            r = requests.get(url, timeout=5)
            r.raise_for_status()
            return r
        except (ConnectionError, Timeout) as e:
            print(f'Попытка {attempt} не удалась: {e}')
            if attempt < max_retries:
                time.sleep(delay * attempt)  # увеличиваем задержку
            else:
                raise
    return None

try:
    response = request_with_retry('https://httpbin.org/status/503', max_retries=2)
    print('Успешно!', response.status_code)
except Exception as e:
    print(f'Запрос не удался после всех попыток: {e}')
Попытка 1 не удалась: 503 Server Error: SERVICE_UNAVAILABLE
Попытка 2 не удалась: 503 Server Error: SERVICE_UNAVAILABLE
Запрос не удался после всех попыток: 503 Server Error: SERVICE_UNAVAILABLE

Обратите внимание: raise_for_status() выбрасывает HTTPError, который не входит в перехватываемые исключения, поэтому при 503 выполняется except (ConnectionError, Timeout)? Нет, HTTPError не является подклассом ConnectionError или Timeout, поэтому он не будет перехвачен. В примере выше он упадёт. Правильная логика: если нужно повторять на 503, то нужно перехватывать HTTPError и проверять статус. Исправим:

Пример
import requests
from requests.exceptions import ConnectionError, Timeout, HTTPError

def request_with_retry_fixed(url, max_retries=3, delay=2):
    for attempt in range(1, max_retries + 1):
        try:
            r = requests.get(url, timeout=5)
            r.raise_for_status()
            return r
        except (ConnectionError, Timeout) as e:
            print(f'Сетевой сбой (попытка {attempt}): {e}')
        except HTTPError as e:
            if e.response.status_code in (503, 502, 504):  # временные ошибки сервера
                print(f'Временная ошибка сервера (попытка {attempt}): {e.response.status_code}')
            else:
                raise
        if attempt < max_retries:
            time.sleep(delay * attempt)
    raise Exception('Запрос не выполнен после всех попыток')

3. Логирование исключений без прерывания программы

Как записать ошибку в лог и продолжить выполнение?

Пример
import logging
import requests
from requests.exceptions import RequestException

logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')

urls = ['https://httpbin.org/status/200', 'https://nonexistent.domain.test', 'https://httpbin.org/get']

for url in urls:
    try:
        r = requests.get(url, timeout=5)
        r.raise_for_status()
        print(f'{url}: успех (код {r.status_code})')
    except RequestException as e:
        logging.error(f'Ошибка при запросе {url}: {e}')
2025-04-08 12:00:00,000 - ERROR - Ошибка при запросе https://nonexistent.domain.test: ...
https://httpbin.org/status/200: успех (код 200)
https://httpbin.org/get: успех (код 200)

4. Использование контекстного менеджера для автоматического закрытия сессии

Как гарантировать закрытие сессии даже при исключении?

Пример
import requests
from requests.exceptions import RequestException

url = 'https://httpbin.org/delay/2'

try:
    with requests.Session() as session:
        resp = session.get(url, timeout=1)
except RequestException as e:
    print(f'Сессия автоматически закрыта после ошибки: {e}')

Выход из блока with гарантирует вызов session.close(), даже если возникло исключение.

5. Создание собственного класса исключений для обёртки ошибок requests

Как унифицировать ошибки в своём приложении?

Пример
import requests
from requests.exceptions import RequestException

class AppRequestError(Exception):
    def __init__(self, original_exception, url):
        self.original = original_exception
        self.url = url
        super().__init__(f'Ошибка при запросе к {url}: {original_exception}')

try:
    r = requests.get('https://httpbin.org/status/500')
    r.raise_for_status()
except RequestException as e:
    raise AppRequestError(e, 'https://httpbin.org/status/500') from e

При этом сохраняется цепочка исключений (с помощью from e), что помогает при отладке.

6. Обработка исключений при потоковой загрузке большого файла

Как безопасно загружать файл порциями и реагировать на обрыв соединения?

Пример
import requests
from requests.exceptions import ChunkedEncodingError, ConnectionError

url = 'https://httpbin.org/stream-bytes/2048'
local_filename = '/tmp/test.bin'

try:
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=256):
                if chunk:  # фильтровать keep-alive новые блоки
                    f.write(chunk)
except (ChunkedEncodingError, ConnectionError) as e:
    print(f'Поток прерван: {e}')
    # можно удалить частично скачанный файл
    import os
    if os.path.exists(local_filename):
        os.remove(local_filename)

7. Извлечение деталей исключения: ответ, запрос, статус

Как получить объект ответа или запроса из исключения?

Пример
import requests
from requests.exceptions import HTTPError

try:
    r = requests.get('https://httpbin.org/status/401')
    r.raise_for_status()
except HTTPError as e:
    # доступны атрибуты response и request
    print(f'Статус: {e.response.status_code}')
    print(f'Заголовки ответа: {e.response.headers}')
    print(f'Метод запроса: {e.request.method}')
    print(f'URL запроса: {e.request.url}')
Статус: 401
Заголовки ответа: ...
Метод запроса: GET
URL запроса: https://httpbin.org/status/401

8. Обработка исключений при использовании прокси и аутентификации

Как отличить ошибку аутентификации от сетевой проблемы с прокси?

Пример
import requests
from requests.exceptions import ProxyError, SSLError, ConnectionError

proxies = {
    'http': 'http://user:pass@proxy.example:8080',
    'https': 'http://user:pass@proxy.example:8080'
}

try:
    r = requests.get('https://api.github.com', proxies=proxies, timeout=10)
    r.raise_for_status()
except ProxyError:
    print('Ошибка прокси: неверный адрес или учётные данные.')
except SSLError:
    print('Ошибка SSL: проблема с сертификатом.')
except ConnectionError:
    print('Соединение не установлено (возможно, прокси недоступен).')

9. Поведение при отсутствии таймаута и его перехват

Что произойдёт, если не указать timeout и сервер завис?

Пример
import requests
from requests.exceptions import Timeout

# Опасно: нет таймаута
try:
    r = requests.get('https://httpbin.org/delay/100')  # зависнет на 100 секунд
    print(r.status_code)
except Timeout:
    print('Таймаут (никогда не сработает, если timeout не задан).')
except requests.exceptions.ConnectionError:
    print('Соединение разорвано (если сервер закрыл соединение).')

Без параметра timeout запрос может ожидать ответа неограниченно долго. Исключение Timeout не возникнет. Рекомендуется всегда задавать timeout.

10. Обработка исключений в асинхронных запросах (aiohttp как альтернатива)

Как работать с ошибками в асинхронном коде (если заменить requests на aiohttp)?

Хотя это выходит за рамки requests, для полноты: в aiohttp исключения аналогичны, но обрабатываются в асинхронных функциях:

Пример
import aiohttp
import asyncio

async def fetch(session, url):
    try:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
            return await resp.text()
    except aiohttp.ClientConnectorError as e:
        print(f'Ошибка соединения: {e}')
    except asyncio.TimeoutError:
        print('Таймаут')

async def main():
    async with aiohttp.ClientSession() as session:
        await fetch(session, 'https://httpbin.org/get')

asyncio.run(main())

Исключения в Python requests - comments

En
Python requests exceptions (python)