Tại sao request.get () không trả về? Thời gian chờ mặc định mà request.get () sử dụng là gì?


93

Trong tập lệnh của tôi, requests.getkhông bao giờ trả về:

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)

(Các) lý do có thể là gì? Bất kỳ biện pháp khắc phục? Thời gian chờ mặc định getsử dụng là gì?


1
@ user2357112: Có vấn đề gì không? Tôi nghi ngờ.
Nawaz

Nó chắc chắn quan trọng. Nếu bạn cung cấp URL mà bạn đang cố gắng truy cập và proxy bạn đang cố gắng sử dụng, chúng tôi có thể biết điều gì sẽ xảy ra khi chúng tôi cố gắng gửi các yêu cầu tương tự.
user2357112 hỗ trợ Monica

1
@ user2357112: Được rồi. Đã chỉnh sửa câu hỏi.
Nawaz

2
Proxy của bạn cũng không chính xác. Bạn phải xác định nó như vậy: proxies={'http': 'http://222.255.169.74:8080'}. Đó có thể là lý do tại sao nó không hoàn thành nếu không có thời gian chờ.
Ian Stapleton Cordasco

Câu trả lời:


130

Thời gian chờ mặc định được sử dụng là gì?

Thời gian chờ mặc định là None, có nghĩa là nó sẽ đợi (treo) cho đến khi kết nối bị đóng.

Điều gì xảy ra khi bạn vượt qua giá trị thời gian chờ?

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

3
Tôi nghĩ bạn đúng. Nonecó nghĩa là vô hạn (hoặc "đợi cho đến khi kết nối gần"). Nếu tôi tự mình vượt qua thời gian chờ, nó sẽ trở lại!
Nawaz

14
Timeout @User hoạt động giống như tốt với https như nó bằng http
jaapz

Điều này có vẻ thực sự khó tìm thấy trong tài liệu bằng cách sử dụng googling hoặc cách khác. Có ai biết điều này hiển thị ở đâu trong tài liệu không?
wordsforthewise


Cảm ơn, làm print(requests.request.__doc__)trong IPython là nhiều hơn những gì tôi đang tìm kiếm. Tôi đã tự hỏi những đối số tùy chọn khác request.get()có.
wordsforthewise

40

Từ tài liệu yêu cầu :

Bạn có thể yêu cầu Yêu cầu ngừng chờ phản hồi sau một số giây nhất định bằng tham số thời gian chờ:

>>> 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)

Ghi chú:

thời gian chờ không phải là giới hạn thời gian trên toàn bộ tải xuống phản hồi; thay vào đó, một ngoại lệ được đưa ra nếu máy chủ không đưa ra phản hồi cho giây hết thời gian chờ (chính xác hơn, nếu không có byte nào được nhận trên ổ cắm bên dưới cho giây hết thời gian).

Điều xảy ra rất nhiều với tôi rằng request.get () mất một thời gian rất dài để trả về ngay cả khi timeoutlà 1 giây. Có một số cách để khắc phục sự cố này:

1. Sử dụng TimeoutSaucelớp nội bộ

Từ: 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

Mã này sẽ khiến chúng tôi đặt thời gian chờ đọc bằng với thời gian chờ kết nối, là giá trị thời gian chờ bạn chuyển cho cuộc gọi Session.get () của mình. (Lưu ý rằng tôi chưa thực sự kiểm tra mã này, vì vậy nó có thể cần một số gỡ lỗi nhanh, tôi chỉ viết nó thẳng vào cửa sổ GitHub.)

2. Sử dụng một nhánh các yêu cầu từ kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Từ tài liệu của nó: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Nếu bạn chỉ định một giá trị duy nhất cho thời gian chờ, như sau:

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

Giá trị thời gian chờ sẽ được áp dụng cho cả kết nối và thời gian chờ đã đọc. Chỉ định một bộ giá trị nếu bạn muốn đặt các giá trị riêng biệt:

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

GHI CHÚ: Thay đổi đã được hợp nhất với dự án Yêu cầu chính .

3. Sử dụng evenlethoặc signalnhư đã được đề cập trong câu hỏi tương tự: Thời gian chờ cho các yêu cầu python. Hãy nhắm toàn bộ phản hồi


7
Bạn không bao giờ trả lời những gì mặc định là
tài khoản

Trích dẫn: Bạn có thể yêu cầu Yêu cầu ngừng chờ phản hồi sau một số giây nhất định bằng tham số thời gian chờ. Gần như tất cả mã sản xuất nên sử dụng tham số này trong gần như tất cả các yêu cầu. Không làm như vậy có thể khiến chương trình của bạn bị treo vô thời hạn: Lưu ý thời gian chờ không phải là giới hạn thời gian trên toàn bộ tải xuống phản hồi; thay vào đó, một ngoại lệ được đưa ra nếu máy chủ không đưa ra phản hồi cho giây hết thời gian chờ (chính xác hơn là nếu không nhận được byte nào trên ổ cắm bên dưới cho giây hết thời gian). Nếu không có thời gian chờ nào được chỉ định rõ ràng, các yêu cầu sẽ không hết thời gian chờ.
DDay

Mã có một typo: yêu cầu nhập khẩu <dòng mới đây> từ requests.adapters nhập TimeoutSauce
Sinan Çetinkaya

4

Tôi muốn thời gian chờ mặc định dễ dàng được thêm vào một loạt mã (giả sử thời gian chờ đó giải quyết được vấn đề của bạn)

Đây là giải pháp mà tôi chọn được từ một vé được gửi đến kho lưu trữ Yêu cầu.

tín dụng: https://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

Giải pháp là vài dòng cuối cùng ở đây, nhưng tôi hiển thị nhiều mã hơn để có ngữ cảnh tốt hơn. Tôi muốn sử dụng một phiên để thử lại hành vi.

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

thì bạn có thể làm điều gì đó như sau:

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

4

Đã xem xét tất cả các câu trả lời và đi đến kết luận rằng vấn đề vẫn còn tồn tại. Trên một số trang web, các yêu cầu có thể bị treo vô hạn và việc sử dụng đa xử lý dường như là quá mức cần thiết. Đây là cách tiếp cận của tôi (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)

CẬP NHẬT

Nếu bạn nhận được cảnh báo không dùng nữa về việc sử dụng conn_timeout và read_timeout, hãy kiểm tra gần cuối tài liệu tham khảo NÀY để biết cách sử dụng cấu trúc dữ liệu ClientTimeout. Một cách đơn giản để áp dụng cấu trúc dữ liệu này cho mỗi tham chiếu được liên kết với mã gốc ở trên sẽ là:

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+. Cảm ơn bạn đã đặt câu hỏi, đã cập nhật câu trả lời với phiên bản Python. Đó là mã Python hợp pháp. Vui lòng xem qua tài liệu aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha

Điều này giải quyết vấn đề của tôi trong khi các phương pháp khác không. Py 3,7. Do mô tả, đã phải sử dụng ... timeout = aiohttp.ClientTimeout (tổng cộng = 60) không đồng bộ với aiohttp.ClientSession (timeout = timeout) làm khách hàng:
Thom Ives

2

Việc vá chức năng "send" được lập thành văn bản sẽ sửa lỗi này cho tất cả các yêu cầu - ngay cả trong nhiều thư viện và sdk phụ thuộc. Khi vá các libs, hãy nhớ vá các chức năng được hỗ trợ / tài liệu, chứ không phải TimeoutSauce - nếu không, bạn có thể kết thúc một cách âm thầm làm mất tác dụng của bản vá.

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

Ảnh hưởng của việc không có thời gian chờ nào là khá nghiêm trọng và việc sử dụng thời gian chờ mặc định hầu như không bao giờ có thể phá vỡ bất cứ điều gì - vì bản thân TCP cũng có thời gian chờ mặc định.


0

Trong trường hợp của tôi, lý do "request.get không bao giờ trở lại" là do requests.get()cố gắng kết nối với máy chủ lưu trữ được giải quyết bằng ip ipv6 trước . Nếu có sự cố khi kết nối ip ipv6 đó và bị kẹt, thì nó chỉ thử lại ip ipv4 nếu tôi đã đặt rõ ràngtimeout=<N seconds> và nhấn hết thời gian.

Giải pháp của tôi là khóa con trăn socketđể bỏ qua ipv6 (hoặc ipv4 nếu ipv4 không hoạt động), câu trả lời này hoặc câu trả lời này này phù hợp với tôi.

Bạn có thể tự hỏi tại sao curllệnh hoạt động, vì curlkết nối ipv4 mà không cần đợi ipv6 hoàn tất. Bạn có thể theo dõi các cuộc gọi hệ thống ổ cắm bằng strace -ff -e network -s 10000 -- curl -vLk '<your url>'lệnh. Đối với python, strace -ff -e network -s 10000 -- python3 <your python script>lệnh có thể được sử dụng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.