Ошибки HTTP-клиента в Python: причины, диагностика, исправление
Обработка ошибок HTTP-клиента в Python
При работе с HTTP-запросами на Python клиентские ошибки (коды 4xx) могут возникать по разным причинам: неверный URL, отсутствие авторизации, превышение лимитов и др. Рассмотрим основные подходы к их перехвату и обработке.
Как перехватывать любую клиентскую ошибку и получать детали?
Самый надёжный способ - использовать встроенный метод raise_for_status() из библиотеки requests. Он генерирует исключение requests.exceptions.HTTPError при любом коде 4xx или 5xx. Далее можно анализировать объект ответа.
import requests
try:
response = requests.get('https://httpbin.org/status/404')
response.raise_for_status() # выбросит исключение
except requests.exceptions.HTTPError as err:
print(f'Ошибка HTTP: {err}')
print(f'Код статуса: {response.status_code}')
print(f'Тело ответа: {response.text}')Client error python (ошибка http-клиента в python)
Ошибка HTTP: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404 Код статуса: 404 Тело ответа: ...
No installed python found (python не найден в системе)
Типичная ошибка:
- Если не вызвать
raise_for_status(), программа продолжит выполнение, считая ответ успешным. - Исключение не даёт доступа к телу ответа через
err- его нужно брать из объектаresponseвнутри блока except.
Решение: всегда использовать response.raise_for_status() и помещать обработку в try-except.
Как проверить статус без исключений?
Когда не требуется прерывание потока, можно просто проверять свойство response.ok или сравнивать status_code.
resp = requests.get('https://httpbin.org/status/403')
if not resp.ok:
print(f'Ошибка доступа (код {resp.status_code})')
# дополнительная логика
else:
print('Успех')
Python traceback using (трассировка ошибок в python)
Проблема:
При статусе 2xx свойство ok - True, иначе False. Но для некоторых кодов (например, 3xx) также будет False, если не настроен редирект. Это может ввести в заблуждение.
Как обработать конкретную ошибку (404, 401)?
Иногда нужно различное поведение для разных кодов. Проверка статуса вручную даёт полный контроль.
resp = requests.get('https://httpbin.org/status/401')
if resp.status_code == 401:
print('Требуется авторизация. Обновляем токен...')
elif resp.status_code == 404:
print('Ресурс не найден. Возможно, удалён.')
elif 400 <= resp.status_code < 500:
print(f'Другая клиентская ошибка: {resp.status_code}')
else:
print('OK')Python pip not found (ошибка 'pip not found' в python)
Риск:
При большом количестве кодов код становится громоздким. Лучше использовать словарь обработчиков.
handlers = {
401: lambda r: 'Обновление токена',
404: lambda r: 'Поиск альтернативы',
429: lambda r: 'Пауза на 10 секунд'
}
resp = requests.get('...')
handler = handlers.get(resp.status_code)
if handler:
print(handler(resp))Unable to locate package python (ошибка 'unable to locate package' в python)
Как автоматически повторить запрос при ошибке HTTP-клиента?
Для временных ошибок (например, 429 Too Many Requests) можно использовать адаптер с повторными попытками из requests.adapters.
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))
try:
response = session.get('https://httpbin.org/status/429')
response.raise_for_status()
except requests.exceptions.RetryError:
print('Все попытки исчерпаны.')
except requests.exceptions.HTTPError as e:
print(f'Ошибка после повторных попыток: {e}')File not found python (ошибка filenotfounderror в python)
Важно:
- Повторные попытки не решают проблему постоянных ошибок (404, 401).
- При большом
backoff_factorвозрастает время ожидания.
Как обрабатывать ошибки в асинхронных запросах (aiohttp)?
В aiohttp клиентские ошибки также являются исключениями, но перехватываются через try-except внутри корутины.
import aiohttp
import asyncio
async def fetch():
async with aiohttp.ClientSession() as session:
try:
async with session.get('https://httpbin.org/status/418') as resp:
resp.raise_for_status()
data = await resp.text()
except aiohttp.ClientResponseError as e:
print(f'Ошибка {e.status}: {e.message}')
except aiohttp.ClientError as e:
print(f'Общая ошибка клиента: {e}')
asyncio.run(fetch())Python modulenotfounderror no module named (ошибка modulenotfounderror)
Нюанс:
aiohttp.ClientResponseError не всегда содержит тело ответа. Для его получения нужно обращаться к resp.text() до вызова raise_for_status().
async with session.get(...) as resp:
body = await resp.text()
resp.raise_for_status() # если ошибка, тело уже сохраненоIo error python (ошибка ввода-вывода в python)
Расширенные примеры обработки клиентских ошибок
Дополнительные техники: логирование, интеграция с декораторами, обработка ошибок HTTPX.
Как логировать все клиентские ошибки централизованно?
Использование декоратора для всех запросов.
import requests
import logging
logging.basicConfig(level=logging.INFO)
def log_client_error(func):
def wrapper(*args, **kwargs):
try:
resp = func(*args, **kwargs)
resp.raise_for_status()
return resp
except requests.exceptions.HTTPError as e:
logging.error(f'HTTP {resp.status_code}: {e}')
raise
return wrapper
@log_client_error
def fetch_data(url):
return requests.get(url)
fetch_data('https://httpbin.org/status/400')ошибка компиляции python (ошибка компиляции (синтаксиса) в python)
ERROR:root:HTTP 400: 400 Client Error: BAD REQUEST for url: https://httpbin.org/status/400
Python traceback (трассировка стека в python)
Как обрабатывать ошибки HTTPX с собственным исключением?
Библиотека httpx предоставляет httpx.HTTPStatusError, похожее на requests.
import httpx
try:
with httpx.Client() as client:
response = client.get('https://httpbin.org/status/410')
response.raise_for_status()
except httpx.HTTPStatusError as e:
print(f'Код: {e.response.status_code}, сообщение: {e.response.text}')
except httpx.RequestError as e:
print(f'Ошибка соединения: {e}')Script not found python (ошибка 'script not found')
Код: 410, сообщение: ...
List out of range python (ошибка indexerror: list index out of range в python)
Как извлечь тело ошибки и код из ответа без исключения?
Иногда нужно прочитать JSON с описанием ошибки, даже если код 4xx.
resp = requests.get('https://httpbin.org/status/422')
if resp.status_code != 200:
try:
error_info = resp.json()
print(f'Причина: {error_info.get("message", "-")}')
except ValueError:
print('Тело не в JSON:', resp.text[:100])Python codec can t decode byte (ошибка декодирования байтов в python)
Как пропускать определённые коды ошибок (например, 404) при массовых запросах?
Использовать фильтрацию перед raise_for_status().
import requests
def safe_get(url, ignore_codes={404}):
resp = requests.get(url)
if resp.status_code in ignore_codes:
print(f'Пропуск {resp.status_code}')
return None
resp.raise_for_status()
return resp
safe_get('https://httpbin.org/status/404') # None без исключений
Дополнительные подробные примеры
Пример 1: Кастомный обработчик для повторных попыток с учётом заголовка Retry-After
При ошибке 429 сервер может указать время ожидания. Реализуем повтор с задержкой.
import time
import requests
def retry_on_429(url, max_retries=3):
for attempt in range(max_retries):
resp = requests.get(url)
if resp.status_code != 429:
resp.raise_for_status()
return resp
wait = int(resp.headers.get('Retry-After', 5))
print(f'Повтор через {wait} сек. Попытка {attempt+1}')
time.sleep(wait)
raise Exception('Слишком много запросов, превышено число попыток.')
try:
response = retry_on_429('https://httpbin.org/status/429')
except Exception as e:
print(e)
Повтор через 5 сек. Попытка 1 Повтор через 5 сек. Попытка 2 ... Слишком много запросов, превышено число попыток.
Пример 2: Обработка ошибок в цепочке редиректов (requests)
Если редирект ведёт на несуществующий URL, raise_for_status() сработает на последнем ответе.
import requests
try:
# Устанавливаем allow_redirects=False для ручного отслеживания
resp = requests.get('https://httpbin.org/redirect-to?url=https://httpbin.org/status/404', allow_redirects=False)
if resp.status_code in (301, 302, 303, 307, 308):
print(f'Редирект на {resp.headers["Location"]}')
# Вручную переходим
resp2 = requests.get(resp.headers['Location'])
resp2.raise_for_status()
except requests.exceptions.HTTPError as e:
print('Обнаружена ошибка в редиректе:', e)
Пример 3: Использование HTTP-сессии с собственным исключением для бизнес-логики
import requests
from requests import Session
class ClientError(Exception):
def __init__(self, status, body):
self.status = status
self.body = body
super().__init__(f'Сервер вернул {status}')
def api_request(session: Session, url: str):
resp = session.get(url)
if 400 <= resp.status_code < 500:
raise ClientError(resp.status_code, resp.text)
resp.raise_for_status()
return resp.json()
session = requests.Session()
try:
data = api_request(session, 'https://httpbin.org/status/403')
except ClientError as e:
print(f'Код {e.status}: {e.body}')
Пример 4: Обработка ошибок в aiohttp с разделением на сетевые и клиентские
import aiohttp
import asyncio
async def fetch_with_errors(session, url):
try:
async with session.get(url) as resp:
if resp.status >= 400:
text = await resp.text()
raise aiohttp.ClientResponseError(
resp.request_info,
resp.history,
status=resp.status,
message=text[:50]
)
return await resp.json()
except aiohttp.ClientConnectorError as e:
print(f'Соединение не удалось: {e}')
except asyncio.TimeoutError:
print('Таймаут запроса')
except aiohttp.ClientResponseError as e:
print(f'HTTP {e.status}: {e.message}')
async def main():
async with aiohttp.ClientSession() as session:
await fetch_with_errors(session, 'https://httpbin.org/status/418')
asyncio.run(main())
Пример 5: Проверка нескольких эндпоинтов и сбор списка ошибок
import requests
urls = [
'https://httpbin.org/status/200',
'https://httpbin.org/status/404',
'https://httpbin.org/status/500',
]
errors = []
for url in urls:
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
errors.append(None)
except requests.RequestException as e:
errors.append((url, str(e)))
for url, err in errors:
if err:
print(f'{url}: {err}')
https://httpbin.org/status/404: 404 Client Error: NOT FOUND for url: ... https://httpbin.org/status/500: 500 Server Error: INTERNAL SERVER ERROR for url: ...