Jak středně dlouhé zaseknutí v průběhu odpovědi uniklo běžnému timeout handleru v requests

Naposledy aktualizováno August 17, 2026
Jak středně dlouhé zaseknutí v průběhu odpovědi uniklo běžnému timeout handleru v requests
AI shrnutí
Hledáte chyby v existujícím requests kódu? Zkontrolujte předpoklady kolem redirectů, handlery, které zachytí jen requests.exceptions.Timeout, ale čekají, že pokryjí i zaseknutí uprostřed těla, a spotřebitele .text, kteří dostávají odpovědi bez charsetu. Migrace mechanicky na httpx? Přepište namespaces výjimek na httpx.TimeoutException nebo na užší třídní varianty, rozhodněte se, jestli zapnout follow_redirects, a znovu otestujte předpoklady kolem dekódování. Současný requests handler už ten demonstrovaný mid-body případ stejně nechytil; migrace tenhle konkrétní bug nevytvoří. Píšete něco nového, co tahá spoustu URL? httpx dává smysl, když potřebujete AsyncClient a samostatné timeouty pro connect, read, write a pool.

Napište si tenhle guard, který používá skoro každý:

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

Nasměrujte ho na server, který pošle stavový řádek a hlavičky okamžitě, ale pak se zasekne ještě před tělem odpovědi. Čtení vyprší. Guard se ale nespustí. Ven se dostane ConnectionError, a ten není potomkem Timeout.

Stejné zaseknutí v httpx vyhodí ReadTimeout, což je TimeoutException, a ten ekvivalentní guard už zachytí.

Zajímalo mě, v čem se httpx liší od requests právě v těch bodech, které dělají scraperům problémy. Původně jsem čekal, že budu psát o async. Nakonec se ukázalo, že to je na celém seznamu skoro ta nejméně zajímavá věc.

Co jsem testoval a jak

Osm probeů proti lokálnímu testovacímu serveru, protože to, co klient tvrdí, není důkaz o tom, co skutečně udělal. Server počítá TCP připojení — přičte jedno za každý přijatý socket, ještě předtím, než se parsuje první request line — a skutečně načtené cesty. Znovupoužití spojení i následování redirectů jsou tvrzení o tom, co jde po drátě, a právě na drátu se to dá ověřit.

httpx 0.28.1 s extra http2, requests 2.34.2, Python 3.14.2, macOS arm64. Oba klienti běželi v čerstvém virtualenvu, aby si nezdědili žádnou konfiguraci ani závislosti jeden od druhého. Surový výstup: httpx-probes.json.

Do harnessu šlo ještě před prvním během šest predikcí a po něm zůstaly beze změny. Tři vyšly, dvě byly špatně, jedna seděla na scénář, který jsem měl v hlavě, ale minula ten důležitý. Účetnictví je v prediction-scorecard.json.

Výchozí nastavení, které vás může překvapit

Measured results chart: Defaults that differ between clients

Chovánírequests 2.34.2httpx 0.28.1
Redirecty sleduje ve výchozím stavuanone
Volání na úrovni modulu znovu používá spojenínene
Zaseknutí před hlavičkamiReadTimeoutReadTimeout
Zaseknutí uprostřed tělaConnectionErrorReadTimeout
Když není uveden charsetISO-8859-1utf-8
HTTP/2není k dispozicivolitelně, funguje
Samostatné timeouty pro connect/read/write/poolneano

Redirecty, znovupoužití socketů, výsledky výjimek, dekódování i vyjednání protokolu byly ověřeny v probech. Tvar timeout API a absence přepínače pro HTTP/2 v requests jsou pozorování o možnostech API. httpx-probes.json.

Tři z těchto řádků změní, co vaše aplikace dělá v den migrace, aniž by cokoli vyhodily.

Redirecty: ve výchozím stavu vypnuté a server to dokáže potvrdit

Řetězec čtyř redirectů končící na /ok:

KlientCo server vidělVrácený status
requests5 requestů200
httpx1 request302
httpx, follow_redirects=True5 requestů200

To pět je čtyři skoky plus cílová adresa. Moje predikce říkala čtyři, což byla aritmetika, kterou jsem si neověřil; směr tvrzení seděl a počet je tady opravený, ne potichu přepsaný v textu.

Tohle je v httpx zdokumentované chování a je to rozumné rozhodnutí — redirect je něco, o čem chce caller často vědět. Zároveň je to nejpravděpodobnější způsob, jak migrace rozbije běh bez jediné výjimky. Kód dostane 302, response.text je prázdný, parser nenajde žádné řádky a logy přesto působí jako „200 OK“… až na to, že ve skutečnosti hlásí 302, a nikdo nekontroloval status code, protože v requests na něj předtím nebylo potřeba koukat.

Zjištění kolem timeoutu, které jsem měl opačně

Předpověděl jsem, že httpx bude pojmenovávat přesně tu fázi, která selhala, a requests obě situace slije do jedné třídy. Je to přesně naopak.

Oficiální reference: Requests timeout documentation.

System diagram: Where the Timeout Lands

Oficiální reference: HTTPX timeout documentation.

Zaseknutírequestshttpx
Před stavovým řádkemReadTimeoutReadTimeout
Uprostřed těla, po odeslání hlavičekConnectionErrorReadTimeout

httpx obě situace pojmenuje stejně a správně. requests je rozlišuje — a rozděluje je přesně v místě, na které bývá napsaný retry kód.

Důsledek neplyne jen z hierarchie tříd. Ten guard jsem skutečně spustil:

Zaseknutíexcept requests.exceptions.Timeoutexcept httpx.TimeoutException
Před stavovým řádkemzachytízachytí
Uprostřed tělaunikne jako ConnectionErrorzachytí

timeout-retry-guard.json. requests.exceptions.ConnectionError není potomkem requests.exceptions.Timeout; httpx.ReadTimeout je potomkem httpx.TimeoutException.

Chybová hláška requests uvnitř ConnectionError říká Read timed out.. Knihovna tedy ví, co se stalo. Jen to nepropíše do typového systému, a právě na ten se váš except dívá.

Měřený případ je konkrétní: hlavičky dorazí, potom se tok těla odpovědi zastaví dost dlouho na překročení read timeoutu. Odpověď, která dál posílá chunky včas, včetně záměrného streamu, se může chovat jinak a tady testovaná nebyla.

Connection pooling: rozdíl je v API klienta

Deset GETů, čtyři způsoby, sockety počítané na straně serveru:

JakOtevřených socketů
httpx.get() × 1010
requests.get() × 1010
httpx.Client()1
requests.Session()1

Stejné, a je dobré to výslovně říct, protože je to nejčastější kus lidové moudrosti o téhle dvojici — že httpx pooluje a requests ne. Na úrovni modulu nepooluje ani jeden. Oba poolují přes objekt klienta. Pokud dnes voláte requests.get() ve smyčce, přechod na httpx.get() ve smyčce nic na počtu otevřených spojení nezmění.

System diagram: Pooling Lives in the Client

HTTP/2 je explicitní a vyžaduje extra balíček

Proti jednomu veřejnému HTTP/2 endpointu zaznamenanému v artefaktu:

Oficiální reference: RFC 9113: HTTP/2.

KlientDojednaný protokol
httpx.Client(http2=True)HTTP/2
httpx.Client(http2=False)HTTP/1.1
requestsHTTP/1.1, žádný přepínač neexistuje

Potřebujete extra httpx[http2]. Předpokládal jsem, že obyčejný pip install httpx nechá klienta tiše spadnout na HTTP/1.1, a šel jsem to před napsáním textu ověřit:

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

Výjimka padne už při vytvoření Client, ještě před jediným requestem, a hláška rovnou řekne, co opravit. To je ta lepší varianta téhle chyby, a já měl i tady představu opačně (http2-extra-missing.json).

Tenhle probe potvrzuje, že na daném endpointu se protokol skutečně dohodl správně. Nedokazuje, že je scraping rychlejší; odpovídající workload přes HTTP/1.1 jsem netestoval.

Sekvenční versus souběžná propustnost fixture

Dvacet requestů na endpoint, který spí 0.3 s:

RežimČas na hodináchSockety
Sync, jeden Client6.138 s1
Async, jeden AsyncClient0.357 s20

Souběžný běh skončil za 0.357 sekundy oproti 6.138 sekundám u sekvenčního běhu. Zároveň otevřel dvacet spojení, zatímco synchronní klient znovu použil jedno, takže experiment mění model vykonávání a efektivní souběžnost, místo aby izoloval rychlost knihovny.

To je k číslu poctivý popis. Je to měření souběžnosti proti záměrně pomalému endpointu, ne měření samotného httpx. Jakýkoli klient s funkční async podporou dopadne v podobné kategorii a proti rychlému endpointu se rozdíl smrskne.

Charset případ, který mě nenapadlo předpovědět

Předpověděl jsem, že odpověď, jejíž hlavička lže — charset=iso-8859-1 na utf-8 bajtech — bude v obou knihovnách dávat stejný nesmyslný text. A opravdu dává. Obě vrátí Café Ubersetzung â naïve résumé, zatímco zdroj je Café Ubersetzung — naïve résumé.

Nepředpovězený případ je ten důležitý:

Odpověďrequests dekódujehttpx dekóduje
charset=utf-8, utf-8 bajtysprávněsprávně
charset=iso-8859-1, utf-8 bajtynesmyslněnesmyslně
žádný charsetnesmyslněsprávně

requests bez uvedeného charsetu padá zpět na ISO-8859-1, zatímco httpx defaultně používá utf-8. V testu s chybějícím charsetem tedy klienti přes .text vrátili odlišně dekódovaný text; kdo používá response.content, dostane stále stejné původní bajtové data.

Paměť, protože se dá snadno změřit

Peak RSS, /usr/bin/time -l, pro každou buňku jeden nový proces:

Buňkarequestshttpx
Jen import36.0 MiB30.6 MiB
Import + jeden GET35.8 MiB40.7 MiB

Jsou to jednorázové snímky z jednoho procesu a hodnota requests po jednom GETu, která je lehce pod hodnotou jen importu, ukazuje šum měření. Z toho nejde vyvodit žádný směr ohledně paměti; bylo by potřeba víc vzorků a rozsahů.

Co to znamená při výběru

Hledáte chyby v existujícím requests kódu? Zkontrolujte předpoklady kolem redirectů, handlery, které zachytí jen requests.exceptions.Timeout, ale čekají, že pokryjí i zaseknutí uprostřed těla, a spotřebitele .text, kteří dostávají odpovědi bez charsetu.

Migrace mechanicky na httpx? Přepište namespaces výjimek na httpx.TimeoutException nebo na užší třídní varianty pro konkrétní fázi, rozhodněte se, jestli zapnout follow_redirects, a znovu otestujte předpoklady kolem dekódování. Současný requests handler už ten demonstrovaný mid-body případ stejně nechytil; migrace tenhle konkrétní bug nevytvoří.

Píšete něco nového, co tahá spoustu URL? httpx dává smysl, když potřebujete AsyncClient a samostatné timeouty pro connect, read, write a pool. Tyto fáze vám řeknou, kde se čekalo — při navázání spojení, při postupu těla odpovědi, při uploadu requestu nebo při čekání na lokální pool — ne proč se vzdálený server choval tak, jak se choval.

Píšete něco malého a synchronního? requests je v pohodě a je všude. Důvodem pro změnu není rychlost.

Ať už vyberete cokoli, používejte objekt klienta, ne funkci na úrovni modulu. To je jediná změna v celém seznamu, která je v obou knihovnách čisté plus.

Kam zapadá managed API

Všechno výše je fetch vrstva, a ta je ta snadná část. Nic z toho nespustí JavaScript, nic z toho nevyřeší anti-bot challenge a nic z toho nepřetvoří HTML do řádků, které vlastně chcete.

Poznámka autora: Thunderbit je naše spravovaná varianta pro rendering a extrakci přímo z URL. V tomhle testovacím harnessu HTTP klientů jsme ji netestovali. Uvažujte o téhle kategorii až ve chvíli, kdy problémem je získání stránky nebo strukturovaná extrakce — ne samotná semantika HTTP klienta.

Pokud taháte běžné stránky a parsujete si je sami, do hry stále vstupují oba klienti. Managed služba je samostatné rozhodnutí build vs. buy, ne důkaz pro výběr mezi těmito knihovnami.

Pro širší přehled náš přehled web scraping API pokrývá hostované možnosti a pillar o open-source scrapech zase self-hosted varianty.

Vyzkoušet Thunderbit pro extrakci webových dat

Verdikt

Pro novou Python fetch vrstvu, která potřebuje async souběžnost, timeouty rozlišené podle fáze a utf-8 fallback, je pro mě httpx výchozí volba v rámci testovaných podmínek. Requests zůstává použitelné pro zralý synchronní kód, kde riziko migrace převáží nad těmito výhodami. Proxy, retry politika, TLS fingerprinting, streaming, uploady ani realistické síťové odchylky jsem netestoval, takže tohle není univerzální žebříček scraper klientů.

Důvod k opatrnosti je výchozí chování u redirectů, a je to skutečné riziko právě proto, že je to dobré designové rozhodnutí. Explicitní je lepší než implicitní — až do chvíle, kdy ta implicitní věc držela celý kód, který už jste dávno nasadili.

Předregistrovaný scorecard skončil třemi správnými predikcemi, dvěma chybnými a jednou neúplnou. Nejužitečnější oprava se týká výjimky uprostřed těla; zbytek rozhodnutí by měl vycházet z pozorovaného chování, ne z příběhu kolem scorecardu.

Vyzkoušet Thunderbit pro extrakci webových dat Get Started Free

Časté dotazy

Opravdu httpx ve výchozím stavu nenásleduje redirecty? Ne, ve výchozím stavu ne. Server u čtyřskokového řetězce viděl jen jeden request a odpověď přišla jako 302. follow_redirects=True můžete nastavit pro jednotlivé volání nebo jednou na Client. Je to zdokumentované a záměrné; při migraci je to přesto nejpravděpodobnější věc, která se potichu rozbije, protože selhání vypadá jako prázdný parser, ne jako výjimka.

Nestačí opravdu except requests.exceptions.Timeout? Ne u serveru, který se zasekne až po odeslání hlaviček. V tom případě padne ConnectionError, což není potomek Timeout, takže guard to mine — a to přímo demonstrovaně, ne odvozením. Pokud chcete zachytit obojí, použijte requests.exceptions.RequestException, ale počítejte s tím, že tím zachytíte i věci, které timeouty nejsou.

Je httpx rychlejší než requests? Ne nijak zásadně, když jde o jeden request po druhém — k tomu není určený. Násobek 17.2× v tomhle testu je měření dvaceti souběžných requestů proti endpointu, který spí 0.3 s, tedy měření souběžnosti. Pokud je váš workload sekvenční, nečekejte zrychlení a vybírejte raději podle výchozích hodnot.

Potřebuji extra http2? Jen pokud chcete HTTP/2 — a když nastavíte http2=True bez něj, httpx při vytváření Client vyhodí ImportError s instrukcí, abyste nainstalovali httpx[http2]. Žádný tichý downgrade nehrozí. Já čekal něco jiného a radši to ověřil.

Co se tady netestovalo? Chování proxy, a to je pro scraping hodně důležité téma, které potřebuje vlastní harness. Retry — httpx žádnou retry logiku nepřináší a requests ji bere z urllib3, takže férové srovnání je vlastně i srovnáním dvou retry knihoven. TLS fingerprinting, což je osa, na kterou anti-bot systémy skutečně koukají, a kterou neřeší ani jedna knihovna. Streaming a uploady souborů. A všechno tohle běželo na jednom stroji, jedné verzi Pythonu a u šesti z osmi probeů na localhostu — latence z fixture serveru vypovídá o designu, ne o vaší síti.

Ke
Ke
CTO ve Thunderbit | Senior Data Scientist a expert na ML S téměř desetiletou zkušeností v oblasti strojového učení a datové vědy je Ke Shen absolventem Kolumbijské univerzity a bývalým Senior Data Scientist ve Walmart Labs. Díky hlubokým odborným znalostem v Pythonu, R, Javě a statistice, uznávaným i mezi kolegy, sdílí ověřené poznatky o tom, jak převést složité AI algoritmy od teorie až k produkční architektuře.
Obsah
Thunderbit · AI agent pro webová data

Extrahuj data z libovolné stránky za 1 kliknutí

Důvěřuje mu více než 250 000 uživatelů
k dispozici bezplatný plán
Z webové stránky do tabulky
Popiš, co potřebuješ — AI agent Thunderbit to nasbírá a exportuje do Excelu, Google Sheets, Airtable nebo Notion. Začni zdarma.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week