ทำไมไม่ request.get () return? การหมดเวลาเริ่มต้นที่ request.get () ใช้คืออะไร?


94

ในสคริปต์ของฉัน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 รองรับ Monica

1
@ user2357112: เอาล่ะ แก้ไขคำถามแล้ว
Nawaz

2
พร็อกซีของคุณยังไม่ถูกต้อง คุณต้องระบุดังนี้: proxies={'http': 'http://222.255.169.74:8080'}. นั่นอาจเป็นสาเหตุว่าทำไมจึงไม่เสร็จสมบูรณ์โดยไม่มีการหมดเวลา
Ian Stapleton Cordasco

คำตอบ:


132

การหมดเวลาเริ่มต้นที่ใช้คืออะไร?

การหมดเวลาเริ่มต้นคือNoneซึ่งหมายความว่าจะรอ (หยุด) จนกว่าการเชื่อมต่อจะปิด

จะเกิดอะไรขึ้นเมื่อคุณผ่านค่าการหมดเวลา

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

3
ฉันคิดว่าคุณพูดถูก Noneหมายถึงไม่มีที่สิ้นสุด (หรือ "รอจนกว่าการเชื่อมต่อจะปิด") ถ้าฉันหมดเวลาตัวเองมันจะกลับมา!
Nawaz

14
@ การหมดเวลาของผู้ใช้ทำงานได้ดีกับ https เช่นเดียวกับ http
jaapz

ดูเหมือนว่าจะหายากมากในเอกสารโดยใช้ googling หรืออย่างอื่น ใครทราบว่าสิ่งนี้ปรากฏในเอกสารหรือไม่
พูด


ขอบคุณการทำ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
คุณไม่เคยตอบว่าค่าเริ่มต้นคืออะไร
ผู้ใช้

ใบเสนอราคา: คุณสามารถบอกให้คำร้องขอหยุดรอการตอบกลับหลังจากผ่านไปตามจำนวนวินาทีที่กำหนดโดยใช้พารามิเตอร์การหมดเวลา รหัสการผลิตเกือบทั้งหมดควรใช้พารามิเตอร์นี้ในคำขอเกือบทั้งหมด หากไม่ทำเช่นนั้นอาจทำให้โปรแกรมของคุณหยุดทำงานอย่างไม่มีกำหนด: หมายเหตุการหมดเวลาไม่ใช่การ จำกัด เวลาสำหรับการดาวน์โหลดการตอบกลับทั้งหมด แต่จะมีข้อยกเว้นเพิ่มขึ้นหากเซิร์ฟเวอร์ไม่ได้ตอบสนองเป็นวินาทีหมดเวลา (แม่นยำยิ่งขึ้นหากไม่มีการรับไบต์บนซ็อกเก็ตที่อยู่ภายใต้การหมดเวลา) หากไม่มีการระบุระยะหมดเวลาอย่างชัดเจนคำขอจะไม่หมดเวลา
DDay

รหัสมีการพิมพ์ผิด: คำขอนำเข้า <บรรทัดใหม่ที่นี่> จากการร้องขออะแดปเตอร์นำเข้า 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
Alex Polekha

สิ่งนี้ช่วยแก้ปัญหาของฉันเมื่อวิธีอื่นไม่สามารถทำได้ Py 3.7 เนื่องจากการลดราคาต้องใช้ ... timeout = aiohttp.ClientTimeout (total = 60) async กับ aiohttp.ClientSession (timeout = timeout) เป็นไคลเอนต์:
Thom Ives

2

การแก้ไขฟังก์ชัน "ส่ง" ที่บันทึกไว้จะแก้ไขปัญหานี้สำหรับคำขอทั้งหมดแม้ในไลบรารีและ sdk ที่ขึ้นอยู่กับจำนวนมาก เมื่อทำการแพทช์ libs อย่าลืมแก้ไขฟังก์ชันที่รองรับ / เอกสารไม่ใช่ 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>และกดหมดเวลา

วิธีแก้ปัญหาของฉันคือการปะลิง python socketเพื่อละเว้น ipv6 (หรือ ipv4 หาก ipv4 ไม่ทำงาน) ทั้งคำตอบนี้หรือคำตอบนี้ใช้ได้กับฉัน

คุณอาจสงสัยว่าทำไมcurlคำสั่งจึงใช้งานได้เนื่องจากcurlเชื่อมต่อ ipv4 โดยไม่ต้องรอให้ ipv6 เสร็จสมบูรณ์ คุณสามารถติดตาม syscalls ซ็อกเก็ตด้วยstrace -ff -e network -s 10000 -- curl -vLk '<your url>'คำสั่ง สำหรับ python strace -ff -e network -s 10000 -- python3 <your python script>สามารถใช้คำสั่งได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.