Ошибки HTTP-клиента в Python: причины, диагностика, исправление

Раздел: HTTP -> Ошибки и исключения

Обработка ошибок 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 без исключений
- Python externally managed environment (ошибка externally managed environment в python)
- Python error code 1 (ошибка python с кодом 1)
- Line in module python ошибка (ошибка в строке модуля python)

Дополнительные подробные примеры

Пример 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: ...

Ошибка HTTP-клиента в Python - comments

En
Client error python (python)