Rust ile scraping altyapısı kuruyorsanız, en büyük sorun hız değil — kararlılıktır. Tek bir IP'den binlerce istek attığınızda rate-limit, CAPTCHA ve bloklar anında devreye girer. Çözüm: Rust HTTP proxy desteğini doğru yapılandırmak ve residential proxy'lerle dönen bir IP havuzu oluşturmak.
Bu rehberde reqwest'ten hyper'a, tokio + JoinSet eşzamanlılık deseninden thiserror ile hata yönetimine kadar Rust ekosisteminde proxy kullanımını çalıştırılabilir kod örnekleriyle ele alıyoruz. ProxyHat residential proxy'lerini kullanarak tüm örnekleri canlı ortamda test edebilirsiniz.
reqwest ile Temel Proxy Yapılandırması
reqwest, Rust'ın en yaygın HTTP istemcisi. Proxy desteği varsayılan olarak gelir — Proxy::all() ile tüm trafiği, Proxy::http() ile yalnızca HTTP trafiğini yönlendirebilirsiniz.
use reqwest::Proxy;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// ProxyHat HTTP proxy'si — tüm trafiği yönlendir
let proxy = Proxy::all("http://user-country-US:pass@gate.proxyhat.com:8080")?
.no_proxy("localhost,127.0.0.1"); // Dahili adresler proxy'den geçmesin
let client = reqwest::Client::builder()
.proxy(proxy)
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(30))
.build()?;
let resp = client.get("https://httpbin.org/ip").send().await?;
let body = resp.text().await?;
println!("Proxy üzerinden IP: {}", body);
Ok(())
}
Burada iki kritik nokta var: Proxy::all() hem HTTP hem HTTPS trafiğini yakalar; .no_proxy() ise dahili servis çağrılarınızın proxy'den geçmesini engeller. ProxyHat kullanıcı adında country-US gibi geo-targeting bayrakları kullanarak ABD çıkışlı bir residential IP alabilirsiniz.
Kimlik Doğrulama ve Geo-Targeting
ProxyHat'ta kimlik doğrulama URL içine gömülüdür. Kullanıcı adı alanında şehir seviyesinde hedefleme ve sticky session da yapabilirsiniz:
// Şehir seviyesinde hedefleme
// "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
// Sticky session (aynı IP'yi tut)
// "http://user-session-abc123:pass@gate.proxyhat.com:8080"
// SOCKS5 proxy
// "socks5://user-country-GB:pass@gate.proxyhat.com:1080"
use reqwest::Proxy;
fn build_geo_proxy(country: &str, city: Option<&str>) -> Result<Proxy, reqwest::Error> {
let auth = match city {
Some(c) => format!("user-country-{}-city-{}:pass", country, c.to_lowercase()),
None => format!("user-country-{}:pass", country),
};
Proxy::all(format!("http://{}@gate.proxyhat.com:8080", auth))
}
hyper ile Düşük Seviye Proxy Desteği
hyper, reqwest'in alt katmanıdır. Daha fazla kontrol istiyorsanız — örneğin özel header enjeksiyonu, manuel CONNECT tüneli — hyper ile doğrudan çalışabilirsiniz. HTTPS üzerinden HTTP proxy kullanmak için CONNECT yöntemini anlamak kritiktir.
use hyper::{Client, Request, Method, Uri, body::Buf};
use hyper::client::connect::proxy::ProxyConnector;
use hyper_proxy::{Proxy, ProxyConnector as ProxyConn};
use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::Client as LegacyClient;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// HTTP proxy connector oluştur
let proxy_uri: Uri = "http://user-country-FR:pass@gate.proxyhat.com:8080"
.parse()?;
let proxy = Proxy::new(hyper_proxy::Intercept::All, proxy_uri);
// rustls tabanlı HTTPS connector
let https = HttpsConnector::builder()
.with_webpki_roots()?
.https_only(true)
.build();
let proxy_connector = ProxyConn::from_proxy(https, proxy)?;
let client: LegacyClient<_, hyper::Body> = LegacyClient::builder(TokioExecutor)
.build(proxy_connector);
let req = Request::builder()
.method(Method::GET)
.uri("https://httpbin.org/ip")
.body(hyper::Body::empty())?;
let resp = client.request(req).await?;
let body_bytes = hyper::body::to_bytes(resp.into_body()).await?;
println!("Yanıt: {}", String::from_utf8_lossy(body_bytes.chunk()));
Ok(())
}
hyper ile çalışırken hyper-proxy crate'i CONNECT tünellerini otomatik yönetir. HTTPS hedefine giderken proxy önce bir CONNECT isteği gönderir, sonra TLS el sıkışmasını proxy üzerinden yapar. Bu, residential proxy'lerin güvenli sitelere erişimi için zorunludur.
tokio + JoinSet ile Eşzamanlı Scraping
Tek istek atmak scraping değil. Gerçek dünyada yüzlerce sayfayı paralel çekmeniz gerekir. tokio::task::JoinSet, iptal edilebilir ve yönetilebilir eşzamanlı görevler için idealdir.
use reqwest::Client;
use std::sync::Arc;
use tokio::task::JoinSet;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Her istekte farklı ülke IP'si kullan
let countries = ["US", "DE", "GB", "FR", "JP"];
let urls = vec![
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://httpbin.org/get",
"https://httpbin.org/json",
];
let mut tasks: JoinSet<Result<String, reqwest::Error>> = JoinSet::new();
for (i, url) in urls.iter().enumerate() {
let country = countries[i % countries.len()];
let url = url.to_string();
tasks.spawn(async move {
let proxy_url = format!(
"http://user-country-{}:pass@gate.proxyhat.com:8080",
country
);
let proxy = reqwest::Proxy::all(&proxy_url)?;
let client = reqwest::Client::builder()
.proxy(proxy)
.timeout(Duration::from_secs(20))
.build()?;
let resp = client.get(&url).send().await?;
resp.text().await
});
}
// Tüm görevleri topla — hata olsa bile devam et
while let Some(result) = tasks.join_next().await {
match result {
Ok(Ok(body)) => println!("✓ İstek başarılı (ilk 80 karakter): {:.80}", body),
Ok(Err(e)) => eprintln!("✗ HTTP hatası: {}", e),
Err(e) => eprintln!("✗ Görev hatası: {}", e),
}
}
Ok(())
}
Performans ipucu: Her görev için yeniClientoluşturmak pahalıdır. Üretimde client'ları birArcile paylaşın veya bir proxy havuzu soyutlaması kullanın (aşağıda).
Dönen Proxy Havuzu: Trait Tabanlı Soyutlama
Farklı proxy sağlayıcıları, farklı döndürme stratejileri… Bunları bir trait altında birleştirmek, test edilebilirlik ve değiştirilebilirlik açısından kritik. Rust residential proxies kullanırken her istekte IP döndürmek, rate-limit'e karşı en etkili stratejidir.
use async_trait::async_trait;
use reqwest::{Client, Proxy, RequestBuilder};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
/// Proxy havuzu soyutlaması
#[async_trait]
pub trait ProxyPool: Send + Sync {
/// Bir sonraki proxy URL'sini döndür
async fn next_proxy_url(&self) -> String;
/// Proxy'li bir istemci döndür
async fn client(&self) -> Result<Client, reqwest::Error>;
}
/// ProxyHat residential proxy havuzu — her istekte farklı IP
pub struct RotatingProxyPool {
countries: Vec<String>,
counter: AtomicUsize,
base_user: String,
password: String,
}
impl RotatingProxyPool {
pub fn new(username: &str, password: &str, countries: Vec<&str>) -> Self {
Self {
countries: countries.iter().map(|s| s.to_string()).collect(),
counter: AtomicUsize::new(0),
base_user: username.to_string(),
password: password.to_string(),
}
}
}
#[async_trait]
impl ProxyPool for RotatingProxyPool {
async fn next_proxy_url(&self) -> String {
let idx = self.counter.fetch_add(1, Ordering::Relaxed);
let country = &self.countries[idx % self.countries.len()];
let session = format!("sess-{}", idx);
format!(
"http://{}-country-{}-session-{}:{}@gate.proxyhat.com:8080",
self.base_user, country, session, self.password
)
}
async fn client(&self) -> Result<Client, reqwest::Error> {
let proxy_url = self.next_proxy_url().await;
let proxy = Proxy::all(&proxy_url)?;
Client::builder()
.proxy(proxy)
.timeout(std::time::Duration::from_secs(30))
.build()
}
}
// Kullanım
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = Arc::new(RotatingProxyPool::new(
"user", "pass", vec!["US", "DE", "JP", "GB"]
));
let targets = vec!["https://httpbin.org/ip"; 8];
let mut handles = Vec::new();
for url in targets {
let pool = Arc::clone(&pool);
let url = url.to_string();
handles.push(tokio::spawn(async move {
let client = pool.client().await?;
let resp = client.get(&url).send().await?;
let body = resp.text().await?;
println!("IP: {}", body.lines().next().unwrap_or("?"));
Ok::<(), reqwest::Error>(())
}));
}
for h in handles {
let _ = h.await;
}
Ok(())
}
thiserror ile Hata Yönetimi
Scraping'de her şey yanlış gider — proxy zaman aşımı, CAPTCHA, DNS hatası, TLS el sıkışma başarısızlığı. thiserror ile tip güvenli hata tipleri oluşturmak, unwrap() paniklerinden kurtulmanın en temiz yoludur.
use thiserror::Error;
use reqwest::StatusCode;
#[derive(Error, Debug)]
pub enum ScrapingError {
#[error("Proxy bağlantı hatası: {0}")]
ProxyConnection(String),
#[error("HTTP {status} — {url}")]
Http {
status: StatusCode,
url: String,
},
#[error("Rate limit aşıldı — {retry_after:?} sonra tekrar deneyin")]
RateLimited {
retry_after: Option<std::time::Duration>,
},
#[error("CAPTCHA tespit edildi — {domain}")]
Captcha { domain: String },
#[error("Zaman aşımı: {0}")]
Timeout(String),
#[error("İstek hatası: {0}")]
Reqwest(#[from] reqwest::Error),
}
/// Yanıtı kontrol et ve iş hatası fırlat
pub fn check_response(resp: &reqwest::Response) -> Result<(), ScrapingError> {
let status = resp.status();
let url = resp.url().to_string();
if status == StatusCode::TOO_MANY_REQUESTS {
let retry_after = resp.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok())
.map(std::time::Duration::from_secs);
return Err(ScrapingError::RateLimited { retry_after });
}
if status.is_server_error() || status == StatusCode::FORBIDDEN {
// Basit CAPTCHA tespiti — gerçek projede body'yi de kontrol edin
if status == StatusCode::FORBIDDEN {
let domain = resp.url().host_str().unwrap_or("unknown").to_string();
return Err(ScrapingError::Captcha { domain });
}
return Err(ScrapingError::Http { status, url });
}
Ok(())
}
/// Retry mantığı ile güvenli istek
pub async fn fetch_with_retry(
client: &reqwest::Client,
url: &str,
max_retries: u32,
) -> Result<String, ScrapingError> {
let mut attempt = 0;
loop {
attempt += 1;
match client.get(url).send().await {
Ok(resp) => {
check_response(&resp)?;
return resp.text().await.map_err(ScrapingError::from);
}
Err(e) if e.is_timeout() && attempt <= max_retries => {
let delay = std::time::Duration::from_millis(500 * attempt as u64);
tokio::time::sleep(delay).await;
continue;
}
Err(e) if e.is_connect() && attempt <= max_retries => {
// Proxy bağlantı hatası — yeni IP ile tekrar dene
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
}
Err(e) => return Err(ScrapingError::from(e)),
}
}
}
TLS: rustls vs native-tls
Rust ekosisteminde iki büyük TLS arka ucu var. Seçiminiz derleme zamanı özellik bayraklarıyla (feature flags) belirlenir:
| Özellik | rustls | native-tls |
|---|---|---|
| Arka uç | Pure Rust (ring / aws-lc-rs) | OpenSSL / SChannel / Secure Transport |
| Cross-compile | Kolay — C bağımlılığı yok | Zor — OpenSSL bağlantısı gerekir |
| Performans | Yüksek (özellikle aws-lc-rs ile) | Platforma göre değişir |
| Sertifika deposu | webpki-roots (Mozilla bundle) | İşletim sistemi deposu |
| Proxy uyumluluğu | CONNECT tüneli desteklenir | CONNECT tüneli desteklenir |
| Boyut | Daha küçük binary | Daha büyük (OpenSSL link) |
Öneri: Docker'da cross-compile yapıyorsanız rustls seçin. Kurumsal ortamda işletim sistemi sertifika deposu gerekiyorsa native-tls.
reqwest Cargo.toml Yapılandırması
# rustls ile (varsayılan reqwest davranışı — 0.12+)
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls",
"charset",
"http2",
"mac-auth-system", # macOS
] }
# native-tls ile
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [
"native-tls",
"charset",
"http2",
] }
# SOCKS5 proxy desteği de eklemek isterseniz
reqwest = { version = "0.12", features = ["socks"] }
Derleme Zamanı Özellik Bayrakları ile Proxy Desteği
Üretimde her zaman proxy'ye ihtiyaç olmayabilir — örneğin unit testlerde veya dahili API'lerde. Feature flag'ler ile derleme zamanında proxy desteğini açıp kapatabilirsiniz:
# Cargo.toml
[features]
default = ["proxy-support"]
proxy-support = ["reqwest/proxy"] # reqwest 0.12'de proxy opsiyonel
socks-proxy = ["reqwest/socks"]
# src/lib.rs
#[cfg(feature = "proxy-support")]
pub fn create_client(proxy_url: &str) -> Result<reqwest::Client, reqwest::Error> {
let proxy = reqwest::Proxy::all(proxy_url)?;
reqwest::Client::builder()
.proxy(proxy)
.build()
}
#[cfg(not(feature = "proxy-support"))]
pub fn create_client(_proxy_url: &str) -> Result<reqwest::Client, reqwest::Error> {
// Proxy desteği yok — doğrudan bağlan
reqwest::Client::builder().build()
}
// Test ederken proxy'siz derleme:
// cargo test --no-default-features
Bu yaklaşım CI/CD pipeline'larınızda proxy bağımlılığını ortadan kaldırır ve binary boyutunu küçültür. Rust HTTP proxy desteğini koşullu olarak eklemek, mikro-servis mimarilerinde özellikle değerlidir.
Üretim İçin En İyi Uygulamalar
- Client'ı yeniden kullanın: Her istekte
Client::new() oluşturmak bağlantı havuzunu sıfırlar.Arc<Client>ile paylaşın. - Timeout belirleyin: Residential proxy'lerde ortalama latency 1-3 saniyedir. 30 saniye üst limit, 10 saniye bağlantı timeout iyi başlangıçtır.
- Retry ile exponential backoff:
fetch_with_retryörneğindeki gibi bağlantı hatalarında artan bekleme süresiyle tekrar deneyin. - Rate-limit'e saygı: Aynı anda 500+ istek atmak proxy sağlayıcının rate-limit'ini tetikleyebilir.
tokio::sync::Semaphoreile concurrency'yi sınırlayın. - Etik kurallar:
robots.txt'i kontrol edin, ToS ihlali yapmayın, GDPR/CCPA kapsamındaki kişisel verileri toplayacaksanız hukuki danışmanlık alın.
Hangi Proxy Türü Ne Zaman?
| Senaryo | Proxy Türü | Neden |
|---|---|---|
| SERP scraping | Residential + geo-targeting | Arama motorları datacenter IP'leri bloklar |
| Fiyat karşılaştırma | Residential (dönen) | E-ticaret siteleri rate-limit uygular |
| API proxy (hızlı) | Datacenter | Düşük latency, blok riski yok |
| Sosyal medya araştırması | Mobile proxy | Platformlar mobil IP'lere daha az şüphelenir |
| Sneaker/ticketing | Residential + sticky session | Oturum tutarlılığı gerekir |
ProxyHat, tüm bu senaryoları 70+ lokasyonda residential, mobile ve datacenter proxy'lerle karşılıyor. Detaylı fiyatlandırma için pricing sayfasını inceleyin.
Key Takeaways
- reqwest ile proxy yapılandırmak tek satırlık iş —
Proxy::all()tüm trafiği,Proxy::http()yalnızca HTTP'yi yönlendirir. - hyper ile CONNECT tüneli manuel kontrol edebilir, düşük seviye ihtiyaçlarınızı karşılayabilirsiniz.
- tokio::task::JoinSet ile yüzlerce proxy'li isteği eşzamanlı ve yönetilebilir şekilde çalıştırın.
- Rotating proxy havuzu trait'i, farklı sağlayıcı ve stratejileri değiştirilebilir kılar — test edilebilirlik için kritik.
- thiserror ile tip güvenli hata yönetimi,
unwrap()paniklerinden kurtarır ve retry mantığını temizler. - rustls cross-compile ve küçük binary için; native-tls işletim sistemi sertifika deposu için.
- Feature flags ile proxy desteğini derleme zamanında açıp kapatın — CI ve test süreçlerinizi hızlandırır.
Rust ile yüksek performanslı scraping altyapısı kurmak istiyorsanız, web scraping kullanım senaryolarımızı ve SERP tracking çözümümüzü inceleyin. ProxyHat residential proxy'leri ile Rust HTTP proxy entegrasyonunuzu bugün başlatın.






