Kegagalan di Tengah Body yang Lolos dari Handler Timeout Umum requests

Terakhir diperbarui pada August 17, 2026
Kegagalan di Tengah Body yang Lolos dari Handler Timeout Umum requests
Ringkasan AI
Mencari bug di kode requests yang sudah ada? Audit asumsi redirect, handler yang hanya menangkap requests.exceptions.Timeout padahal ingin menutup stall di tengah body, dan konsumer .text yang menerima respons tanpa charset. Migrasi mekanis ke httpx? Ubah namespace exception ke httpx.TimeoutException atau kelas fase yang lebih spesifik, tentukan apakah follow_redirects perlu diaktifkan, dan uji ulang asumsi decoding. Handler requests yang ada memang sudah melewatkan kasus mid-body yang didemonstrasikan; migrasi tidak menciptakan bug spesifik itu. Membuat sesuatu yang baru dan mengambil banyak URL? httpx layak dipilih saat Anda butuh AsyncClient dan timeout terpisah untuk connect, read, write, dan pool.

Tulis guard yang dipakai hampir semua orang:

try:
    r = requests.get(url, timeout=0.5)
except requests.exceptions.Timeout:
    retry()

Arahkan ke server yang langsung mengirim status line dan header, lalu berhenti sebelum body dikirim. Read timeout terjadi. Guard itu tidak terpanggil. Exception yang muncul adalah ConnectionError, dan ConnectionError bukan turunan dari Timeout.

Stall yang sama di httpx memunculkan ReadTimeout, yang merupakan TimeoutException, jadi guard setara akan menangkapnya.

Saya sempat cari tahu mana perbedaan httpx dan requests yang benar-benar bikin scraper kacau, dengan asumsi saya bakal nulis soal async. Ternyata, async justru bagian yang paling tidak menarik dari daftar ini.

Apa yang saya uji dan bagaimana caranya

Delapan probe ke server fixture lokal, karena laporan klien tentang apa yang terjadi bukan bukti nyata tentang apa yang benar-benar berlangsung. Server menghitung koneksi TCP — bertambah satu untuk setiap socket yang diterima, sebelum request line diparse — dan path yang benar-benar di-fetch. Reuse koneksi dan follow redirect sama-sama klaim soal wire, dan wire-lah tempat klaim itu diuji.

httpx 0.28.1 dengan extra http2, requests 2.34.2, Python 3.14.2, macOS arm64. Keduanya dijalankan dalam satu virtualenv baru, jadi tidak saling mewarisi jejak konfigurasi. Output mentah: httpx-probes.json.

Enam prediksi dimasukkan ke harness sebelum run pertama dan tetap ada sesudahnya. Tiga tepat, dua salah, satu benar untuk kasus yang saya bayangkan tapi meleset di kasus yang justru penting. prediction-scorecard.json memuat rinciannya.

Default yang berubah diam-diam

Measured results chart: Defaults that differ between clients

Perilakurequests 2.34.2httpx 0.28.1
Follow redirect secara defaultyatidak
Pemanggilan level modul memakai ulang koneksitidaktidak
Stall sebelum headersReadTimeoutReadTimeout
Stall di tengah bodyConnectionErrorReadTimeout
Tanpa charset yang dideklarasikanISO-8859-1utf-8
HTTP/2tidak tersediaopsional, berfungsi
Timeout terpisah untuk connect/read/write/pooltidakya

Redirect, reuse socket, hasil exception, decoding, dan negosiasi protokol diamati lewat probe. Bentuk API timeout dan ketiadaan flag HTTP/2 di requests adalah observasi kemampuan API. httpx-probes.json.

Tiga baris di atas bisa mengubah perilaku kode Anda di hari migrasi, tanpa peringatan.

Redirect: nonaktif secara default, dan server membuktikannya

Rantai redirect empat hop yang berakhir di /ok:

ClientYang dilihat serverStatus yang dikembalikan
requests5 request200
httpx1 request302
httpx, follow_redirects=True5 request200

Angka lima itu empat hop ditambah tujuan akhir. Prediksi saya menulis empat, yang ternyata cuma hitung-hitungan yang tidak saya cek; arah klaimnya benar, dan jumlahnya saya koreksi di sini, bukan diam-diam di dalam narasi.

Ini perilaku httpx yang terdokumentasi dan desainnya masuk akal — redirect adalah sesuatu yang memang mungkin perlu diketahui caller. Namun, ini juga cara paling umum migrasi bisa rusak tanpa memunculkan error. Kode Anda menerima 302, response.text kosong, parser tidak menemukan baris apa pun, dan log Anda bilang 200 OK… padahal sebenarnya tertulis 302, dan tidak ada yang mengawasi status code karena di requests dulu memang tidak pernah ada yang perlu diawasi.

Temuan soal timeout, yang justru sempat saya balik

Saya memprediksi httpx akan menamai fase yang gagal, sedangkan requests akan menyamarkan keduanya dalam satu kelas. Ternyata kebalikannya.

Referensi resmi: Dokumentasi timeout Requests.

System diagram: Where the Timeout Lands

Referensi resmi: Dokumentasi timeout HTTPX.

Stallrequestshttpx
Sebelum status lineReadTimeoutReadTimeout
Di tengah body, setelah header terkirimConnectionErrorReadTimeout

httpx memberi nama yang sama dan akurat untuk keduanya. requests memisahkannya — dan memisahkannya tepat di batas yang biasanya jadi dasar kode retry.

Konsekuensinya bukan hasil inferensi dari hierarki class. Saya menjalankan guard-nya:

Stallexcept requests.exceptions.Timeoutexcept httpx.TimeoutException
Sebelum status linemenangkapmenangkap
Di tengah bodylolos sebagai ConnectionErrormenangkap

timeout-retry-guard.json. requests.exceptions.ConnectionError bukan turunan dari requests.exceptions.Timeout; httpx.ReadTimeout adalah turunan dari httpx.TimeoutException.

Pesan exception requests berbunyi Read timed out. di dalam ConnectionError. Library-nya tahu apa yang terjadi. Hanya saja, ia tidak meneruskannya ke sistem tipe, padahal itu yang dipakai klausa except Anda.

Kasus yang saya ukur ini spesifik: header datang dulu, lalu progres body berhenti cukup lama sampai lewat read timeout. Respons yang terus mengirim chunk di dalam window timeout, termasuk stream yang memang sengaja dibuat, bisa berperilaku berbeda dan tidak diuji di sini.

Connection pooling: perbedaan utamanya ada di API client

Sepuluh GET, empat cara, socket dihitung di sisi server:

CaraSocket yang dibuka
httpx.get() × 1010
requests.get() × 1010
httpx.Client()1
requests.Session()1

Hasilnya identik, dan ini layak disebut karena justru itu pengetahuan umum yang paling sering salah tentang pasangan ini — bahwa httpx melakukan pooling dan requests tidak. Faktanya, level modul di keduanya tidak melakukan pooling. Pooling terjadi lewat objek client. Kalau hari ini Anda memanggil requests.get() dalam loop, lalu pindah ke httpx.get() dalam loop, tidak ada yang berubah soal pergantian socket.

System diagram: Pooling Lives in the Client

HTTP/2 itu eksplisit dan butuh extra

Terhadap satu endpoint HTTP/2 publik yang tercatat di artefak:

Referensi resmi: RFC 9113: HTTP/2.

ClientNegosiasi
httpx.Client(http2=True)HTTP/2
httpx.Client(http2=False)HTTP/1.1
requestsHTTP/1.1, tidak ada flag

Anda butuh extra httpx[http2]. Saya sempat mengira pip install httpx biasa akan memberi client yang diam-diam menegosiasikan 1.1, lalu saya cek dulu sebelum menuliskannya:

ImportError: Using http2=True, but the 'h2' package is not installed.
Make sure to install httpx using `pip install httpx[http2]`.

Error-nya muncul saat konstruksi Client, sebelum satu request pun dikirim, dan pesannya langsung nunjukin solusinya. Ini versi gagal yang baik, dan saya sempat salah menduganya (http2-extra-missing.json).

Probe ini membuktikan negosiasi protokol berhasil pada endpoint tersebut. Ia tidak membuktikan manfaat kecepatan untuk scraping; tidak ada workload HTTP/1.1 yang dipasangkan untuk perbandingan.

Throughput fixture: sekuensial versus konkuren

Dua puluh request ke endpoint yang tidur 0,3 detik:

ModeWaktu totalSocket
Sync, satu Client6.138 s1
Async, satu AsyncClient0.357 s20

Run konkuren selesai dalam 0,357 detik, dibanding 6,138 detik untuk run sekuensial. Ia juga membuka dua puluh koneksi sementara client sinkron memakai ulang satu koneksi, jadi eksperimen ini mengubah model eksekusi dan tingkat concurrency efektif, bukan mengisolasi kecepatan library.

Itulah framing yang jujur untuk angka tersebut. Ini ukuran concurrency terhadap endpoint yang sengaja lambat, bukan ukuran murni httpx. Client apa pun yang punya async story yang jalan akan ada di kisaran yang sama, dan terhadap endpoint yang cepat, jaraknya akan mengecil.

Kasus charset yang tidak saya duga akan penting

Saya memprediksi bahwa respons dengan header yang bohong — charset=iso-8859-1 pada byte utf-8 — akan menghasilkan mojibake yang sama di keduanya. Ternyata benar. Keduanya mengembalikan Café Ubersetzung â naïve résumé padahal sumber aslinya Café Ubersetzung — naïve résumé.

Kasus yang tidak saya prediksi justru yang penting:

Responsrequests mendekodehttpx mendekode
charset=utf-8, byte utf-8benarbenar
charset=iso-8859-1, byte utf-8mojibakemojibake
tanpa charset sama sekalimojibakebenar

requests kembali ke ISO-8859-1 saat header tidak menyebutkan apa pun, sedangkan httpx default ke utf-8. Pada fixture tanpa charset, kedua client karena itu menghasilkan teks terdekode yang berbeda lewat .text; consumer yang memakai response.content tetap melihat byte asli yang sama.

Memori, karena ini murah untuk diukur

Peak RSS, /usr/bin/time -l, satu proses baru per sel:

Selrequestshttpx
Import saja36.0 MiB30.6 MiB
Import + satu GET35.8 MiB40.7 MiB

Ini snapshot satu proses, dan nilai requests yang satu GET-nya sedikit di bawah nilai import-only menunjukkan noise pengukuran. Dari sini tidak ada kesimpulan arah memori yang layak diambil; perlu sampel berulang dan rentang data.

Artinya saat Anda harus memilih

Mencari bug di kode requests yang sudah ada? Audit asumsi redirect, handler yang cuma menangkap requests.exceptions.Timeout padahal ingin menutup stall di tengah body, dan consumer .text yang menerima respons tanpa charset.

Migrasi mekanis ke httpx? Ubah namespace exception ke httpx.TimeoutException atau kelas fase yang lebih spesifik, tentukan apakah follow_redirects perlu diaktifkan, dan uji ulang asumsi decoding. Handler requests yang ada memang sudah melewatkan kasus mid-body yang saya demonstrasikan; migrasi tidak menciptakan bug spesifik itu.

Membuat sesuatu yang baru dan mengambil banyak URL? httpx layak dipilih saat Anda butuh AsyncClient dan timeout terpisah untuk connect, read, write, dan pool. Fase-fase itu memberi tahu di mana waktu tunggu terjadi — saat koneksi dibuat, body respons berjalan, upload request, atau pengambilan slot dari pool lokal — bukan kenapa host remote berperilaku seperti itu.

Membuat sesuatu yang kecil dan sinkron? requests tetap oke dan ada di mana-mana. Alasan pindah bukan speed.

Apa pun pilihan Anda, pakailah objek client, bukan fungsi level modul. Itu satu-satunya perubahan dalam daftar ini yang jelas menguntungkan di kedua library.

Di mana managed API cocok

Semua yang dibahas di atas adalah lapisan fetch, dan lapisan fetch justru bagian yang mudah. Tidak ada yang merender JavaScript, tidak ada yang menangani challenge anti-bot, dan tidak ada yang mengubah HTML menjadi baris data yang Anda inginkan.

Catatan penulis: Thunderbit adalah opsi managed kami untuk rendering dan ekstraksi dari URL. Ia tidak diuji dalam harness HTTP client ini. Pertimbangkan kategori ini hanya ketika masalah Anda adalah pengambilan halaman atau ekstraksi terstruktur — bukan semantik HTTP client.

Kalau Anda cuma mengambil halaman biasa dan memprosesnya sendiri, kedua client masih relevan. Layanan managed adalah keputusan build-versus-buy yang terpisah, bukan bukti untuk memilih salah satu library ini.

Untuk gambaran yang lebih luas, roundup API web scraping kami membahas opsi hosted, dan pilar open-source scraper membahas opsi self-hosted.

Coba Thunderbit untuk Ekstraksi Data Web

Kesimpulan

Untuk lapisan fetch Python baru yang butuh concurrency async, timeout spesifik per fase, dan fallback UTF-8, httpx jadi default saya dalam batasan yang diuji di sini. Requests tetap layak untuk kode sinkron yang matang ketika risiko migrasi lebih besar daripada manfaatnya. Proxy, kebijakan retry, fingerprint TLS, streaming, upload, dan variasi jaringan yang realistis tidak diuji, jadi ini bukan peringkat universal untuk semua client scraping.

Alasan untuk hati-hati ada pada default redirect, dan itu memang berbahaya justru karena merupakan keputusan desain yang bagus. Explicit memang lebih baik daripada implicit — sampai sesuatu yang implicit ternyata jadi penopang kode yang sudah terlanjur Anda kirim.

Scorecard yang dipra-registrasi berakhir dengan tiga prediksi benar, dua salah, dan satu prediksi yang belum lengkap. Koreksi yang paling berguna adalah kelas exception di tengah body; sisanya sebaiknya ditentukan dari perilaku yang diamati, bukan dari narasi scorecard.

Coba Thunderbit untuk Ekstraksi Data Web Get Started Free

FAQ

Apakah benar httpx tidak mengikuti redirect? Tidak secara default. Server cuma menghitung satu request untuk rantai empat hop, dan respons kembali sebagai 302. Tambahkan follow_redirects=True per pemanggilan, atau set sekali di Client. Ini terdokumentasi dan memang disengaja; tetap saja ini yang paling mungkin diam-diam merusak migrasi, karena gagalnya berupa parse kosong, bukan exception.

Apakah except requests.exceptions.Timeout memang tidak cukup? Tidak untuk server yang berhenti setelah mengirim header. Kasus itu memunculkan ConnectionError, yang bukan turunan Timeout, jadi guard-nya meleset — dibuktikan langsung, bukan disimpulkan. Tangkap requests.exceptions.RequestException kalau Anda ingin keduanya, dan terima bahwa Anda juga akan menangkap hal-hal yang bukan timeout.

Apakah httpx lebih cepat daripada requests? Tidak secara berarti, kalau cuma satu request pada satu waktu — memang bukan untuk itu. Angka 17,2× pada pengujian ini adalah ukuran dua puluh request konkuren terhadap endpoint 0,3 detik, jadi itu ukuran concurrency. Kalau workload Anda sekuensial, jangan berharap ada percepatan dan pilih berdasarkan default-nya.

Apakah saya perlu extra http2? Hanya kalau Anda ingin HTTP/2 — dan kalau Anda mengaktifkan http2=True tanpa extra itu, httpx akan melempar ImportError saat Anda membangun Client, dengan pesan yang menyuruh Anda menginstal httpx[http2]. Tidak ada downgrade diam-diam yang perlu dikhawatirkan. Saya sempat mengira ada, lalu memeriksanya sebelum menulis.

Apa yang tidak diuji di sini? Perilaku proxy, yang sangat penting untuk scraping dan butuh harness tersendiri. Retry — httpx tidak menyertakan logika retry, sedangkan requests mendapatkannya dari urllib3, jadi perbandingan yang adil sebenarnya adalah perbandingan dua library retry. TLS fingerprinting, yaitu sumbu yang benar-benar dilihat sistem anti-bot dan yang tidak ditangani oleh kedua library. Streaming dan file upload. Dan semuanya di sini memakai satu mesin, satu versi Python, dan localhost untuk enam dari delapan probe — angka latensi dari fixture server adalah ukuran desain, bukan jaringan Anda.

Ke
Ke
CTO di Thunderbit | Senior Data Scientist & Expert ML Dengan pengalaman hampir satu dekade di bidang machine learning dan data science, Ke Shen adalah lulusan Columbia University dan mantan Senior Data Scientist di Walmart Labs. Dengan keahlian mendalam yang diakui sejawat dalam Python, R, Java, dan Statistik, ia membagikan wawasan teruji lapangan tentang bagaimana membawa algoritma AI yang kompleks dari teori ke arsitektur siap produksi.
Daftar isi
Thunderbit · Agen data web AI

Ekstrak data dari halaman apa pun dalam 1 klik

Dipercaya 250.000+ pengguna
tersedia paket gratis
Dari halaman web ke spreadsheet
Jelaskan kebutuhanmu — Agen AI Thunderbit akan men-scrape lalu mengekspor ke Excel, Google Sheets, Airtable, atau Notion. Gratis untuk memulai.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week