Способы обработки сетевых сбоев в библиотеке requests

Раздел: Сетевое программирование -> HTTP-запросы

Основные принципы обработки HTTP ошибок

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

Метод raise_for_status объекта Response анализирует код состояния. Если код указывает на ошибку (4xx или 5xx), вызывается исключение requests.exceptions.HTTPError. Это избавляет от ручных проверок.


import requests

response = requests.get('https://httpbin.org/status/404')
response.raise_for_status()  # вызовет HTTPError

запрос страницы python (запрос веб-страницы в python)

# Вывод:
# requests.exceptions.HTTPError: 404 Client Error: Not Found for url: ...

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

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

Решение: использовать try-except для перехвата HTTPError и анализировать response.status_code внутри блока except.

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

Иногда требуется просто проанализировать код ответа, не прерывая выполнение. Можно проверять свойство response.status_code и свойство response.ok.


import requests

response = requests.get('https://httpbin.org/status/200')
if response.ok:
    print('Запрос успешен')
else:
    print(f'Ошибка: {response.status_code}')

Http error requests python (обработка http ошибок в requests)

Запрос успешен

Make request python (выполнение http-запроса)

Проблема: ручная проверка требует повторяющегося кода; легко забыть проверить статус во всех местах. Для больших проектов рекомендуется использовать raise_for_status с обработкой исключений.

Как перехватывать разные типы сетевых ошибок?

Библиотека requests предоставляет иерархию исключений: RequestException (базовое), HTTPError, ConnectionError, Timeout, TooManyRedirects и др. Можно ловить их по отдельности.


import requests
from requests.exceptions import HTTPError, ConnectionError, Timeout

try:
    response = requests.get('https://httpbin.org/delay/5', timeout=2)
    response.raise_for_status()
except Timeout:
    print('Таймаут запроса')
except ConnectionError:
    print('Проблемы с соединением')
except HTTPError as err:
    print(f'HTTP ошибка: {err}')
except Exception as e:
    print(f'Другая ошибка: {e}')

Python urllib3 request (использование urllib3 для запросов)

Проблема: порядок обработки исключений важен – сначала более конкретные, потом общие. Иначе общее исключение может перехватить частное.

Как настроить таймауты, чтобы избежать зависания?

Параметр timeout задаёт максимальное время ожидания ответа. Если ответ не получен, выбрасывается requests.exceptions.ConnectTimeout или ReadTimeout. Рекомендуется всегда указывать timeout.


import requests

try:
    response = requests.get('https://httpbin.org/delay/10', timeout=3)
    print(response.text)
except requests.exceptions.Timeout:
    print('Запрос превысил время ожидания')

Python request files file name (отправка файла через requests)

Запрос превысил время ожидания

Python request params (параметры запроса)

Проблема: значение timeout применяется и к соединению, и к чтению. Раздельные таймауты задаются кортежем (connect_timeout, read_timeout).

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

Использование HTTPAdapter с объектом Retry из urllib3. Это позволяет повторять запрос при ошибках 5xx, таймаутах и некоторых других. Повторения с задержкой (backoff) уменьшают нагрузку.


import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount('http://', adapter)
session.mount('https://', adapter)

try:
    response = session.get('https://httpbin.org/status/500')
    response.raise_for_status()
except requests.exceptions.RetryError as e:
    print(f'Все попытки исчерпаны: {e}')
except requests.exceptions.HTTPError as e:
    print(f'HTTP ошибка после повторов: {e}')

Python request download (скачивание файла через requests)

(может быть вывод RetryError после 3 попыток)

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

Как логировать все ошибки для отладки?

Модуль logging позволяет записывать информацию об ошибках в файл или консоль. Можно настроить уровень логирования для библиотеки requests, чтобы видеть все запросы.


import requests
import logging

logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)

try:
    response = requests.get('https://httpbin.org/status/400')
    response.raise_for_status()
except requests.exceptions.HTTPError as e:
    logging.error(f'HTTP ошибка: {e}')

Проблема: слишком подробное логирование может замедлить программу и захламить вывод. Для продакшена уровень логирования обычно устанавливают WARNING или ERROR.

Как обрабатывать ошибки в сессиях с сохранением состояния?

Сессии (requests.Session) поддерживают cookies и заголовки. Обработка ошибок в сессиях ничем не отличается от отдельных запросов, но удобно централизовать логику.


import requests
from requests.exceptions import HTTPError

session = requests.Session()
session.headers.update({'User-Agent': 'MyApp/1.0'})

urls = ['https://httpbin.org/get', 'https://httpbin.org/status/403', 'https://httpbin.org/status/500']
for url in urls:
    try:
        resp = session.get(url)
        resp.raise_for_status()
        print(f'{url}: успех')
    except HTTPError as e:
        print(f'{url}: ошибка {resp.status_code} - {e}')
https://httpbin.org/get: успех
https://httpbin.org/status/403: ошибка 403 - 403 Client Error: ...
https://httpbin.org/status/500: ошибка 500 - 500 Server Error: ...

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

Расширенные примеры обработки ошибок

Пример 1: Кастомная стратегия повторных попыток с экспоненциальной задержкой

Цель: автоматически повторять запросы при временных ошибках сервера (5xx) с увеличением паузы между попытками.

Пример

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time

def retry_with_backoff():
    session = requests.Session()
    retry_strategy = Retry(
        total=5,
        backoff_factor=0.5,
        status_forcelist=[500, 502, 503, 504],
        allowed_methods=["GET"],
        raise_on_status=False  # не выбрасывать RetryError, а вернуть последний ответ
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount('https://', adapter)
    
    for attempt in range(6):  # максимум 5 повторов + первый
        try:
            response = session.get('https://httpbin.org/status/503')
            if response.ok:
                print('Успех')
                break
            else:
                wait = (2 ** attempt) * 0.5  # экспоненциальная задержка для демонстрации
                print(f'Попытка {attempt+1}: статус {response.status_code}, ждём {wait:.1f} с')
                time.sleep(wait)
        except requests.exceptions.ConnectionError as e:
            print(f'Ошибка соединения: {e}')
            time.sleep(2 ** attempt)
    else:
        print('Все попытки исчерпаны')

retry_with_backoff()
Попытка 1: статус 503, ждём 0.5 с
Попытка 2: статус 503, ждём 1.0 с
Попытка 3: статус 503, ждём 2.0 с
Попытка 4: статус 503, ждём 4.0 с
Попытка 5: статус 503, ждём 8.0 с
Попытка 6: статус 503, ждём 16.0 с
Все попытки исчерпаны

Проблема: ручная задержка не использует возможности Retry из urllib3. Лучше доверить повторение самому Retry, а здесь показан альтернативный вариант для наглядности.

Пример 2: Загрузка файла с обработкой ошибок и повторными попытками

Цель: надёжно загрузить файл, возобновляя при разрыве соединения.

Пример

import requests
from requests.exceptions import RequestException
import time

def download_file(url, local_path, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, stream=True, timeout=10)
            response.raise_for_status()
            with open(local_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f'Файл сохранён: {local_path}')
            return
        except (RequestException, IOError) as e:
            print(f'Попытка {attempt+1} не удалась: {e}')
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
            else:
                raise
    raise Exception('Не удалось скачать файл')

download_file('https://httpbin.org/bytes/1024', 'test.bin')
Файл сохранён: test.bin

Пример 3: Обработка разных кодов ошибок с разной логикой

Цель: на 401 (Unauthorized) обновить токен и повторить запрос, на 404 вернуть None, на 5xx – повторить с задержкой.

Пример

import requests
from requests.exceptions import HTTPError

def robust_get(url, token=None):
    session = requests.Session()
    if token:
        session.headers.update({'Authorization': f'Bearer {token}'})
    
    response = session.get(url)
    
    if response.status_code == 200:
        return response.json()
    elif response.status_code == 401:
        new_token = refresh_token()
        session.headers.update({'Authorization': f'Bearer {new_token}'})
        return robust_get(url, new_token)
    elif response.status_code == 404:
        print('Ресурс не найден')
        return None
    elif 500 <= response.status_code < 600:
        import time
        time.sleep(2)
        return robust_get(url, token)
    else:
        response.raise_for_status()

def refresh_token():
    return 'new_token_123'

try:
    data = robust_get('https://httpbin.org/status/404')
    print(data)
except Exception as e:
    print(f'Не удалось получить данные: {e}')
Ресурс не найден
None

Обработка HTTP ошибок в requests - comments

En
Http error requests python (python)