Управление таймаутами HTTP запросов средствами Python
Установка таймаута для 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=…).
Расширенные примеры настройки таймаута
Пример 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