Gerçek sitelerde beş dakikadan fazla çalışan herhangi bir parser, er ya da geç IP engellemesine takılır.
Rust, reqwest kütüphanesi ile
mükemmel bir performans sunar, ancak proxy döngüsü sorununu kutudan çıkar çıkmaz çözmez - bunu kendiniz gerçekleştirmeniz gerekir.
Bu yazıda, SOCKS5 ve HTTP(S) proxy nasıl bağlanır, istemci havuzu nasıl toplanır ve IP döngüsü nasıl organize edilir,
böylece parser ilk 429 ve 403 yanıtlarından sonra çökmez, bunu inceleyeceğiz.
Parser için proxy neden gereklidir ve yokluğunun tehlikeleri
Pazar yerleri, fiyat toplayıcılar ve sosyal medya gibi siteler, bir IP'den anormal aktiviteyi izler: saniyede onlarca istek, benzer User-Agent'lar, istekler arasında çerezlerin olmaması. Şüpheli eylemlerin sayacı eşiği aştığında, IP yasak listesine alınır - geçici veya kalıcı. Bu, parser için veri toplamanın tamamen durması anlamına gelir, IP manuel olarak değiştirilene kadar.
Proxy'ler bu sorunu çözer, istekleri onlarca veya yüzlerce IP adresi arasında dağıtır. Burada önemli olan sadece "proxy eklemek" değil, bağlantı havuzunu ve döngüyü akıllıca organize etmektir, aksi takdirde ya reqwest'in kendi limitlerine (bir istemci = bir bağlantı havuzu) takılırsınız ya da aynı proxy'den çok sık istek göndermeye başlarsınız. İki sorunu sırayla inceleyeceğiz: önce temel bağlantı, sonra havuz mimarisi.
Üretim parser'ı için sadece IP döngüsü değil, aynı zamanda adreslerin kalitesi de önemlidir. Eğer Wildberries, Ozon veya Avito'yu tarıyorsanız, veri merkezi IP'leri gerçek kullanıcıların IP'lerinden daha hızlı yasaklanır - bu konu proxy türü seçimi bölümünde daha ayrıntılı olarak ele alınacaktır.
reqwest'te HTTP/HTTPS proxy temel ayarları
reqwest, Proxy yapısını
destekler, bu yapı ClientBuilder'a
iletilmelidir. Cargo.toml dosyanıza bağımlılıkları ekleyin:
[dependencies]
reqwest = { version = "0.12", features = ["socks", "json"] }
tokio = { version = "1", features = ["full"] }
Kullanıcı adı ve şifre ile kimlik doğrulama gerektiren bir HTTP proxy ile minimum örnek:
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!("Proxy üzerinden yanıt: {resp}");
Ok(())
}
Proxy::all
metodu hem HTTP hem de HTTPS trafiğini proxy üzerinden yönlendirir. Protokolleri ayırmanız gerekiyorsa,
Proxy::http() ve
Proxy::https()
ayrı ayrı kullanın - bu, proxy sağlayıcınızın farklı protokoller için farklı portlar dinlemesi durumunda yararlıdır.
SOCKS5 proxy bağlantısı
SOCKS5, HTTP proxy'den daha düşük bir seviyede çalışır ve istek içeriğini ayırt etmeden TCP bağlantılarını destekler -
bu, özellikle sadece HTTP(S) ile değil, aynı zamanda WebSocket uç noktalarını da çağırıyorsanız, daha hızlı ve daha kararlıdır.
SOCKS5 için, Cargo.toml'da socks
özelliğini etkinleştirdiğinizden emin olun (yukarıda zaten belirtildi). Bağlantı örneği:
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()
}
socks5h
şemasına dikkat edin, socks5
yerine. Sonundaki "h" harfi, alan adının DNS çözümlemesinin proxy sunucusu tarafında gerçekleştiği anlamına gelir, yerel olarak değil.
Bu, anonimlik için kritik öneme sahiptir: bunun olmaması durumunda, gerçek DNS çözümleyiciniz, proxy'yi geçerek
istenen alan adlarını "sızdırabilir", hatta trafik SOCKS5 tüneli üzerinden geçiyor olsa bile.
Eğer proxy sağlayıcınız kullanıcı adı/şifre ile kimlik doğrulama gerektirmiyorsa (IP beyaz listeleme), sadece
basic_auth çağırmayın -
sağlayıcı panelinde, isteklerin geldiği sunucunun IP adresini belirtin.
Farklı proxy'ler için reqwest istemci havuzu
Sık yapılan bir hata, her istek için yeni bir Client
oluşturmaktır. Bu maliyetlidir: reqwest, içinde bir TCP bağlantı havuzu ve TLS oturumları tutar ve istemcinin yeniden oluşturulması
tüm keep-alive avantajlarını ortadan kaldırır. Doğru yaklaşım, her proxy için önceden bir istemci oluşturmak ve bunları
ortak havuzdan yeniden kullanmaktır.
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 })
}
}
Bu havuz, parser başlatıldığında bir kez oluşturulur. İstemci sayısı genellikle havuzunuzdaki benzersiz proxy adreslerinin sayısına eşittir - eğer sağlayıcı, her yeni TCP bağlantısında otomatik olarak IP döngüsü sağlıyorsa (gateway düzeyinde), bir veya iki istemci yeterlidir.
Yüksek paralellik gerektiren görevler için havuzu Arc ile sarın
ve tokio görevleri arasında paylaşın - reqwest istemcileri thread-safe'dir ve eşzamanlı kullanım için tasarlanmıştır.
IP döngü stratejileri
İki temel yaklaşım vardır. Birincisi - sağlayıcı tarafında döngü: bir gateway adresine bağlanıyorsunuz ve sağlayıcı her istek için ya da her N dakikada bir yeni IP ekliyor. İkincisi - sizin tarafınızda döngü: statik proxy'lerin bir listesini tutuyorsunuz ve bir sonraki istek için hangisini kullanacağınıza kendiniz karar veriyorsunuz.
Eğer rezidans proxy'leri kullanıyorsanız ve gateway üzerinden otomatik döngü varsa, kod minimuma indirgenir - sadece aynı istemci üzerinden istek gönderirsiniz, sağlayıcı çıkış IP'sini kendisi değiştirir. Ancak kontrol gerekiyorsa (örneğin, gerçek bir kullanıcının davranışını taklit etmek için IP'yi 10 dakika boyunca oturuma sabitlemek), döngüyü manuel olarak gerçekleştirin:
use rand::seq::SliceRandom;
use std::sync::atomic::{AtomicUsize, Ordering};
struct RotatingPool {
clients: Vec<Client>,
counter: AtomicUsize,
}
impl RotatingPool {
// Round-robin: sırayla istemcileri döngüye alıyoruz
fn next_round_robin(&self) -> &Client {
let idx = self.counter.fetch_add(1, Ordering::SeqCst) % self.clients.len();
&self.clients[idx]
}
// Rastgele seçim - desen analizine karşı faydalı
fn next_random(&self) -> &Client {
self.clients.choose(&mut rand::thread_rng()).unwrap()
}
}
Round-robin, tüm IP'ler üzerinde eşit yük dağılımı önemli olduğunda uygundur. Rastgele seçim, hedef site bir adresten gelen istekler arasındaki aralıkları analiz ediyorsa, istek desenlerini daha iyi maskelemektedir. Ozon'da fiyat takibi veya Avito'dan ilan toplama gibi görevlerde, pratikte daha etkili bir kombinasyon: rastgele proxy seçimi artı aynı istemci için istekler arasında 1-3 saniye rastgele gecikme.
Sticky oturumlar hakkında ayrı bir şey söylemek gerekir. Eğer parser çok aşamalı bir senaryodan geçiyorsa (giriş → sayfalar arasında geçiş → eylem), IP'yi oturum ortasında değiştiremezsiniz - site, aktif bir çerez oturumu sırasında IP değişikliğini fark eder ve ya çıkış yapar ya da hesabı engeller. Bu tür senaryolar için, belirli bir "oturum" süresince bir istemciyi (ve dolayısıyla bir IP'yi) sabitleyin.
Hata işleme ve yeniden deneme mantığı
Proxy'ler zaman zaman kopabilir: zaman aşımı, 407 (Proxy Authentication Required), TCP bağlantısının kesilmesi. Parser, "proxy bozuldu - havuzdan çıkar" ile "site 429 döndürdü - sadece bekle ve IP'yi değiştir" arasında ayrım yapabilmelidir. İşte yeniden deneme ve ölü proxy'leri hariç tutma ile çalışan bir desen:
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: hız limiti, IP değiştiriyoruz".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!("Beklenmedik durum: {}", resp.status());
}
Err(e) if e.is_timeout() || e.is_connect() => {
last_error = format!("Proxy ile ilgili sorun: {e}");
continue;
}
Err(e) => {
last_error = e.to_string();
}
}
}
Err(format!("Tüm denemeler tükendi: {last_error}"))
}
Önemli bir nokta: üstel gecikme (500 * attempt)
hedef site üzerindeki yükü azaltır ve aynı anda tüm proxy havuzunun tamamen engellenme riskini azaltır. Üretim parser'ları için, havuzdaki her proxy için hata sayacı ekleyin ve eğer bir proxy art arda 5+ hata verirse, otomatik olarak adresi hariç tutun - bu, zaten yasaklanan IP üzerinden gereksiz denemelerden kaçınmanızı sağlar.
Hangi proxy türünü seçmelisiniz
Proxy türü seçimi, engellenmeden önce alacağınız başarılı istek sayısını doğrudan etkiler. Veri merkezi IP'leri en ucuz ve en hızlıdır, ancak tanınmış barındırma sağlayıcılarının aralıklarına aittir - pazar yerleri ve sosyal medya anti-bot sistemleri bunları neredeyse anında ASN veritabanlarına göre tespit eder. Rezidans ve mobil IP'ler ise normal kullanıcılar gibi görünür, bu nedenle çok daha uzun süre yaşarlar.
| Proxy Türü | Hız | Engellenme Riski | Ne Zaman Kullanılmalı |
|---|---|---|---|
| Veri Merkezi | Yüksek | Yüksek | Açık API'ler, anti-botsuz siteler |
| Rezidans | Orta | Düşük | Wildberries, Ozon, Avito, sosyal medya |
| Mobil | Orta | Minimum | Instagram, TikTok, katı anti-botlar |
Büyük hacimlerde pazar yerlerini taramak için makul bir denge - rezidans proxy'leridir: bunlar, mobil olanlar kadar trafik başına pahalı değildir, ancak veri merkezi aralıklarını kesen "gerçek kullanıcı" düzeyinde kontrollerden geçerler. Eğer parser, sosyal medya API'leri üzerinden çalışıyorsa veya mobil uygulamayı taklit ediyorsa, mobil proxy'ler IP'lerin operatörler tarafından sabitlenmesi sayesinde en düşük captcha ve geçici yasak oranını sağlar.
Performans ve yükle ilgili ipuçları
Rust, bir örnekte binlerce eşzamanlı isteği tutmanıza olanak tanır, ancak proxy sağlayıcısı ve hedef site yine de gerçek bant genişliğini sınırlar. Üretim parser'ı için birkaç pratik kural:
tokio::sync::Semaphorearacılığıyla paralelliği sınırlayın - bir proxy havuzuna en fazla 20-50 eşzamanlı istek, aksi takdirde bazı bağlantılar zaman aşımına uğramaya başlayacaktır.- reqwest'in, istikrarsız proxy'ler üzerinden ölü bağlantıları tutmaması için
pool_idle_timeoutkullanın. - Her proxy için ayrı ayrı HTTP durumlarını ve yanıt sürelerini kaydedin - böylece hangi adreslerin "çürümeye" başladığını ve değiştirilmesi gerektiğini hızlıca görebilirsiniz.
- User-Agent'ı IP döngüsü ile birlikte döndürmek, tespit olasılığını önemli ölçüde azaltır: bunları senkronize olarak değiştirin, ayrı döngülerle değil.
- Uzun ömürlü parser'lar için sağlık kontrolü ekleyin: her birkaç dakikada bir, her proxy üzerinden https://httpbin.org/ip adresine test isteği gönderin ve yanıt vermeyenleri hariç tutun.
TLS parmak izlerine (JA3/JA3S) özel bir dikkat gösterin. reqwest, varsayılan olarak rustls veya nativetls kullanır
derleme özelliklerine bağlı olarak ve her ikisinin de farklı TLS el sıkışma parmak izleri vardır. Bazı anti-bot sistemleri (örneğin,
büyük pazar yerlerinde) tam olarak TLS parmak izini analiz eder, sadece IP'yi değil - bu nedenle, mükemmel bir proxy bile
kurtaramaz, eğer istemci kendisini standart dışı bir TLS el sıkışması ile tanıtıyorsa. "Temiz" proxy'lerle engellemelerle karşılaşırsanız,
reqwest'in özelliklerini kontrol edin ve rustls yerine native-tls özelliğini deneyin veya tam tersini.
Sonuç
Rust reqwest'te proxy ayarları hızlı bir şekilde yapılır - Proxy::all
veya SOCKS5 şeması socks5h:// ile birkaç satırda.
Asıl iş, daha sonra başlar: istemci havuzunun oluşturulması, döngü stratejisinin seçilmesi (round-robin, rastgele
veya sticky oturumlar) ve çalışmayan adreslerin havuzdan hariç tutulması ile hata işleme. Bunu yapmadan, Rust'ta en hızlı parser bile
birkaç dakikalık çalışmadan sonra engellemelerle karşılaşır.
Eğer parser'ınız pazar yerleri veya sosyal medya ile çalışıyorsa, anti-bot korumasının sadece istek sıklığını değil, aynı zamanda IP kökenini de analiz ettiğini göz önünde bulundurarak, rezidans proxy'leri ile başlayın - bunlar, fiyat ve oturumların dayanıklılığı açısından en iyi dengeyi sağlar. Daha sıkı hedefler için, örneğin Instagram ve TikTok, mobil IP'leri test etmek faydalı olabilir, basit anti-botsuz görevler için ise hızlı ve ucuz veri merkezi proxy'leri yeterlidir.