Чому request.get () не повертається? Який тайм-аут за замовчуванням використовує request.get ()?


93

У моєму сценарії 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

Відповіді:


129

Який час очікування за замовчуванням використовується?

Типовий час очікування - Noneце означає, що він зачекає (зависне), поки з’єднання не буде закрито.

Що відбувається, коли ви передаєте значення тайм-ауту?

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

3
Гадаю, ти маєш рацію. Noneозначає нескінченне (або "зачекайте, поки зв’язок не закриється") Якщо я сам проходжу тайм-аут, він повертається!
Наваз

14
Час очікування @User працює так само добре з https, як і з http
jaapz

Здається, це дуже важко знайти в документах, гуглюючи чи іншим способом. Хтось знає, де це відображається в документах?
слова до того ж


Дякую, print(requests.request.__doc__)хоча робота в IPython - це більше того, що я шукав. Мені було цікаво, які ще необов’язкові аргументи request.get()були.
слова до того ж

40

З документації запитів :

Ви можете сказати Requests припинити очікування відповіді через задану кількість секунд за допомогою параметра timeout:

>>> 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. Отримати всю відповідь


7
Ви так і не відповіли, що за замовчуванням
користувач

Цитата: Ви можете сказати Requests припинити очікування відповіді через задану кількість секунд за допомогою параметра timeout. Майже весь робочий код повинен використовувати цей параметр майже у всіх запитах. Якщо цього не зробити, програма може зависнути на невизначений час: час очікування примітки не є обмеженням часу для всього завантаження відповіді; швидше, виникає виняток, якщо сервер не видав відповідь протягом секунд тайм-ауту (точніше, якщо байтів не було отримано в базовому сокеті за секунди тайм-ауту). Якщо час очікування не вказано явно, запити не таймаут.
ДД

Код має друкарську помилку: імпортувати запити <новий рядок тут> із request.adapters import timeoutSauce
Sinan Çetinkaya

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 (всього = 60) асинхронізації з aiohttp.ClientSession (timeout = timeout) як клієнт:
Том Айвс

2

Виправлення задокументованої функції "надсилання" це виправить для всіх запитів - навіть у багатьох залежних бібліотеках та 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 також має тайм-аути за замовчуванням.


0

У моєму випадку причина "request.get ніколи не повертається" полягає в тому, що requests.get()спробу підключення до хосту вирішено спочатку за допомогою ipv6 ip . Якщо щось пішло не так, щоб підключити цей ipv6 ip і застрягти, тоді він повторює ipv4 ip лише в тому випадку, якщо я явно встановив timeout=<N seconds>і натиснув тайм-аут.

Моє рішення полягає в виправленні мавп на python, 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.