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_timeoutdi 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.