Mengapa requests.get () tidak kembali? Apa waktu tunggu default yang digunakan requests.get ()?


94

Dalam skrip saya, requests.gettidak pernah kembali:

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)

Apa kemungkinan alasannya? Ada obatnya? Apa waktu tunggu default yang getdigunakan?


1
@ user2357112: Apakah itu penting? Saya ragu.
Nawaz

Itu pasti penting. Jika Anda memberikan URL yang Anda coba akses dan proxy yang Anda coba gunakan, kami dapat melihat apa yang terjadi ketika kami mencoba mengirim permintaan serupa.
user2357112 mendukung Monica

1
@ user2357112: Baiklah. Mengedit pertanyaan.
Nawaz

2
Proksi Anda juga salah. Anda harus menentukan rasanya begitu: proxies={'http': 'http://222.255.169.74:8080'}. Itu bisa jadi mengapa tidak selesai tanpa batas waktu.
Ian Stapleton Cordasco

Jawaban:


132

Apa waktu tunggu default yang digunakan?

Batas waktu default adalah None, yang berarti menunggu (hang) hingga koneksi ditutup.

Apa yang terjadi jika Anda memberikan nilai batas waktu?

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

3
Saya pikir Anda benar. Noneberarti tak terbatas (atau "tunggu sampai koneksi dekat"). Jika saya sendiri melewatkan waktu tunggu, itu kembali!
Nawaz

14
@User timeout bekerja dengan baik dengan https seperti halnya dengan http
jaapz

Ini tampaknya sangat sulit ditemukan di dokumen dengan googling atau sebaliknya. Adakah yang tahu di mana ini muncul di dokumen?
pula kata


Terima kasih, melakukan print(requests.request.__doc__)di IPython lebih dari apa yang saya cari. Saya bertanya-tanya argumen opsional apa lagi yang request.get()ada.
kata

40

Dari dokumentasi permintaan :

Anda dapat memberi tahu Permintaan untuk berhenti menunggu respons setelah beberapa detik dengan parameter waktu tunggu:

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

catatan:

waktu tunggu bukanlah batas waktu untuk seluruh unduhan respons; sebaliknya, pengecualian dimunculkan jika server tidak mengeluarkan respons untuk detik waktu tunggu (lebih tepatnya, jika tidak ada byte yang diterima pada soket yang mendasari untuk detik batas waktu).

Itu sering terjadi pada saya bahwa requests.get () membutuhkan waktu yang sangat lama untuk kembali bahkan jika timeout1 detik. Ada beberapa cara untuk mengatasi masalah ini:

1. Gunakan TimeoutSaucekelas internal

Dari: 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

Kode ini seharusnya membuat kita menyetel waktu tunggu baca sama dengan waktu tunggu koneksi, yang merupakan nilai batas waktu yang Anda teruskan pada panggilan Session.get () Anda. (Perhatikan bahwa saya belum benar-benar menguji kode ini, jadi mungkin perlu beberapa debugging cepat, saya baru saja menulisnya langsung ke jendela GitHub.)

2. Gunakan garpu permintaan dari kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Dari dokumentasinya: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Jika Anda menentukan satu nilai untuk waktu tunggu, seperti ini:

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

Nilai batas waktu akan diterapkan ke waktu tunggu koneksi dan baca. Tentukan tupel jika Anda ingin menyetel nilainya secara terpisah:

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

CATATAN: Perubahan tersebut telah digabungkan ke proyek Permintaan utama .

3. Menggunakan evenletatau signalseperti yang telah disebutkan dalam pertanyaan serupa: Batas waktu untuk permintaan python. Dapatkan seluruh respons


8
Anda tidak pernah menjawab apa defaultnya
Pengguna

Kutipan: Anda dapat memberi tahu Permintaan untuk berhenti menunggu respons setelah beberapa detik dengan parameter batas waktu. Hampir semua kode produksi harus menggunakan parameter ini di hampir semua permintaan. Kegagalan untuk melakukannya dapat menyebabkan program Anda macet tanpa batas: Catatan waktu tunggu bukanlah batas waktu untuk seluruh unduhan tanggapan; sebaliknya, pengecualian dimunculkan jika server tidak mengeluarkan respons untuk detik waktu tunggu (lebih tepatnya, jika tidak ada byte yang diterima pada soket yang mendasari untuk detik batas waktu). Jika tidak ada batas waktu yang ditentukan secara eksplisit, permintaan tidak akan habis.
DDay

Kode salah ketik: permintaan impor <baris baru di sini> dari requests.adapters import TimeoutSauce
Sinan Çetinkaya

4

Saya ingin batas waktu default dengan mudah ditambahkan ke sekumpulan kode (dengan asumsi batas waktu menyelesaikan masalah Anda)

Ini adalah solusi yang saya ambil dari tiket yang dikirimkan ke repositori untuk Permintaan.

kredit: https://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

Solusinya adalah beberapa baris terakhir di sini, tetapi saya menunjukkan lebih banyak kode untuk konteks yang lebih baik. Saya suka menggunakan sesi untuk mencoba lagi perilaku.

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

maka Anda dapat melakukan sesuatu seperti ini:

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

4

Meninjau semua jawaban dan sampai pada kesimpulan bahwa masalahnya masih ada. Di beberapa situs, permintaan mungkin macet tanpa batas dan menggunakan multiprosesing tampaknya berlebihan. Inilah pendekatan saya (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)

MEMPERBARUI

Jika Anda menerima peringatan penghentian penggunaan conn_timeout dan read_timeout, periksa di dekat bagian bawah referensi INI untuk mengetahui cara menggunakan struktur data ClientTimeout. Satu cara sederhana untuk menerapkan struktur data ini per referensi yang ditautkan ke kode asli di atas adalah:

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+. Terima kasih atas pertanyaannya, perbarui jawabannya dengan versi Python. Ini kode Python legal. Silakan lihat dokumentasi aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha

Ini memecahkan masalah saya ketika metode lain tidak. Py 3.7. Karena deprications, harus menggunakan ... timeout = aiohttp.ClientTimeout (total = 60) async dengan aiohttp.ClientSession (timeout = timeout) sebagai klien:
Thom Ives

2

Menambal fungsi "kirim" yang terdokumentasi akan memperbaiki masalah ini untuk semua permintaan - bahkan di banyak pustaka dan SDK yang bergantung. Saat menambal libs, pastikan untuk menambal fungsi yang didukung / didokumentasikan, bukan TimeoutSauce - jika tidak, Anda mungkin akan kehilangan efek tambalan Anda secara diam-diam.

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

Efek dari tidak adanya batas waktu cukup parah, dan penggunaan batas waktu default hampir tidak pernah merusak apa pun - karena TCP sendiri memiliki batas waktu default juga.


1

Dalam kasus saya, alasan "requests.get tidak pernah kembali" adalah karena requests.get()upaya untuk menyambung ke host diselesaikan dengan ipv6 ip terlebih dahulu . Jika ada yang salah saat menghubungkan ipv6 ip itu dan macet, maka ipv4 ip akan dicoba lagi jika saya secara eksplisit mengatur timeout=<N seconds>dan menekan batas waktu.

Solusi saya adalah menambal python socketuntuk mengabaikan ipv6 (atau ipv4 jika ipv4 tidak berfungsi), baik jawaban ini atau jawaban ini berfungsi untuk saya.

Anda mungkin bertanya-tanya mengapa curlperintah berfungsi, karena curlsambungkan ipv4 tanpa menunggu ipv6 selesai. Anda dapat melacak sistem socket dengan strace -ff -e network -s 10000 -- curl -vLk '<your url>'perintah. Untuk python, strace -ff -e network -s 10000 -- python3 <your python script>perintah bisa digunakan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.