Способы обработки сетевых сбоев в библиотеке requests
Основные принципы обработки 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