← Zurück zum Blog

Proxys in Rust reqwest: SOCKS5-Konfiguration, Client-Pool und IP-Rotation für Web-Scraper

Wir erklären, wie man SOCKS5-Proxy in Rust reqwest verbindet, einen Client-Pool aufbaut und IP-Rotation für stabiles Scraping ohne Sperrungen implementiert.

📅18. Oktober 2026

Jeder Parser, der länger als fünf Minuten auf realen Websites arbeitet, stößt früher oder später auf IP-Sperren. Rust mit der Bibliothek reqwest bietet hervorragende Leistung, löst jedoch nicht von Haus aus das Problem der Proxy-Rotation – das muss man selbst implementieren. In diesem Artikel werden wir besprechen, wie man SOCKS5- und HTTP(S)-Proxys anschließt, einen Client-Pool aufbaut und die IP-Rotation organisiert, damit der Parser nach den ersten 429- und 403-Antworten nicht ausfällt.

Warum braucht ein Parser Proxys und welche Gefahren birgt deren Fehlen

Websites wie Marktplätze, Preisaggregatoren und soziale Netzwerke überwachen anomale Aktivitäten von einer IP-Adresse: Dutzende Anfragen pro Sekunde, einheitliche User-Agents, fehlende Cookies zwischen Anfragen. Sobald der Zähler verdächtiger Aktivitäten einen bestimmten Schwellenwert überschreitet, wird die IP auf die Sperrliste gesetzt – vorübergehend oder dauerhaft. Für einen Parser bedeutet dies eine vollständige Unterbrechung der Datensammlung, bis die IP manuell gewechselt wird.

Proxys lösen dieses Problem, indem sie die Anfragen auf Dutzende oder Hunderte von IP-Adressen verteilen. Dabei ist es wichtig, nicht nur einfach "Proxys einzusetzen", sondern den Pool von Verbindungen und die Rotation sinnvoll zu organisieren, sonst stoßen Sie entweder auf die Limits von reqwest selbst (ein Client = ein Pool von Verbindungen), oder Sie beginnen, Anfragen von demselben Proxy zu oft zu senden. Im Folgenden werden wir beide Probleme nacheinander behandeln: zuerst die grundlegende Verbindung, dann die Architektur des Pools.

Für einen Produktionsparser ist nicht nur die IP-Rotation wichtig, sondern auch die Qualität der Adressen selbst. Wenn Sie Wildberries, Ozon oder Avito parsen, werden IPs von Rechenzentren schneller gesperrt als IPs von echten Nutzern – mehr dazu im Abschnitt über die Auswahl des Proxytyps.

Grundlegende Einrichtung von HTTP/HTTPS-Proxys in reqwest

reqwest unterstützt Proxys über die Struktur Proxy, die an ClientBuilder übergeben werden muss. Fügen Sie die Abhängigkeiten in Cargo.toml hinzu:

[dependencies]
reqwest = { version = "0.12", features = ["socks", "json"] }
tokio = { version = "1", features = ["full"] }

Ein minimales Beispiel mit einem HTTP-Proxy, der eine Authentifizierung mit Benutzername und Passwort erfordert:

use reqwest::{Client, Proxy};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proxy = Proxy::all("http://gate.proxycove.com:8000")?
        .basic_auth("user_login", "user_password");

    let client = Client::builder()
        .proxy(proxy)
        .timeout(std::time::Duration::from_secs(15))
        .build()?;

    let resp = client
        .get("https://httpbin.org/ip")
        .send()
        .await?
        .text()
        .await?;

    println!("Antwort über Proxy: {resp}");
    Ok(())
}

Die Methode Proxy::all leitet sowohl HTTP- als auch HTTPS-Traffic über den Proxy. Wenn Sie die Protokolle trennen müssen, verwenden Sie Proxy::http() und Proxy::https() separat – das ist nützlich, wenn der Proxy-Anbieter unterschiedliche Ports für verschiedene Protokolle verwendet.

Anschluss von SOCKS5-Proxys

SOCKS5 arbeitet auf einer niedrigeren Ebene als HTTP-Proxys und unterstützt TCP-Verbindungen, ohne den Inhalt der Anfrage zu analysieren – das ist schneller und stabiler für das Parsing, insbesondere wenn Sie nicht nur über HTTP(S) zugreifen, sondern auch WebSocket-Endpunkte ansteuern. Für SOCKS5 müssen Sie das Feature socks in Cargo.toml aktivieren (bereits oben angegeben). Beispiel für die Verbindung:

use reqwest::{Client, Proxy};

async fn build_socks5_client(
    host: &str,
    login: &str,
    password: &str,
) -> reqwest::Result<Client> {
    let proxy_url = format!("socks5h://{host}");
    let proxy = Proxy::all(&proxy_url)?.basic_auth(login, password);

    Client::builder()
        .proxy(proxy)
        .timeout(std::time::Duration::from_secs(20))
        .build()
}

Beachten Sie das Schema socks5h anstelle von socks5. Der Buchstabe "h" am Ende bedeutet, dass die DNS-Auflösung des Domainnamens auf der Seite des Proxy-Servers erfolgt, nicht lokal. Dies ist entscheidend für die Anonymität: Ohne sie kann Ihr echter DNS-Resolver die angeforderten Domains an den Proxy "leaken", selbst wenn der Traffic über einen SOCKS5-Tunnel läuft.

Wenn der Proxy-Anbieter keine Authentifizierung mit Benutzername/Passwort erfordert (IP-Whitelisting), rufen Sie einfach nicht basic_auth auf – geben Sie in der Anbieteroberfläche die IP-Adresse Ihres Servers an, von dem die Anfragen ausgehen.

Client-Pool von reqwest für verschiedene Proxys

Ein häufiger Fehler besteht darin, für jede Anfrage einen neuen Client zu erstellen. Das ist teuer: reqwest hält intern einen Pool von TCP-Verbindungen und TLS-Sitzungen, und die Neuerstellung des Clients vernichtet alle Vorteile von Keep-Alive. Der richtige Ansatz besteht darin, im Voraus einen Client für jeden Proxy zu erstellen und diese aus einem gemeinsamen Pool wiederzuverwenden.

use reqwest::{Client, Proxy};
use std::sync::Arc;

#[derive(Clone)]
struct ProxyEndpoint {
    url: String,
    login: String,
    password: String,
}

struct ClientPool {
    clients: Vec<Client>,
}

impl ClientPool {
    fn build(endpoints: &[ProxyEndpoint]) -> reqwest::Result<Self> {
        let mut clients = Vec::with_capacity(endpoints.len());

        for ep in endpoints {
            let proxy = Proxy::all(&ep.url)?
                .basic_auth(&ep.login, &ep.password);

            let client = Client::builder()
                .proxy(proxy)
                .pool_max_idle_per_host(4)
                .timeout(std::time::Duration::from_secs(15))
                .build()?;

            clients.push(client);
        }

        Ok(Self { clients })
    }
}

Ein solcher Pool wird einmal beim Start des Parsers erstellt. Die Anzahl der Clients entspricht normalerweise der Anzahl der einzigartigen Proxy-Adressen in Ihrem Pool – wenn der Anbieter Zugang zur Rotation auf Gateway-Ebene bietet, reicht ein oder zwei Clients aus, da die IP-Rotation automatisch auf der Seite des Anbieters bei jeder neuen TCP-Verbindung erfolgt.

Für Aufgaben mit hoher Parallelität wickeln Sie den Pool in Arc und teilen Sie ihn zwischen Tokio-Tasks – reqwest-Clients sind threadsicher und für die gleichzeitige Nutzung konzipiert.

Strategien zur IP-Rotation

Es gibt zwei grundlegend unterschiedliche Ansätze zur Rotation. Der erste ist die Rotation auf der Seite des Anbieters: Sie verbinden sich mit einer Gateway-Adresse, und der Anbieter ersetzt die IP bei jeder Anfrage oder alle N Minuten selbst. Der zweite – die Rotation auf Ihrer Seite: Sie halten eine Liste statischer Proxys und wählen selbst aus, welchen Sie für die nächste Anfrage verwenden möchten.

Wenn Sie residential Proxys mit automatischer Rotation über Gateway verwenden, vereinfacht sich der Code auf ein Minimum – Sie senden einfach Anfragen über denselben Client, der Anbieter ändert die ausgehende IP selbst. Aber wenn Sie Kontrolle benötigen (zum Beispiel, um eine IP für 10 Minuten an eine Sitzung zu binden, um das Verhalten eines echten Nutzers zu simulieren), implementieren Sie die Rotation manuell:

use rand::seq::SliceRandom;
use std::sync::atomic::{AtomicUsize, Ordering};

struct RotatingPool {
    clients: Vec<Client>,
    counter: AtomicUsize,
}

impl RotatingPool {
    // Round-robin: wir durchlaufen die Clients der Reihe nach
    fn next_round_robin(&self) -> &Client {
        let idx = self.counter.fetch_add(1, Ordering::SeqCst) % self.clients.len();
        &self.clients[idx]
    }

    // Zufällige Auswahl – nützlich gegen Musteranalyse
    fn next_random(&self) -> &Client {
        self.clients.choose(&mut rand::thread_rng()).unwrap()
    }
}

Round-robin eignet sich, wenn eine gleichmäßige Lastverteilung auf alle IPs wichtig ist. Zufällige Auswahl maskiert besser das Muster der Anfragen, wenn die Zielwebsite die Intervalle zwischen Anfragen von derselben Adresse analysiert. Für Aufgaben wie die Preisüberwachung auf Ozon oder das Sammeln von Anzeigen von Avito ist in der Praxis eine Kombination effektiver: zufällige Auswahl von Proxys plus zufällige Verzögerung von 1–3 Sekunden zwischen Anfragen desselben Clients.

Separat sollte man über sticky-Sitzungen sprechen. Wenn der Parser ein mehrstufiges Szenario durchläuft (Login → Seitenwechsel → Aktion), darf die IP während der Sitzung nicht gewechselt werden – die Website wird den IP-Wechsel bei einer aktiven Cookie-Sitzung bemerken und entweder abmelden oder das Konto sperren. Für solche Szenarien binden Sie einen Client (und entsprechend eine IP) an eine bestimmte "Sitzung" des Parsers für die gesamte Lebensdauer.

Fehlerbehandlung und Retry-Logik

Proxys fallen gelegentlich aus: Zeitüberschreitungen, 407 (Proxy-Authentifizierung erforderlich), Abbrüche von TCP-Verbindungen. Der Parser muss in der Lage sein, "Proxy defekt – aus dem Pool ausschließen" von "Website hat 429 zurückgegeben – einfach warten und IP wechseln" zu unterscheiden. Hier ist ein funktionierendes Muster mit Retry und dem Ausschluss toter Proxys:

use reqwest::StatusCode;
use std::time::Duration;

async fn fetch_with_retry(
    pool: &RotatingPool,
    url: &str,
    max_attempts: u32,
) -> Result<String, String> {
    let mut last_error = String::new();

    for attempt in 0..max_attempts {
        let client = pool.next_random();

        match client.get(url).send().await {
            Ok(resp) if resp.status() == StatusCode::TOO_MANY_REQUESTS => {
                last_error = "429: Rate Limit, IP wechseln".into();
                tokio::time::sleep(Duration::from_millis(500 * (attempt as u64 + 1))).await;
                continue;
            }
            Ok(resp) if resp.status().is_success() => {
                return resp.text().await.map_err(|e| e.to_string());
            }
            Ok(resp) => {
                last_error = format!("Unerwarteter Status: {}", resp.status());
            }
            Err(e) if e.is_timeout() || e.is_connect() => {
                last_error = format!("Problem mit dem Proxy: {e}");
                continue;
            }
            Err(e) => {
                last_error = e.to_string();
            }
        }
    }

    Err(format!("Alle Versuche erschöpft: {last_error}"))
}

Ein wichtiger Punkt: exponentielle Verzögerung (500 * attempt) verringert die Last auf die Zielwebsite bei massenhaften 429-Antworten und gleichzeitig das Risiko einer vollständigen Sperrung des gesamten Proxy-Pools auf einmal. Für Produktionsparser fügen Sie einen Fehlerzähler für jeden Proxy im Pool hinzu und schließen Sie automatisch die Adresse aus, wenn sie 5+ Fehler hintereinander gibt – das schützt vor nutzlosen Versuchen über bereits gesperrte IPs.

Welchen Proxytyp für das Parsing wählen

Die Wahl des Proxytyps beeinflusst direkt, wie viele erfolgreiche Anfragen Sie vor einer Sperrung erhalten. IPs von Rechenzentren sind die günstigsten und schnellsten, gehören jedoch zu bekannten Bereichen von Hosting-Anbietern – Anti-Bot-Systeme von Marktplätzen und sozialen Netzwerken erkennen sie fast sofort anhand von ASN-Datenbanken. Residential und mobile IPs sehen aus wie normale Nutzer, daher leben sie um ein Vielfaches länger.

Proxytyp Geschwindigkeit Sperrrisiko Wann anwenden
Rechenzentrum Hoch Hoch Öffentliche APIs, Websites ohne Anti-Bot
Residential Mittel Niedrig Wildberries, Ozon, Avito, soziale Netzwerke
Mobile Mittel Minimal Instagram, TikTok, strenge Anti-Bots

Für das Parsing von Marktplätzen in großen Mengen ist ein vernünftiger Kompromiss – residential Proxys: Sie sind nicht so teuer im Verhältnis zum Traffic wie mobile, bestehen jedoch die Prüfungen auf der Ebene "echter Nutzer", die die Bereiche von Rechenzentren ausschneiden. Wenn der Parser jedoch über APIs von sozialen Netzwerken arbeitet oder eine mobile Anwendung simuliert, bieten mobile Proxys den geringsten Prozentsatz an Captchas und zeitlichen Sperren aufgrund von IPs, die den Mobilfunkanbietern zugeordnet sind.

Leistung und Tipps zur Lastbewältigung

Rust ermöglicht es, Tausende von konkurrierenden Anfragen auf einer Instanz zu halten, aber der Proxy-Anbieter und die Zielwebsite begrenzen dennoch die tatsächliche Bandbreite. Einige praktische Regeln für Produktionsparser:

  • Begrenzen Sie die Parallelität über tokio::sync::Semaphore – nicht mehr als 20-50 gleichzeitige Anfragen an einen Proxy-Pool, sonst beginnen einige Verbindungen, aufgrund von Zeitüberschreitungen abzubrechen.
  • Verwenden Sie pool_idle_timeout in ClientBuilder, damit reqwest keine toten Verbindungen über instabile Proxys hält.
  • Protokollieren Sie HTTP-Status und Antwortzeiten für jeden Proxy separat – so sehen Sie schnell, welche Adressen anfangen zu "verrotten" und ersetzt werden müssen.
  • Die Rotation von User-Agent zusammen mit der Rotation von IP verringert erheblich die Wahrscheinlichkeit einer Erkennung: Ändern Sie sie synchron und nicht in separaten Zyklen.
  • Für langlebige Parser fügen Sie einen Health-Check hinzu: Senden Sie alle paar Minuten eine Testanfrage an https://httpbin.org/ip über jeden Proxy und schließen Sie nicht antwortende aus.

Achten Sie besonders auf TLS-Fingerabdrücke (JA3/JA3S). reqwest verwendet standardmäßig rustls oder nativetls je nach den Build-Features, und beide haben unterschiedliche Fingerabdrücke des TLS-Handshakes. Einige Anti-Bot-Systeme (z. B. bei großen Marktplätzen) analysieren genau den TLS-Fingerabdruck und nicht nur die IP – daher wird selbst der beste Proxy nicht helfen, wenn der Client sich mit einem nicht standardmäßigen TLS-Handshake ausgibt. Wenn Sie bei "sauberen" Proxys auf Sperren stoßen, überprüfen Sie die Features von reqwest und versuchen Sie das Feature native-tls anstelle von rustls oder umgekehrt.

Fazit

Proxys in Rust reqwest lassen sich schnell einrichten – ein paar Zeilen mit Proxy::all oder dem SOCKS5-Schema socks5h://. Die eigentliche Arbeit beginnt danach: Aufbau eines Client-Pools, Auswahl der Rotationsstrategie (Round-Robin, zufällige oder sticky-Sitzungen) und sinnvolle Fehlerbehandlung mit dem Ausschluss nicht funktionierender Adressen aus dem Pool. Ohne dies wird selbst der schnellste Parser in Rust nach wenigen Minuten Arbeit auf Sperren stoßen.

Wenn Ihr Parser mit Marktplätzen oder sozialen Netzwerken arbeitet, bei denen die Anti-Bot-Schutzmaßnahmen nicht nur die Häufigkeit der Anfragen, sondern auch die Herkunft der IP analysieren, beginnen Sie mit residential Proxys – sie bieten das beste Verhältnis von Preis zu Lebensdauer der Sitzungen. Für strengere Ziele wie Instagram und TikTok sollten Sie mobile IPs testen, und für einfache Aufgaben ohne Anti-Bot reichen schnelle und günstige Proxys von Rechenzentren vollkommen aus.