Ett avbrott mitt i kroppen glider förbi en vanlig timeout-hanterare i requests

Senast uppdaterad August 17, 2026
Ett avbrott mitt i kroppen glider förbi en vanlig timeout-hanterare i requests
AI-sammanfattning
Hittar du buggar i befintlig requests-kod? Granska antaganden om redirects, hanterare som bara fångar requests.exceptions.Timeout men tror att de täcker ett stopp mitt i body, och .text-användare som får svar utan charset. Migrerar du mekaniskt till httpx? Byt undantagsnamnrymder till httpx.TimeoutException eller de snävare fas-klasserna, bestäm om followredirects ska aktiveras och testa om avkodningsantaganden igen. Den befintliga requests-hanteraren missar redan det visade fallet med stopp mitt i body; migrationen skapar inte just den buggen. Bygger du något nytt som hämtar många URL:er? httpx är ett bra alternativ när du behöver AsyncClient och separata timeoutar för connect, read, write och pool.

Skriv den skyddskod som alla använder:

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

Peka den mot en server som skickar statusraden och headers direkt och sedan låser sig innan body kommer. Läsningen går på timeout. Skyddet triggas inte. Undantaget som kommer ut är ConnectionError, och ConnectionError är inte en underklass till Timeout.

Samma typ av stopp mot httpx ger ReadTimeout, som är en TimeoutException, och det fångar motsvarande skyddskod.

Jag började leta efter hur httpx skiljer sig från requests på sätt som ställer till det för web scraping, och tänkte att jag skulle skriva om async. Det visade sig vara det minst intressanta på listan.

Vad jag testade och hur

Åtta kontroller mot en lokal testserver, eftersom en klient som själv säger vad den gjorde inte är bevis för vad den faktiskt gjorde. Servern räknar TCP-anslutningar — ökar med ett för varje accepterat socket, innan någon request line ens tolkas — och sökvägar som faktiskt hämtats. Återanvändning av anslutningar och redirect-hantering är båda påståenden om trafiken på nätet, och det är där de kan verifieras.

httpx 0.28.1 med http2-extrat, requests 2.34.2, Python 3.14.2, macOS arm64. Båda kördes i samma nyinstallerade virtualenv så att ingen av dem ärvde något från den andra. Rådata: httpx-probes.json.

Sex förutsägelser lades in i testharnessen innan första körningen och låg kvar efteråt. Tre stämde, två var fel, en var rätt för det jag tänkte på men missade det som faktiskt spelade roll. prediction-scorecard.json innehåller bokföringen.

Standardinställningar som ändrar beteende under dig

Measured results chart: Defaults that differ between clients

Beteenderequests 2.34.2httpx 0.28.1
Följer redirects som standardjanej
Funktionsanrop på modulnivå återanvänder anslutningarnejnej
Stopp innan headersReadTimeoutReadTimeout
Stopp mitt i bodyConnectionErrorReadTimeout
Ingen charset angivenISO-8859-1utf-8
HTTP/2inte tillgängligtvalbart, fungerar
Separata timeoutar för connect/read/write/poolnejja

Redirects, återanvändning av sockets, undantagsresultat, avkodning och protokollförhandling observerades i testkörningar. Formen på timeout-API:t och att requests saknar en HTTP/2-flagga är observationer om API-funktioner. httpx-probes.json.

Tre av de här raderna kommer att ändra vad din kod gör den dag du byter, utan att säga till.

Redirects: avstängda som standard, och servern bevisar det

En redirect-kedja med fyra hopp som slutar på /ok:

KlientServern sågReturstatus
requests5 requests200
httpx1 request302
httpx, follow_redirects=True5 requests200

Femman är fyra hopp plus målet. Min förutsägelse sa fyra, vilket var huvudräkning jag inte kontrollerade; riktningen i påståendet var det viktiga, och räknandet rättas här i stället för att smygas in i texten.

Det här är dokumenterat beteende i httpx och ett fullt rimligt designval — en redirect är något anroparen kanske vill veta om. Men det är också det vanligaste sättet en migration går sönder utan att något kastas. Din kod får en 302, response.text är tom, din parser hittar inga rader, och loggarna säger 200 OK… fast egentligen står det 302, och ingen tittade på statuskoden eftersom det aldrig behövdes med requests.

Timeout-fyndet, som är det jag hade bakvänt

Jag förutsåg att httpx skulle namnge den fas som misslyckades och att requests skulle slå ihop båda i en klass. Det är tvärtom.

Officiell referens: Requests timeout documentation.

System diagram: Where the Timeout Lands

Officiell referens: HTTPX timeout documentation.

Stopprequestshttpx
Före statusradenReadTimeoutReadTimeout
Mitt i body, efter att headers skickatsConnectionErrorReadTimeout

httpx ger samma, korrekta namn till båda. requests skiljer dem åt — och delar upp dem över just den gräns som retry-kod brukar byggas mot.

Konsekvensen är inte en slutsats från klasshierarkin. Jag körde skyddskoden:

Stoppexcept requests.exceptions.Timeoutexcept httpx.TimeoutException
Före statusradenfångasfångas
Mitt i bodyglider förbi som ConnectionErrorfångas

timeout-retry-guard.json. requests.exceptions.ConnectionError är inte en underklass till requests.exceptions.Timeout; httpx.ReadTimeout är en underklass till httpx.TimeoutException.

Meddelandet från requests säger Read timed out. inne i en ConnectionError. Biblioteket vet vad som hände. Det berättar bara inte det för typsystemet, och det är typsystemet som din except-sats frågar.

Det uppmätta fallet är specifikt: headers kommer fram, och sedan upphör body-att ta emot data tillräckligt länge för att read timeout ska passeras. En respons som fortsätter leverera chunkar inom timeoutfönstret, inklusive en avsiktlig ström, kan bete sig annorlunda och testades inte här.

Connection pooling: klient-API:t är hela skillnaden

Tio GET-anrop, fyra sätt, sockets räknade på serversidan:

SättÖppnade sockets
httpx.get() × 1010
requests.get() × 1010
httpx.Client()1
requests.Session()1

Samma resultat, och värt att säga eftersom det är den vanligaste föreställningen om just det här paret — att httpx poolar och requests inte gör det. Ingen av dem poolar på modulnivå. Båda poolar via sitt klientobjekt. Om du idag kör requests.get() i en loop, så ändrar httpx.get() i en loop ingenting i din socket-churn.

System diagram: Pooling Lives in the Client

HTTP/2 är explicit och kräver extrat

Mot en publik HTTP/2-endpoint som loggades i testartefakten:

Officiell referens: RFC 9113: HTTP/2.

KlientFörhandlat protokoll
httpx.Client(http2=True)HTTP/2
httpx.Client(http2=False)HTTP/1.1
requestsHTTP/1.1, ingen flagga finns

Du behöver httpx[http2]-extrat. Jag antog att ett vanligt pip install httpx skulle ge en klient som tyst förhandlar 1.1, och kollade upp det innan jag skrev ner det:

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

Det kastas vid skapandet av Client, innan en enda request skickas, och meddelandet talar om hur det fixas. Det är den bra versionen av det här felet, och jag hade det bakvänt (http2-extra-missing.json).

Den här kontrollen visar att protokollförhandlingen lyckas mot just den endpointen. Den visar inte någon vinst i scraping-hastighet; inget matchande HTTP/1.1-arbetslaster testades.

Sekventiell kontra samtidig throughput mot testservern

Tjugo requests mot en endpoint som sover i 0,3 s:

LägeVäggklocktidSockets
Synkront, en Client6.138 s1
Asynkront, en AsyncClient0.357 s20

Den samtidiga körningen blev klar på 0,357 sekunder jämfört med 6,138 sekunder för den sekventiella körningen. Den öppnade också tjugo anslutningar medan den synkrona klienten återanvände en, så experimentet ändrar exekveringsmodell och faktisk samtidighet snarare än att isolera bibliotekets råa hastighet.

Det är den ärliga tolkningen av siffran. Det är en mätning av samtidighet mot en medvetet långsam endpoint, inte en mätning av httpx. Vilken klient som helst med fungerande async-stöd hamnar i samma härad, och mot en snabb endpoint krymper gapet.

Charset-fallet jag inte tänkte förutsäga

Jag förutsåg att ett svar där headern ljuger — charset=iso-8859-1 på utf-8-bytes — skulle ge samma mojibake i båda. Det gör det. Båda returnerar Café Ubersetzung â naïve résumé där källan säger Café Ubersetzung — naïve résumé.

Fallet jag inte förutsåg är det som faktiskt spelar roll:

Svarrequests avkodarhttpx avkodar
charset=utf-8, utf-8-byteskorrektkorrekt
charset=iso-8859-1, utf-8-bytesmojibakemojibake
ingen charset allsmojibakekorrekt

requests faller tillbaka till ISO-8859-1 när headern inte säger något, medan httpx använder utf-8 som standard. I testfallet utan charset gav klienterna därför olika avkodad text via .text; konsumenter som använder response.content behåller samma ursprungliga bytes.

Minne, eftersom det är billigt att mäta

Peak RSS, /usr/bin/time -l, en ny process per cell:

Cellrequestshttpx
Endast import36.0 MiB30.6 MiB
Import plus en GET35.8 MiB40.7 MiB

Det här är ögonblicksbilder från en enstaka process, och att requests en-GET-värde ligger lite under import-only-värdet visar bara att mätbrus finns. De säger inget säkert om riktningen; det skulle krävas flera sampel och intervall.

Vad det här betyder när du väljer

Hittar du buggar i befintlig requests-kod? Granska antaganden om redirects, hanterare som bara fångar requests.exceptions.Timeout men tror att de täcker ett stopp mitt i body, och .text-användare som får svar utan charset.

Migrerar du mekaniskt till httpx? Byt undantagsnamnrymder till httpx.TimeoutException eller de snävare fas-klasserna, bestäm om follow_redirects ska aktiveras och testa om avkodningsantaganden igen. Den befintliga requests-hanteraren missar redan det visade fallet med stopp mitt i body; migrationen skapar inte just den buggen.

Bygger du något nytt som hämtar många URL:er? httpx är ett bra alternativ när du behöver AsyncClient och separata timeoutar för connect, read, write och pool. De faserna talar om var väntan skedde — anslutningsskapande, framsteg i response-body, uppladdning av request eller lokal pool-anskaffning — inte varför en fjärrserver betedde sig så.

Bygger du något litet och synkront? requests fungerar fint och finns överallt. Skälet att byta är inte fart.

Oavsett vad du väljer, använd klientobjektet i stället för funktionen på modulnivå. Det är den enda förändringen på den här listan som är en tydlig vinst i båda biblioteken.

Var en managed API-lösning passar in

Allt ovan handlar om fetch-lagret, och fetch-lagret är den enkla delen. Inget av det renderar JavaScript, inget hanterar en anti-bot-utmaning, och inget förvandlar HTML till de rader du egentligen ville ha.

Författarnotis: Thunderbit är vårt managed-alternativ för rendering och extrahering direkt från URL. Det testades inte i det här HTTP-klientharneset. Se den kategorin bara när sidinhämtning eller strukturerad extrahering — inte HTTP-klientsemantik — är problemet du försöker ta bort.

Om du hämtar vanliga sidor och parser dem själv, är båda klienterna fortfarande relevanta. En managed tjänst är ett separat build-vs-buy-beslut, inte ett argument för valet mellan de här biblioteken.

För en bredare överblick täcker vår web scraping API roundup de hostade alternativen och open-source scraper pillar de self-hostade.

Testa Thunderbit för extrahering av webdata

Slutsats

För ett nytt Python-fetchlager som behöver async-samtidighet, fas-specifika timeoutar och en utf-8-fallback är httpx mitt förstaval under de förutsättningar som testats här. Requests är fortfarande fullt gångbart för mogen synkron kod där migrationsrisk väger tyngre än de fördelarna. Proxyer, retry-policyer, TLS-fingerprinting, streaming, uppladdningar och verklig nätverksvariation testades inte, så det här är ingen universell ranking av scraping-klienter.

Skälet att vara försiktig är redirect-standardbeteendet, och det är en verklig risk just för att det är ett bra designbeslut. Explicit är bättre än implicit ända tills det implicita var bärande i kod du redan skickat i produktion.

Den förregistrerade scorecarden slutade med tre rätta förutsägelser, två fel och en ofullständig. Den användbara korrigeringen var undantagsklassen för stopp mitt i body; resten av beslutet bör komma från observerat beteende snarare än från berättelsen i scorecarden.

Testa Thunderbit för extrahering av webdata Get Started Free

Vanliga frågor

Följer httpx verkligen inte redirects? Inte som standard, nej. Servern räknade ett enda request för en kedja med fyra hopp, och svaret kom tillbaka som en 302. Skicka follow_redirects=True per anrop, eller sätt det en gång på Client. Det här är dokumenterat och avsiktligt; det är ändå det som sannolikt går sönder tyst vid en migration, eftersom felet blir en tom parsering i stället för ett undantag.

Räcker verkligen inte except requests.exceptions.Timeout? Inte för en server som stannar efter att headers skickats. Det fallet ger ConnectionError, som inte är en underklass till Timeout, så skyddet missar det — visat direkt, inte härlett. Fånga requests.exceptions.RequestException om du vill täcka båda, men acceptera att du då också fångar sådant som inte är timeoutar.

Är httpx snabbare än requests? Inte på något meningsfullt sätt för ett request i taget — det är inte vad det är till för. 17,2× i det här testet är en mätning av tjugo samtidiga requests mot en endpoint som sover i 0,3 s, alltså en mätning av samtidighet. Om din arbetslast är sekventiell, räkna inte med någon fartvinst och välj utifrån standardbeteendena i stället.

Behöver jag http2-extrat? Bara om du vill ha HTTP/2 — och om du sätter http2=True utan det, kastar httpx ImportError när du skapar Client, med ett meddelande som säger att du ska installera httpx[http2]. Ingen tyst nedgradering att oroa sig för. Jag förväntade mig en sådan och kollade i stället för att skriva ned den.

Vad testades inte här? Proxy-beteende, vilket spelar stor roll för web scraping och behöver ett eget testupplägg. Retries — httpx levererar ingen retry-logik och requests får sin från urllib3, så en rättvis jämförelse är egentligen en jämförelse mellan två retry-bibliotek. TLS-fingerprinting, som är den axel anti-bot-system faktiskt tittar på och som inget av biblioteken adresserar. Streaming och filuppladdningar. Och allt här är en maskin, en Python-version och localhost för sex av de åtta kontrollerna — latenssiffror från en testserver mäter designen, inte ditt nätverk.

Ke
Ke
CTO på Thunderbit | Senior data scientist & ML-expert Med nästan ett decenniums erfarenhet inom maskininlärning och datavetenskap är Ke Shen alumn från Columbia University och tidigare Senior Data Scientist på Walmart Labs. Med djup, av branschen erkänd expertis inom Python, R, Java och statistik delar han väl beprövade insikter om hur man förvandlar komplexa AI-algoritmer från teori till produktionsklar arkitektur.
Topics
Web Scraping ToolsAI Web Scraper
Innehållsförteckning
Thunderbit · AI-agent för webbdata

Extrahera data från vilken sida som helst på 1 klick

Betrodd av över 250 000 användare
gratis plan tillgänglig
Från webbsida till kalkylark
Beskriv vad du behöver — Thunderbits AI-agent samlar in det och exporterar till Excel, Google Sheets, Airtable eller Notion. Gratis att börja med.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week