Wie man Timeout-Fehler bei der Arbeit über einen Proxy behebt
Die Anfrage hängt fest, das Skript stürzt mit dem Fehler TimeoutError ab, die Daten werden nicht empfangen. Kommt dir das bekannt vor? Timeout-Fehler über einen Proxy sind eines der häufigsten Probleme beim Scraping und bei der Automatisierung. Lassen Sie uns die Ursachen analysieren und konkrete Lösungen geben.
Warum treten Timeout-Fehler auf
Ein Timeout ist nicht ein Problem, sondern ein Symptom. Bevor man behandelt, muss man die Ursache verstehen:
Langsamer Proxy-Server. Ein überladener Server oder ein geografisch entfernter Proxy fügt jeder Anfrage eine Verzögerung hinzu. Wenn Ihr Timeout 10 Sekunden beträgt, der Proxy aber 12 Sekunden antwortet – Fehler.
Blockierung durch die Zielwebsite. Die Website kann verdächtige Anfragen absichtlich „aufhängen", anstatt sie explizit abzulehnen. Dies ist eine Taktik gegen Bots – die Verbindung unendlich offen halten.
DNS-Probleme. Der Proxy muss die Domain auflösen. Wenn der DNS-Server des Proxys langsam oder nicht erreichbar ist – hängt die Anfrage in der Verbindungsphase fest.
Falsche Timeout-Konfiguration. Ein allgemeiner Timeout für alles – ein häufiger Fehler. Connect-Timeout und Read-Timeout sind unterschiedliche Dinge und müssen separat konfiguriert werden.
Netzwerkprobleme. Paketverlust, instabile Proxy-Verbindung, Routing-Probleme – all dies führt zu Timeouts.
Timeout-Typen und deren Konfiguration
Die meisten HTTP-Bibliotheken unterstützen mehrere Timeout-Typen. Das Verständnis der Unterschiede zwischen ihnen ist der Schlüssel zur richtigen Konfiguration.
Connect-Timeout
Zeit zum Aufbau einer TCP-Verbindung mit dem Proxy und dem Zielserver. Wenn der Proxy nicht erreichbar ist oder der Server nicht antwortet – dieser Timeout wird ausgelöst. Empfohlener Wert: 5-10 Sekunden.
Read-Timeout
Wartezeit auf Daten nach Verbindungsaufbau. Der Server hat sich verbunden, schweigt aber – Read-Timeout wird ausgelöst. Für normale Seiten: 15-30 Sekunden. Für schwere APIs: 60+ Sekunden.
Gesamt-Timeout
Gesamtzeit für die gesamte Anfrage von Anfang bis Ende. Versicherung gegen hängende Verbindungen. Normalerweise: Connect + Read + Reserve.
Beispiel der Konfiguration in Python mit der Bibliothek requests:
import requests
proxies = {
"http": "http://user:pass@proxy.example.com:8080",
"https": "http://user:pass@proxy.example.com:8080"
}
# Tupel: (connect_timeout, read_timeout)
timeout = (10, 30)
try:
response = requests.get(
"https://target-site.com/api/data",
proxies=proxies,
timeout=timeout
)
except requests.exceptions.ConnectTimeout:
print("Verbindung zum Proxy oder Server fehlgeschlagen")
except requests.exceptions.ReadTimeout:
print("Server hat Daten nicht rechtzeitig gesendet")
Für aiohttp (asynchrones Python):
import aiohttp
import asyncio
async def fetch_with_timeout():
timeout = aiohttp.ClientTimeout(
total=60, # Gesamt-Timeout
connect=10, # Zum Verbinden
sock_read=30 # Zum Datenlesen
)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(
"https://target-site.com/api/data",
proxy="http://user:pass@proxy.example.com:8080"
) as response:
return await response.text()
Retry-Logik: der richtige Ansatz
Ein Timeout ist nicht immer ein Fehler. Oft ist eine wiederholte Anfrage erfolgreich. Aber Retry muss intelligent durchgeführt werden.
Exponentielle Verzögerung
Bombardieren Sie den Server nicht ohne Pause mit wiederholten Anfragen. Verwenden Sie exponentiellen Backoff: jeder nächste Versuch – mit zunehmender Verzögerung.
import requests
import time
import random
def fetch_with_retry(url, proxies, max_retries=3):
"""Anfrage mit Retry und exponentieller Verzögerung"""
for attempt in range(max_retries):
try:
response = requests.get(
url,
proxies=proxies,
timeout=(10, 30)
)
response.raise_for_status()
return response
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError) as e:
if attempt == max_retries - 1:
raise # Letzter Versuch – Fehler werfen
# Exponentielle Verzögerung: 1s, 2s, 4s...
# + zufälliger Jitter um keine Anfragewellen zu erzeugen
delay = (2 ** attempt) + random.uniform(0, 1)
print(f"Versuch {attempt + 1} fehlgeschlagen: {e}")
print(f"Wiederholung in {delay:.1f} Sekunden...")
time.sleep(delay)
Bibliothek tenacity
Für Production-Code ist es bequemer, fertige Lösungen zu verwenden:
from tenacity import retry, stop_after_attempt, wait_exponential
from tenacity import retry_if_exception_type
import requests
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
retry=retry_if_exception_type((
requests.exceptions.Timeout,
requests.exceptions.ConnectionError
))
)
def fetch_data(url, proxies):
response = requests.get(url, proxies=proxies, timeout=(10, 30))
response.raise_for_status()
return response.json()
Proxy-Rotation bei Timeouts
Wenn ein Proxy ständig Timeouts verursacht – liegt das Problem bei ihm. Logische Lösung: zu einem anderen wechseln.
import requests
from collections import deque
from dataclasses import dataclass, field
from typing import Optional
import time
@dataclass
class ProxyManager:
"""Proxy-Manager mit Verfolgung fehlgeschlagener Versuche"""
proxies: list
max_failures: int = 3
cooldown_seconds: int = 300
_failures: dict = field(default_factory=dict)
_cooldown_until: dict = field(default_factory=dict)
def get_proxy(self) -> Optional[str]:
"""Funktionierenden Proxy abrufen"""
current_time = time.time()
for proxy in self.proxies:
# Proxys in Cooldown überspringen
if self._cooldown_until.get(proxy, 0) > current_time:
continue
return proxy
return None # Alle Proxys in Cooldown
def report_failure(self, proxy: str):
"""Über fehlgeschlagene Anfrage berichten"""
self._failures[proxy] = self._failures.get(proxy, 0) + 1
if self._failures[proxy] >= self.max_failures:
# Proxy in Cooldown versetzen
self._cooldown_until[proxy] = time.time() + self.cooldown_seconds
self._failures[proxy] = 0
print(f"Proxy {proxy} in Cooldown versetzt")
def report_success(self, proxy: str):
"""Fehlerzähler bei Erfolg zurücksetzen"""
self._failures[proxy] = 0
def fetch_with_rotation(url, proxy_manager, max_attempts=5):
"""Anfrage mit automatischem Proxy-Wechsel bei Fehlern"""
for attempt in range(max_attempts):
proxy = proxy_manager.get_proxy()
if not proxy:
raise Exception("Keine verfügbaren Proxys")
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get(url, proxies=proxies, timeout=(10, 30))
response.raise_for_status()
proxy_manager.report_success(proxy)
return response
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
proxy_manager.report_failure(proxy)
print(f"Timeout über {proxy}, versuchen wir einen anderen...")
continue
raise Exception(f"Daten nach {max_attempts} Versuchen nicht abrufbar")
Bei Verwendung von Residential-Proxys mit automatischer Rotation vereinfacht sich diese Logik – der Anbieter wechselt die IP bei jedem Request oder nach einem festgelegten Intervall automatisch.
Asynchrone Anfragen mit Timeout-Kontrolle
Bei Massen-Scraping sind synchrone Anfragen ineffizient. Der asynchrone Ansatz ermöglicht die parallele Verarbeitung von Hunderten von URLs, erfordert aber sorgfältige Arbeit mit Timeouts.
import aiohttp
import asyncio
from typing import List, Tuple
async def fetch_one(
session: aiohttp.ClientSession,
url: str,
semaphore: asyncio.Semaphore
) -> Tuple[str, str | None, str | None]:
"""Laden einer URL mit Timeout-Verarbeitung"""
async with semaphore: # Parallelität begrenzen
try:
async with session.get(url) as response:
content = await response.text()
return (url, content, None)
except asyncio.TimeoutError:
return (url, None, "timeout")
except aiohttp.ClientError as e:
return (url, None, str(e))
async def fetch_all(
urls: List[str],
proxy: str,
max_concurrent: int = 10
) -> List[Tuple[str, str | None, str | None]]:
"""Massen-Download mit Timeout- und Parallelitätskontrolle"""
timeout = aiohttp.ClientTimeout(total=45, connect=10, sock_read=30)
semaphore = asyncio.Semaphore(max_concurrent)
connector = aiohttp.TCPConnector(
limit=max_concurrent,
limit_per_host=5 # Nicht mehr als 5 Verbindungen pro Host
)
async with aiohttp.ClientSession(
timeout=timeout,
connector=connector
) as session:
# Proxy für alle Anfragen setzen
tasks = [
fetch_one(session, url, semaphore)
for url in urls
]
results = await asyncio.gather(*tasks)
# Statistik
success = sum(1 for _, content, _ in results if content)
timeouts = sum(1 for _, _, error in results if error == "timeout")
print(f"Erfolgreich: {success}, Timeouts: {timeouts}")
return results
# Verwendung
async def main():
urls = [f"https://example.com/page/{i}" for i in range(100)]
results = await fetch_all(
urls,
proxy="http://user:pass@proxy.example.com:8080",
max_concurrent=10
)
asyncio.run(main())
Wichtig: Setzen Sie nicht zu hohe Parallelität. 50-100 gleichzeitige Anfragen über einen Proxy – das ist bereits viel. Besser 10-20 mit mehreren Proxys.
Diagnose: Wie man die Ursache findet
Bevor Sie die Einstellungen ändern, bestimmen Sie die Fehlerquelle.
Schritt 1: Proxy direkt prüfen
# Einfacher Test über curl mit Zeitmessung
curl -x http://user:pass@proxy:8080 \
-w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" \
-o /dev/null -s \
https://httpbin.org/get
Wenn time_connect mehr als 5 Sekunden beträgt – Problem mit Proxy oder Netzwerk zu ihm.
Schritt 2: Mit direkter Anfrage vergleichen
import requests
import time
def measure_request(url, proxies=None):
start = time.time()
try:
r = requests.get(url, proxies=proxies, timeout=30)
elapsed = time.time() - start
return f"OK: {elapsed:.2f}s, status: {r.status_code}"
except Exception as e:
elapsed = time.time() - start
return f"FAIL: {elapsed:.2f}s, error: {type(e).__name__}"
url = "https://target-site.com"
proxy = {"http": "http://proxy:8080", "https": "http://proxy:8080"}
print("Direkt:", measure_request(url))
print("Über Proxy:", measure_request(url, proxy))
Schritt 3: Verschiedene Proxy-Typen prüfen
Timeouts können vom Proxy-Typ abhängen:
| Proxy-Typ | Typische Verzögerung | Empfohlener Timeout |
|---|---|---|
| Datacenter | 50-200 ms | Connect: 5s, Read: 15s |
| Residential | 200-800 ms | Connect: 10s, Read: 30s |
| Mobile | 300-1500 ms | Connect: 15s, Read: 45s |
Schritt 4: Details protokollieren
import logging
import requests
from requests.adapters import HTTPAdapter
# Debug-Protokollierung aktivieren
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)
# Jetzt sehen Sie alle Phasen der Anfrage:
# - DNS-Auflösung
# - Verbindungsaufbau
# - Anfrage senden
# - Antwort erhalten
Checkliste zur Behebung von Timeout-Fehlern
Kurzer Aktionsalgorithmus bei Timeout-Fehlern:
- Timeout-Typ bestimmen – Connect oder Read? Das sind unterschiedliche Probleme.
- Proxy separat prüfen – Funktioniert er überhaupt? Welche Verzögerung?
- Timeouts erhöhen – Möglicherweise sind die Werte zu aggressiv für Ihren Proxy-Typ.
- Retry mit Backoff hinzufügen – Einzelne Timeouts sind normal, Stabilität ist wichtig.
- Rotation konfigurieren – Automatisch zu einem anderen Proxy wechseln bei Problemen.
- Parallelität begrenzen – Zu viele gleichzeitige Anfragen überlasten den Proxy.
- Zielwebsite prüfen – Möglicherweise blockiert oder drosselt sie Ihre Anfragen.
Fazit
Timeout-Fehler über einen Proxy sind ein lösbares Problem. In den meisten Fällen reicht es aus, die Timeouts richtig für den Proxy-Typ zu konfigurieren, Retry-Logik hinzuzufügen und Rotation bei Ausfällen zu implementieren. Für Aufgaben mit hohen Stabilitätsanforderungen verwenden Sie Residential-Proxys mit automatischer Rotation – mehr Details auf proxycove.com.