Исключения в запросах 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
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())