Почему не возвращается request.get ()? Какой тайм-аут по умолчанию использует requests.get ()?


95

В моем сценарии requests.getникогда не возвращается:

import requests

print ("requesting..")

# This call never returns!
r = requests.get(
    "http://www.some-site.com",
    proxies = {'http': '222.255.169.74:8080'},
)

print(r.ok)

Какая может быть возможная причина (ы)? Любое средство? Какой тайм-аут по умолчанию getиспользуется?


1
@ user2357112: Это важно? Я сомневаюсь.
Nawaz

Это определенно имеет значение. Если вы предоставите URL-адрес, к которому вы пытаетесь получить доступ, и прокси-сервер, который вы пытаетесь использовать, мы сможем увидеть, что произойдет, когда мы попытаемся отправить аналогичные запросы.
user2357112 поддерживает Монику

1
@ user2357112: Хорошо. Отредактировал вопрос.
Nawaz

2
Ваш прокси также неверен. Вы должны указать это нравится так: proxies={'http': 'http://222.255.169.74:8080'}. Возможно, поэтому он не завершается без тайм-аута.
Ian Stapleton Cordasco

Ответы:


133

Какой тайм-аут по умолчанию используется?

Тайм-аут по умолчанию - Noneэто означает, что он будет ждать (зависать), пока соединение не будет закрыто.

Что происходит, когда вы передаете значение тайм-аута?

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)

3
Я думаю ты прав. Noneозначает бесконечно (или «ждать, пока соединение не закроется»). Если я сам пропущу таймаут, он вернется!
Nawaz

14
@User timeout работает так же хорошо с https, как и с http
jaapz

Кажется, это действительно сложно найти в документации с помощью поиска в Google или иным образом. Кто-нибудь знает, где это отображается в документах?
поводу


Спасибо, работа print(requests.request.__doc__)в IPython - это больше того, что я искал. Мне было интересно, какие еще необязательные аргументы request.get()были.
поводу

40

Из запросов документации :

Вы можете указать запросам прекратить ожидание ответа через заданное количество секунд с помощью параметра тайм-аута:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

Заметка:

таймаут - это не ограничение по времени на загрузку всего ответа; скорее, возникает исключение, если сервер не отправил ответ на время ожидания в секундах (точнее, если не было получено байтов на базовый сокет в течение секунд ожидания).

У меня часто бывает, что request.get () очень долго возвращается, даже если timeoutэто 1 секунда. Есть несколько способов решить эту проблему:

1. Используйте TimeoutSauceвнутренний класс

От: https://github.com/kennethreitz/requests/issues/1928#issuecomment-35811896

import requests from requests.adapters import TimeoutSauce

class MyTimeout(TimeoutSauce):
    def __init__(self, *args, **kwargs):
        if kwargs['connect'] is None:
            kwargs['connect'] = 5
        if kwargs['read'] is None:
            kwargs['read'] = 5
        super(MyTimeout, self).__init__(*args, **kwargs)

requests.adapters.TimeoutSauce = MyTimeout

Этот код должен заставить нас установить тайм-аут чтения равным таймауту подключения, который является значением тайм-аута, которое вы передаете при вызове Session.get (). (Обратите внимание, что я на самом деле не тестировал этот код, поэтому может потребоваться быстрая отладка, я просто написал его прямо в окне GitHub.)

2. Используйте форк запросов от kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Из его документации: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Если вы укажете одно значение для тайм-аута, например:

r = requests.get('https://github.com', timeout=5)

Значение тайм-аута будет применяться как к таймаутам соединения, так и к тайм-аутам чтения. Укажите кортеж, если вы хотите установить значения отдельно:

r = requests.get('https://github.com', timeout=(3.05, 27))

ПРИМЕЧАНИЕ. С тех пор изменение было внесено в основной проект запросов .

3. Использование evenletили, signalкак уже упоминалось в аналогичном вопросе: Тайм-аут для запросов python. Получить весь ответ


8
Вы так и не ответили, что по умолчанию
Пользователь

Цитата: вы можете указать запросам, чтобы они перестали ждать ответа через заданное количество секунд с помощью параметра тайм-аута. Практически весь производственный код должен использовать этот параметр почти во всех запросах. Невыполнение этого требования может привести к зависанию вашей программы на неопределенное время: Обратите внимание, что тайм-аут не является ограничением по времени для всей загрузки ответа; скорее, возникает исключение, если сервер не отправил ответ на время ожидания в секундах (точнее, если не было получено байтов на базовый сокет в течение секунд ожидания). Если тайм-аут не указан явно, запросы не прерываются.
DD май

В коде есть опечатка: запросы на импорт <новая строка здесь> из requests.adapters import TimeoutSauce
Синан Четинкая

4

Я хотел, чтобы тайм-аут по умолчанию легко добавлялся в кучу кода (при условии, что тайм-аут решает вашу проблему)

Это решение, которое я взял из заявки, отправленной в репозиторий для запросов.

кредит: https://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

Решение - последняя пара строк здесь, но я показываю больше кода для лучшего контекста. Мне нравится использовать сеанс для повторной попытки.

import requests
import functools
from requests.adapters import HTTPAdapter,Retry


def requests_retry_session(
        retries=10,
        backoff_factor=2,
        status_forcelist=(500, 502, 503, 504),
        session=None,
        ) -> requests.Session:
    session = session or requests.Session()
    retry = Retry(
            total=retries,
            read=retries,
            connect=retries,
            backoff_factor=backoff_factor,
            status_forcelist=status_forcelist,
            )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    # set default timeout
    for method in ('get', 'options', 'head', 'post', 'put', 'patch', 'delete'):
        setattr(session, method, functools.partial(getattr(session, method), timeout=30))
    return session

тогда вы можете сделать что-то вроде этого:

requests_session = requests_retry_session()
r = requests_session.get(url=url,...

4

Просмотрел все ответы и пришел к выводу, что проблема все еще существует. На некоторых сайтах запросы могут зависать бесконечно, и использование многопроцессорной обработки кажется излишним. Вот мой подход (Python 3.5+):

import asyncio

import aiohttp


async def get_http(url):
    async with aiohttp.ClientSession(conn_timeout=1, read_timeout=3) as client:
        try:
            async with client.get(url) as response:
                content = await response.text()
                return content, response.status
        except Exception:
            pass


loop = asyncio.get_event_loop()
task = loop.create_task(get_http('http://example.com'))
loop.run_until_complete(task)
result = task.result()
if result is not None:
    content, status = task.result()
    if status == 200:
        print(content)

ОБНОВИТЬ

Если вы получили предупреждение об устаревании использования conn_timeout и read_timeout, проверьте в нижней части ЭТОЙ справки, как использовать структуру данных ClientTimeout. Один простой способ применить эту структуру данных в соответствии со связанной ссылкой на исходный код выше:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.

2
@Nawaz Python 3.5+. Спасибо за вопрос, обновил ответ версией Python. Это законный код Python. Пожалуйста, взгляните на документацию aiohttp aiohttp.readthedocs.io/en/stable/index.html
Алекс Полеха

Это решило мои проблемы, когда другие методы - нет. Py 3.7. Из-за ограничений пришлось использовать ... timeout = aiohttp.ClientTimeout (total = 60) async с aiohttp.ClientSession (timeout = timeout) в качестве клиента:
Том Айвз,

2

Исправление задокументированной функции «send» исправит это для всех запросов - даже во многих зависимых библиотеках и SDK. При установке исправлений для библиотек убедитесь, что вы исправляете поддерживаемые / документированные функции, а не TimeoutSauce - иначе вы можете потерять эффект вашего исправления.

import requests

DEFAULT_TIMEOUT = 180

old_send = requests.Session.send

def new_send(*args, **kwargs):
     if kwargs.get("timeout", None) is None:
         kwargs["timeout"] = DEFAULT_TIMEOUT
     return old_send(*args, **kwargs)

requests.Session.send = new_send

Последствия отсутствия тайм-аута довольно серьезны, и использование тайм-аута по умолчанию почти никогда ничего не может сломать, потому что сам TCP также имеет таймауты по умолчанию.


1

В моем случае причина того, что «requests.get никогда не возвращается», заключается в том, что requests.get()попытка подключения к хосту сначала разрешена с помощью ipv6 ip . Если что-то пошло не так, чтобы подключить этот ipv6 ip и застрять, он повторяет ipv4 ip только в том случае, если я явно установил timeout=<N seconds>и нажал тайм-аут.

Мое решение - обезьяна исправляет питон, socketчтобы игнорировать ipv6 (или ipv4, если ipv4 не работает), либо этот ответ, либо этот ответ работает для меня.

Вы можете спросить, почему curlкоманда работает, потому что curlподключайтесь к ipv4, не дожидаясь завершения ipv6. Вы можете отслеживать системные вызовы сокета с помощью strace -ff -e network -s 10000 -- curl -vLk '<your url>'команды. Для python strace -ff -e network -s 10000 -- python3 <your python script>можно использовать команду.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.