← Kembali ke blog

Proxy di Rust reqwest: pengaturan SOCKS5, kolam klien, dan rotasi IP untuk parser

Membahas cara menghubungkan SOCKS5-proxy di Rust reqwest, membangun kumpulan klien, dan menerapkan rotasi IP untuk pengambilan data yang stabil tanpa pemblokiran.

📅18 Oktober 2026

Setiap parser yang bekerja lebih dari lima menit di situs nyata, cepat atau lambat akan menghadapi pemblokiran berdasarkan IP. Rust dengan pustaka reqwest memberikan kinerja yang sangat baik, tetapi tidak secara otomatis menyelesaikan masalah rotasi proksi — ini perlu diimplementasikan sendiri. Dalam artikel ini, kita akan membahas cara menghubungkan proksi SOCKS5 dan HTTP(S), membangun kumpulan klien, dan mengatur rotasi IP sehingga parser tidak jatuh setelah menerima respons 429 dan 403 yang pertama.

Mengapa parser memerlukan proksi dan bahaya ketidakadaannya

Situs seperti marketplace, agregator harga, dan media sosial melacak aktivitas anomali dari satu IP: puluhan permintaan per detik, User-Agent yang serupa, dan tidak adanya cookies antara permintaan. Begitu penghitung tindakan mencurigakan melebihi ambang batas, IP masuk ke dalam daftar hitam — sementara atau permanen. Bagi parser, ini berarti penghentian total pengumpulan data, sampai IP diganti secara manual.

Proksi menyelesaikan masalah ini dengan mendistribusikan permintaan di antara puluhan atau ratusan alamat IP. Penting untuk tidak hanya "mengganti proksi", tetapi juga mengatur kumpulan koneksi dan rotasi dengan baik, jika tidak, Anda akan terjebak dalam batasan dari reqwest itu sendiri (satu klien = satu kumpulan koneksi), atau Anda akan mulai mengirim permintaan dari proksi yang sama terlalu sering. Selanjutnya, kita akan membahas kedua masalah ini secara bergantian: pertama koneksi dasar, kemudian arsitektur kumpulan.

Untuk parser produksi, penting tidak hanya rotasi IP, tetapi juga kualitas alamat itu sendiri. Jika Anda melakukan parsing Wildberries, Ozon, atau Avito, IP dari pusat data diblokir lebih cepat daripada IP pengguna nyata — lebih lanjut tentang ini di bagian pemilihan jenis proksi.

Pengaturan dasar proksi HTTP/HTTPS di reqwest

reqwest mendukung proksi melalui struktur Proxy, yang perlu diteruskan ke ClientBuilder. Tambahkan ketergantungan di Cargo.toml:

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

Contoh minimal dengan proksi HTTP yang memerlukan otentikasi dengan nama pengguna dan kata sandi:

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!("Respons melalui proksi: {resp}");
    Ok(())
}

Metode Proxy::all mengarahkan lalu lintas HTTP dan HTTPS melalui proksi. Jika perlu memisahkan protokol, gunakan Proxy::http() dan Proxy::https() secara terpisah — ini berguna jika penyedia proksi mendengarkan port yang berbeda untuk protokol yang berbeda.

Menghubungkan proksi SOCKS5

SOCKS5 bekerja pada tingkat yang lebih rendah dibandingkan dengan proksi HTTP, dan mendukung koneksi TCP tanpa memeriksa konten permintaan — ini lebih cepat dan lebih stabil untuk parsing, terutama jika Anda tidak hanya mengakses HTTP(S), tetapi juga memanggil endpoint WebSocket. Untuk SOCKS5, pastikan untuk mengaktifkan fitur socks di Cargo.toml (sudah disebutkan di atas). Contoh koneksi:

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()
}

Perhatikan skema socks5h alih-alih socks5. Huruf "h" di akhir menunjukkan bahwa resolusi DNS domain terjadi di sisi server proksi, bukan secara lokal. Ini sangat penting untuk anonimitas: tanpa itu, resolver DNS nyata Anda dapat "membocorkan" domain yang diminta tanpa proksi, bahkan jika lalu lintas itu sendiri melewati terowongan SOCKS5.

Jika penyedia proksi tidak memerlukan otentikasi dengan nama pengguna/kata sandi (IP-whitelisting), cukup jangan panggil basic_auth — sebutkan di panel penyedia alamat IP server Anda, dari mana permintaan dikirim.

Kumpulan klien reqwest untuk berbagai proksi

Kesalahan umum adalah membuat Client baru untuk setiap permintaan. Ini mahal: reqwest di dalamnya menyimpan kumpulan koneksi TCP dan sesi TLS, dan membuat ulang klien menghancurkan semua keuntungan dari keep-alive. Pendekatan yang benar adalah membuat satu klien untuk setiap proksi sebelumnya dan menggunakan kembali dari kumpulan umum.

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 })
    }
}

Kumpulan seperti ini dibuat satu kali saat parser dimulai. Jumlah klien biasanya sama dengan jumlah alamat proksi unik dalam kumpulan Anda — jika penyedia memberikan akses ke rotasi di tingkat gateway, cukup satu atau dua klien, karena rotasi IP terjadi secara otomatis di sisi penyedia pada setiap koneksi TCP baru.

Untuk tugas dengan paralelisme tinggi, bungkus kumpulan dalam Arc dan bagikan di antara tugas-tugas tokio — klien reqwest aman untuk digunakan secara bersamaan dan dirancang untuk penggunaan bersamaan.

Strategi rotasi IP

Ada dua pendekatan yang berbeda untuk rotasi. Pertama — rotasi di sisi penyedia: Anda terhubung ke satu alamat gateway, dan penyedia secara otomatis mengganti IP baru untuk setiap permintaan atau setiap N menit. Kedua — rotasi di sisi Anda: Anda menyimpan daftar proksi statis dan memilih sendiri yang akan digunakan untuk permintaan berikutnya.

Jika Anda menggunakan proksi residensial dengan rotasi otomatis melalui gateway, kode disederhanakan hingga minimum — cukup kirim permintaan melalui klien yang sama, penyedia mengganti IP keluar sendiri. Tetapi jika Anda memerlukan kontrol (misalnya, mengaitkan IP dengan sesi selama 10 menit untuk meniru perilaku pengguna nyata), implementasikan rotasi secara manual:

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

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

impl RotatingPool {
    // Round-robin: secara bergiliran mengiterasi klien
    fn next_round_robin(&self) -> &Client {
        let idx = self.counter.fetch_add(1, Ordering::SeqCst) % self.clients.len();
        &self.clients[idx]
    }

    // Pilihan acak — berguna untuk melawan analisis pola
    fn next_random(&self) -> &Client {
        self.clients.choose(&mut rand::thread_rng()).unwrap()
    }
}

Round-robin cocok ketika beban yang merata pada semua IP penting. Pilihan acak lebih baik menyembunyikan pola permintaan, jika situs target menganalisis interval antara permintaan dari satu alamat. Untuk tugas seperti memantau harga di Ozon atau mengumpulkan iklan dari Avito, kombinasi lebih efektif dalam praktiknya: pilihan acak proksi ditambah penundaan acak 1–3 detik antara permintaan dari klien yang sama.

Terpisah, perlu disebutkan tentang sesi sticky. Jika parser menjalani skenario multi-langkah (login → navigasi halaman → aksi), IP tidak boleh diubah di tengah sesi — situs akan mendeteksi perubahan IP saat sesi cookie aktif dan akan mengeluarkan logout atau memblokir akun. Untuk skenario seperti itu, kaitkan satu klien (dan sesuai satu IP) dengan "sesi" parser tertentu selama masa hidupnya.

Penanganan kesalahan dan logika retry

Proksi kadang-kadang mengalami masalah: timeout, 407 (Proxy Authentication Required), putusnya koneksi TCP. Parser harus dapat membedakan antara "proksi rusak — kecualikan dari kumpulan" dan "situs mengembalikan 429 — cukup tunggu dan ganti IP". Berikut adalah pola kerja dengan retry dan pengecualian proksi mati:

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: batas laju, mengganti IP".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!("Status tak terduga: {}", resp.status());
            }
            Err(e) if e.is_timeout() || e.is_connect() => {
                last_error = format!("Masalah dengan proksi: {e}");
                continue;
            }
            Err(e) => {
                last_error = e.to_string();
            }
        }
    }

    Err(format!("Semua upaya telah habis: {last_error}"))
}

Nuansa penting: penundaan eksponensial (500 * attempt) mengurangi beban pada situs target saat banyak respons 429 dan secara bersamaan mengurangi risiko pemblokiran total dari seluruh kumpulan proksi sekaligus. Untuk parser produksi, tambahkan penghitung kesalahan pada setiap proksi dalam kumpulan dan secara otomatis kecualikan alamat jika memberikan 5+ kesalahan berturut-turut — ini akan melindungi dari upaya yang tidak berguna melalui IP yang sudah diblokir.

Jenis proksi mana yang dipilih untuk parsing

Pemilihan jenis proksi secara langsung mempengaruhi berapa banyak permintaan sukses yang akan Anda terima sebelum pemblokiran. IP dari pusat data adalah yang termurah dan tercepat, tetapi dimiliki oleh rentang penyedia hosting yang dikenal — sistem anti-bot dari marketplace dan media sosial dapat mendeteksinya hampir seketika melalui basis data ASN. IP residensial dan seluler terlihat seperti pengguna biasa, sehingga bertahan jauh lebih lama.

Jenis proksi Kecepatan Risiko pemblokiran Kapan diterapkan
Pusat data Tinggi Tinggi API terbuka, situs tanpa anti-bot
Residen Sedang Rendah Wildberries, Ozon, Avito, media sosial
Seluler Sedang Minimal Instagram, TikTok, anti-bot ketat

Untuk parsing marketplace dalam volume besar, keseimbangan yang masuk akal adalah proksi residensial: mereka tidak semahal dalam hal lalu lintas seperti proksi seluler, tetapi lolos dari pemeriksaan di tingkat "pengguna nyata", yang memotong rentang pusat data. Namun, jika parser bekerja melalui API media sosial atau meniru aplikasi seluler, proksi seluler memberikan persentase captcha dan pemblokiran waktu terendah berkat IP yang terikat pada operator seluler.

Kinerja dan tips untuk beban

Rust memungkinkan untuk mempertahankan ribuan permintaan bersamaan pada satu instance, tetapi penyedia proksi dan situs target tetap membatasi kapasitas nyata. Beberapa aturan praktis untuk parser produksi:

  • Batasi paralelisme melalui tokio::sync::Semaphore — tidak lebih dari 20-50 permintaan bersamaan pada satu kumpulan proksi, jika tidak, beberapa koneksi akan mulai terputus karena timeout.
  • Gunakan pool_idle_timeout di ClientBuilder, agar reqwest tidak menyimpan koneksi mati melalui proksi yang tidak stabil.
  • Catat status HTTP dan waktu respons untuk setiap proksi secara terpisah — dengan cara ini Anda akan cepat melihat alamat mana yang mulai "busuk" dan perlu diganti.
  • Rotasi User-Agent bersamaan dengan rotasi IP secara signifikan mengurangi kemungkinan deteksi: ubah mereka secara bersamaan, bukan dalam siklus terpisah.
  • Untuk parser yang bertahan lama, tambahkan health-check: setiap beberapa menit kirimkan permintaan uji ke https://httpbin.org/ip melalui setiap proksi dan kecualikan yang tidak merespons.

Perhatikan juga sidik jari TLS (JA3/JA3S). reqwest secara default menggunakan rustls atau nativetls tergantung pada fitur build, dan keduanya memiliki sidik jari TLS handshake yang berbeda. Beberapa sistem anti-bot (misalnya, di marketplace besar) menganalisis sidik jari TLS, bukan hanya IP — jadi bahkan proksi yang sempurna tidak akan menyelamatkan jika klien itu sendiri menunjukkan dirinya dengan handshake TLS yang tidak standar. Jika Anda mengalami pemblokiran saat menggunakan proksi "bersih", periksa fitur reqwest dan coba fitur native-tls alih-alih rustls, atau sebaliknya.

Kesimpulan

Proksi di Rust reqwest dapat diatur dengan cepat — beberapa baris dengan Proxy::all atau skema SOCKS5 socks5h://. Pekerjaan utama dimulai setelah itu: membangun kumpulan klien, memilih strategi rotasi (round-robin, acak atau sesi sticky) dan penanganan kesalahan yang baik dengan pengecualian alamat yang tidak berfungsi dari kumpulan. Tanpa ini, bahkan parser tercepat di Rust akan terjebak dalam pemblokiran setelah beberapa menit beroperasi.

Jika parser Anda bekerja dengan marketplace atau media sosial, di mana perlindungan anti-bot menganalisis tidak hanya frekuensi permintaan, tetapi juga asal IP, mulailah dengan proksi residensial — mereka memberikan keseimbangan terbaik antara harga dan ketahanan sesi. Untuk tujuan yang lebih ketat seperti Instagram dan TikTok, sebaiknya uji IP seluler, dan untuk tugas sederhana tanpa anti-bot, proksi pusat data yang cepat dan murah sudah cukup.