Управление таймаутами HTTP запросов средствами Python

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

Установка таймаута для HTTP запроса с помощью библиотеки requests

Наиболее эффективное решение для установки таймаута HTTP запроса в Python заключается в использовании параметра timeout при вызове методов библиотеки requests. Этот параметр задает максимальное время ожидания ответа от сервера в секундах. Если сервер не отвечает в течение указанного времени, библиотека выбрасывает исключение requests.exceptions.Timeout.

Пример базового использования:

import requests

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

Python requests get (get-запрос через requests в python)

В этом примере запрос к httpbin.org/delay/5 ожидает ответа до 3 секунд. Так как сервер задерживает ответ на 5 секунд, возникает исключение Timeout.

Таймаут можно задавать как кортеж из двух чисел для раздельного контроля соединения и чтения:

response = requests.get('https://example.com', timeout=(3, 10))

Get html python (получение html-содержимого через http в python)

Первое число (3) - таймаут на установление соединения, второе (10) - на получение полного ответа. Если указано одно число, оно применяется к обеим фазам.

Типичные проблемы:

  • Если не указать timeout (оставить None), запрос может ждать ответа бесконечно, что приведет к зависанию программы.
  • Исключение Timeout может быть вызвано не только медленным ответом, но и проблемами с сетью. Рекомендуется обрабатывать также requests.exceptions.ConnectionError и requests.exceptions.ReadTimeout.
  • Таймаут в библиотеке requests не влияет на время разрешения DNS, которое обрабатывается на уровне ОС и может быть настроено отдельно.

Варианты установки таймаута

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

Для этого используется кортеж (connect_timeout, read_timeout). Если нужно отключить один из таймаутов, можно передать None для соответствующей части, но это не рекомендуется, так как может привести к зависанию.

response = requests.get('https://httpbin.org/delay/2', timeout=(5, None))  # таймаут соединения 5 сек, чтение без ограничения

Url запрос python (работа с url в python)

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

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

В объекте requests.Session можно задать параметр timeout по умолчанию с помощью адаптера или при каждом вызове. Удобнее всего создать кастомный адаптер.

import requests
from requests.adapters import HTTPAdapter

session = requests.Session()
adapter = HTTPAdapter()
adapter.timeout = (5, 15)  # задаем таймаут для всех запросов через этот адаптер
session.mount('https://', adapter)

response = session.get('https://httpbin.org/delay/1')
print(response.status_code)

Python urllib request (отправка запросов с помощью urllib.request в python)

В этом примере все запросы, отправленные через session.get(), будут использовать таймаут (5, 15). Обратите внимание, что атрибут timeout у адаптера - нестандартный, но работает.

Такое присваивание может быть недокументированным и измениться в будущих версиях. Альтернатива - использовать декоратор или обертку вокруг session.request.

Как обработать таймаут с помощью сигналов Unix?

Для критически важных таймаутов, которые должны прервать любой блокирующий вызов, можно использовать модуль signal. Этот способ работает только в Unix системах и с однопоточными программами.

import signal
import requests

class TimeoutError(Exception):
    pass

def handler(signum, frame):
    raise TimeoutError('Превышено время ожидания')

signal.signal(signal.SIGALRM, handler)
signal.alarm(5)  # установить таймаут 5 секунд

try:
    response = requests.get('https://httpbin.org/delay/10')
    print(response.status_code)
except TimeoutError:
    print('Запрос прерван по сигналу')
finally:
    signal.alarm(0)  # отключить сигнал

Files upload python (загрузка файлов на сервер с помощью python (requests, flask))

Сигнал SIGALRM посылается через 5 секунд, вызывая исключение. Этот метод может прервать любую блокирующую операцию, но не работает в многопоточных приложениях и на Windows.

Сигналы могут быть опасны, если внутри обработчика выполняются неатомарные операции. Не рекомендуется для продакшн кода без тщательного тестирования.

Как установить таймаут с помощью asyncio и aiohttp?

Для асинхронного сетевого программирования используется библиотека aiohttp, где таймаут задается через параметр timeout в секундах или через объект aiohttp.ClientTimeout.

import asyncio
import aiohttp

async def fetch(session, url):
    timeout = aiohttp.ClientTimeout(total=10)
    try:
        async with session.get(url, timeout=timeout) as response:
            return await response.text()
    except asyncio.TimeoutError:
        return 'Таймаут'

async def main():
    async with aiohttp.ClientSession() as session:
        result = await fetch(session, 'https://httpbin.org/delay/15')
        print(result)

asyncio.run(main())

Python requests method (методы http-запросов в python (get, post, put, delete) с requests)

Таймаут можно задать для соединения, чтения и общей длительности запроса через отдельные поля ClientTimeout.

Необходимо убедиться, что вся программа построена на asyncio, иначе таймауты могут не работать корректно.

Как установить таймаут с использованием urllib3 напрямую?

Библиотека requests использует urllib3 под капотом. Можно настроить таймаут на уровне PoolManager или HTTPAdapter.

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

session = requests.Session()
retries = Retry(total=2, backoff_factor=1, status_forcelist=[502, 503])
adapter = HTTPAdapter(max_retries=retries)
adapter.timeout = (3, 10)  # как ранее
session.mount('http://', adapter)
session.mount('https://', adapter)

response = session.get('https://example.com')

Этот подход позволяет объединить настройки таймаута и повторных попыток.

Атрибут timeout у адаптера не является официальным; лучше передавать таймаут непосредственно в вызове session.get(…, timeout=…).

- Python post file (отправка файла через post-запрос (requests.post(file)) в python)
- Python requests параметры (передача параметров в get/post запросах requests в python)
- Request session python (использование session в библиотеке requests (куки, заголовки) в python)

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

Пример 1. Таймаут с повторными попытками и обработкой ошибок

Пример
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time

session = requests.Session()
retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])
adapter = HTTPAdapter(max_retries=retries)
session.mount('http://', adapter)
session.mount('https://', adapter)

urls = ['https://httpbin.org/delay/1', 'https://httpbin.org/delay/6']
for url in urls:
    try:
        start = time.time()
        # Таймаут 5 секунд для всего запроса
        response = session.get(url, timeout=5)
        elapsed = time.time() - start
        print(f'{url} - статус {response.status_code}, время {elapsed:.2f}s')
    except requests.exceptions.Timeout:
        print(f'{url} - превышен таймаут')
    except requests.exceptions.ConnectionError:
        print(f'{url} - ошибка соединения')
https://httpbin.org/delay/1 - статус 200, время 1.23s
https://httpbin.org/delay/6 - превышен таймаут

Пример 2. Асинхронный таймаут с aiohttp и разными лимитами

Пример
import asyncio
import aiohttp

async def fetch_with_timeout(url, timeout_total=10, timeout_connect=5):
    timeout = aiohttp.ClientTimeout(total=timeout_total, connect=timeout_connect)
    async with aiohttp.ClientSession() as session:
        try:
            async with session.get(url, timeout=timeout) as resp:
                return await resp.json()
        except asyncio.TimeoutError as e:
            return {'error': str(e)}

async def main():
    result = await fetch_with_timeout('https://httpbin.org/delay/8', timeout_total=8)
    print(result)

asyncio.run(main())
{'error': ''}

В данном примере общий таймаут 8 секунд, и запрос к delay/8 успеет вернуть ответ до истечения. Если увеличить задержку, будет выброшено исключение.

Пример 3. Использование Eventlet для таймаута без изменения кода

Пример
import eventlet
eventlet.monkey_patch()
import requests

# Устанавливаем таймаут с помощью примитивов Eventlet
with eventlet.Timeout(3, False):  # False значит не выбрасывать исключение, а просто вернуть None
    try:
        response = requests.get('https://httpbin.org/delay/4')
        print(response.status_code)
    except eventlet.Timeout:
        print('Таймаут по Eventlet')
Таймаут по Eventlet

Обратите внимание: eventlet.Timeout с аргументом False не генерирует исключение, а просто позволяет выполнению продолжиться. Для обработки таймаута нужно явно проверять возвращаемое значение.

Пример 4. Таймаут на уровне Transport Adapter в urllib3

Пример
import requests
from requests.packages.urllib3 import PoolManager, Timeout

class CustomAdapter(requests.adapters.HTTPAdapter):
    def send(self, request, **kwargs):
        kwargs['timeout'] = Timeout(connect=2, read=10)
        return super().send(request, **kwargs)

session = requests.Session()
session.mount('https://', CustomAdapter())

response = session.get('https://httpbin.org/delay/3')
print(response.status_code)

Этот метод позволяет тонко настроить таймауты, переопределив метод send.

200

Пример 5. Комбинирование таймаута с кэшированием и повторными попытками в одном адаптере

Пример
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class TimeoutRetryAdapter(HTTPAdapter):
    def __init__(self, timeout=(5, 10), retries=3):
        self.timeout = timeout
        self.max_retries = Retry(total=retries, backoff_factor=1)
        super().__init__(max_retries=self.max_retries)

    def send(self, request, **kwargs):
        kwargs.setdefault('timeout', self.timeout)
        return super().send(request, **kwargs)

session = requests.Session()
adapter = TimeoutRetryAdapter(timeout=(3, 8), retries=2)
session.mount('https://', adapter)

response = session.get('https://httpbin.org/delay/2')
print(response.status_code)
200

Установка таймаута для HTTP-запроса (requests timeout) в Python - comments

En
Python requests timeout (python)