httpx kütüphanesi, Python'da asenkron HTTP istekleri için standart haline geldi, ancak proxy ile çalışırken, üretimdeki parseleri kıran bazı nüanslar var. Asenkron proxy döngüsünü nasıl ayarlayacağımızı, HTTP/2'yi nasıl etkinleştireceğimizi ve neden her 50-100 istekte bir scriptinizin çöktüğünü inceleyelim.
Neden httpx, requests veya aiohttp değil
requests senkron bir kütüphanedir ve kutudan çıktığı gibi HTTP/2'yi desteklemez. aiohttp asenkron bir kütüphanedir, ancak proxy ayarları daha az kullanıcı dostudur ve HTTP/2 desteği yoktur. httpx her iki sorunu da çözer: senkron ve asenkron bir istemci ile tek bir API, h2 uzantısı aracılığıyla yerel HTTP/2 desteği ve taşıma düzeyinde proxy ile esnek çalışma.
Pazar yerleri (Wildberries, Ozon, Avito), sosyal medya API'lerinden veri toplama veya rakip fiyatlarını izleme gibi parselama görevleri için asenkronluk kritik öneme sahiptir — bu, ardışık isteklere göre hızda katlanarak artış sağlar. Ancak, akıllıca bir proxy döngüsü ve bağlantı yönetimi olmadan asenkronluk yeni sorunların kaynağı haline gelir: bağlantı havuzunun tükenmesi, takılı kalan korutinler, dosya tanımlayıcılarının sızıntısı.
İkinci önemli nokta — HTTP/2, bir TCP bağlantısında birden fazla isteği çoklayabilir, bu da hedef sunucu üzerindeki yükü azaltır ve bir IP'den gelen TCP el sıkışmalarının sayısına bakan bazı basit anti-bot sistemlerini aşmaya yardımcı olur.
httpx'te proxy için temel ayar
httpx 0.26+ sürümünden itibaren proxy ayarlarının sözdizimi değişti — proxies yerine proxy parametresi kullanılıyor. Eski eğitimlere bakıyorsanız, bu sıkça kafa karışıklığına neden olur.
import httpx
import asyncio
PROXY_URL = "http://user:[email protected]:8000"
async def fetch(url: str):
async with httpx.AsyncClient(proxy=PROXY_URL, timeout=15.0) as client:
response = await client.get(url)
return response.status_code, response.text[:200]
asyncio.run(fetch("https://httpbin.org/ip"))
HTTP ve HTTPS trafiği için proxy'leri ayırmanız veya farklı alanlar için farklı proxy'ler ayarlamanız gerekiyorsa, httpx.Client(mounts=...) kullanın — taşıyıcılar URL şablonuna göre monte edilir:
mounts = {
"http://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
"https://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
}
async with httpx.AsyncClient(mounts=mounts) as client:
response = await client.get("https://example.com")
Kullanıcı adı ve şifre ile kimlik doğrulama için http://user:pass@host:port formatı, HTTP proxy'leri için güvenilir bir şekilde çalışır. SOCKS5 için ayrıca httpx[socks] yüklemeniz gerekir, aksi takdirde çalışma zamanında ImportError alırsınız, kod yazma aşamasında değil — bu, birçok kişinin düştüğü bir tuzaktır.
Asenkron proxy döngüsü: çalışan kod
Proxy döngüsündeki en büyük hata — her istek için yeni bir AsyncClient oluşturmaktır. Bu, performansı öldürür çünkü istemci bir bağlantı havuzunu tutar ve yeniden oluşturulması, keep-alive bağlantılarının tüm avantajlarını ortadan kaldırır. Doğru yaklaşım — önceden bir proxy için bir istemci havuzu oluşturmak ve görevleri bunlar arasında dağıtmaktır.
import httpx
import asyncio
import itertools
import random
PROXIES = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
class ProxyPool:
def __init__(self, proxies: list[str]):
self.clients = [
httpx.AsyncClient(proxy=p, timeout=httpx.Timeout(15.0, connect=5.0))
for p in proxies
]
self._cycle = itertools.cycle(self.clients)
def get_client(self) -> httpx.AsyncClient:
return next(self._cycle) # round-robin
# rastgele seçim için: random.choice(self.clients)
async def close_all(self):
for client in self.clients:
await client.aclose()
async def fetch_with_retry(pool: ProxyPool, url: str, retries: int = 3):
for attempt in range(retries):
client = pool.get_client()
try:
response = await client.get(url)
if response.status_code == 200:
return response.text
except (httpx.ConnectTimeout, httpx.ProxyError, httpx.ReadTimeout):
await asyncio.sleep(0.5 * (attempt + 1))
continue
return None
async def main():
pool = ProxyPool(PROXIES)
urls = [f"https://example.com/page/{i}" for i in range(100)]
sem = asyncio.Semaphore(20) # paralelliği sınırlıyoruz
async def bound_fetch(url):
async with sem:
return await fetch_with_retry(pool, url)
results = await asyncio.gather(*(bound_fetch(u) for u in urls))
await pool.close_all()
return results
asyncio.run(main())
Bu desen, üç sorunu aynı anda çözer: keep-alive aracılığıyla TCP bağlantılarını yeniden kullanır, proxy'ler arasında round-robin yöntemiyle yük dağıtır ve hedef sunucuyu çökertmemek ve rate-limit nedeniyle yasaklanmayı tetiklememek için paralelliği sınırlamak için bir semafor kullanır.
Wildberries, Ozon veya Instagram API'si ile çalışan üretim parselerinin, proxy havuzunun coğrafi olarak yeterince büyük ve çeşitli olması önemlidir — aksi takdirde, tek bir alt ağ içindeki döngü, engellemeyi önlemez. Burada, gerçek kullanıcıların IP'lerine sahip olan ve anti-bot sistemlerinin ASN ve IP aralığının itibarını analiz etmesi nedeniyle daha az filtreye takılan rezidans proxy'leri iyi bir seçimdir.
httpx'te HTTP/2: neden ve nasıl etkinleştirilir
Varsayılan olarak httpx HTTP/1.1 kullanır. HTTP/2'yi etkinleştirmek için uzantıyı yüklemeniz ve bayrağı açıkça iletmeniz gerekir:
# pip install httpx[http2]
async with httpx.AsyncClient(http2=True, proxy=PROXY_URL) as client:
response = await client.get("https://example.com")
print(response.http_version) # "HTTP/2" bekliyoruz
Önemli bir nokta: Tüm proxy'ler HTTP/2 tünelleme desteğine sahip değildir. Bazı ucuz veri merkezi proxy'leri, son sunucu HTTP/2'yi desteklese bile yalnızca HTTP/1.1 proxy olarak çalışır — bu durumda httpx sessizce CONNECT tüneli aracılığıyla HTTP/1.1'e geri döner ve bunu açık bir kontrol olmadan her zaman fark etmeyebilirsiniz response.http_version.
HTTP/2, üç senaryoda gerçek bir kazanç sağlar: bir alan adına çok sayıda istek yaptığınızda (bir bağlantıda çoklama gecikmeleri azaltır), anti-bot koruması için isteklerin görünürlüğünü azaltmak önemli olduğunda (bir IP'den daha az TCP el sıkışması daha doğal görünür) ve hedef site kendisi HTTP/2 talep ettiğinde ve HTTP/1.1'deki istemcilere hata veya kısıtlı içerik döndürdüğünde — bu bazen Cloudflare üzerinde bulunan sitelerde görülür.
Proxy aracılığıyla HTTP/2 bağlantılarının hızı ve istikrarı önemliyse, özel veri merkezi proxy'leri daha iyi bir seçimdir — bunlar rezidans proxy'lerinden daha hızlıdır ve genellikle modern protokollerin tünellemesini doğru bir şekilde destekler.
httpx ile proxy'de 5 yaygın hata
1. Her istek için istemciyi yeniden oluşturma
async with httpx.AsyncClient(...) as client: await client.get(url) şeklindeki kod döngü içinde bir antipattern'dır. Her istemci oluşturma, yeni bir TCP bağlantısı, yeni bir TLS el sıkışması ve bağlantı havuzunun tamamen göz ardı edilmesi anlamına gelir. 1000 istek için doğru ve yanlış yaklaşım arasındaki hız farkı 5-10 katına kadar çıkabilir.
2. Zaman aşımı yok veya varsayılan olarak çok büyük zaman aşımı
Varsayılan olarak httpx, her şey için 5 saniyelik bir zaman aşımı ayarlar — bağlantı, okuma, yazma, havuz. Dengesiz proxy'ler ile çalışırken bu, bağlantı için yetersiz, ancak proxy "takılı" kaldığında okuma için çok fazla. Zaman aşımını açıkça ayırın:
timeout = httpx.Timeout(
connect=5.0,
read=10.0,
write=10.0,
pool=5.0
)
client = httpx.AsyncClient(proxy=PROXY_URL, timeout=timeout)
3. Bağlantı havuzunun sınırlarını göz ardı etme
Çok sayıda eşzamanlı görev (500+ korutini semafor olmadan toplamak) olduğunda httpx PoolTimeout hatası vermeye başlar, çünkü havuzdaki bağlantı limiti (varsayılan olarak 100) tükenmiştir. Sınırları açıkça httpx.Limits(max_connections=200, max_keepalive_connections=50) ile ayarlayın ve her zaman paralelliği kontrol etmek için bir semafor kullanın.
4. Doğru sonuçları anlamadan doğrulamayı devre dışı bırakma
Birçok kişi, proxy ile MITM sertifikaları aracılığıyla SSL hatalarından kurtulmak için verify=False kodunu Stack Overflow'dan kopyalar. Bu, "adam ortada" saldırılarına kapı açar ve tehlikeli bir proxy aracılığıyla veri sızıntısına neden olabilir. Proxy özel bir sertifika gerektiriyorsa — bunu açıkça verify="/path/to/cert.pem" ile ekleyin, tüm kontrolü devre dışı bırakmayın.
5. Kapatılmamış istemciler ve dosya tanımlayıcılarının sızıntısı
Eğer istemci havuzunu manuel olarak oluşturuyorsanız (yukarıdaki örnekte olduğu gibi), her birinin iş bitiminde await client.aclose() çağrısını yapmayı unutmayın. Uzun süre çalışan scriptler, açık bağlantıların sayısı için işletim sisteminin (genellikle Linux'ta varsayılan olarak 1024) sınırına ulaşır ve parseler gizemli bir Too many open files hatası ile birkaç saat içinde çöker.
Hangi proxy türü parselama için seçilmeli
Proxy seçimi, yukarıdaki listedeki hataların ne kadarını göreceğinizi doğrudan etkiler — dengesiz proxy'ler zaman aşımına ve bağlantı kopmalarına neden olur, kod mükemmel yazılmış olsa bile.
| Proxy Türü | Hız | Engellenme Riski | Ne Zaman Kullanılmalı |
|---|---|---|---|
| Veri Merkezi | Yüksek | Korunan sitelerde yüksek | Açık API, zayıf anti-bot koruması |
| Rezidans | Orta | Düşük | Wildberries, Ozon, sosyal medya |
| Mobil | Orta-düşük | Minimum | Instagram, TikTok API, sıkı koruma |
Orta düzeyde korumaya sahip API'lere yüksek hızda istek yapmak için asenkron parselama için veri merkezi proxy'leri ekonomik bir seçenek olmaya devam ediyor — önemli olan, yükü havuzda eşit bir şekilde dağıtmaktır, yukarıdaki kodda gösterildiği gibi.
Üretim öncesi kontrol listesi
- httpx istemcileri bir kez oluşturulur ve her istek için yeniden kullanılmaz
- Zaman aşım süreleri bağlantı/okuma/yazma/havuz olarak ayrılmıştır, varsayılan kullanılmaz
- Paralellik
asyncio.Semaphoreile sınırlıdır - Bağlantı havuzunun sınırları gerçek yük altında ayarlanmıştır
- ProxyError, ConnectTimeout, ReadTimeout ile hata işleme mantığı vardır
- Tüm istemciler iş bitiminde açıkça
aclose()ile kapatılır - Gerekmedikçe verify=False kullanılmaz
- HTTP/2 bilinçli olarak etkinleştirilmiş ve
response.http_versionile kontrol edilmiştir
Sonuç
httpx, proxy ve HTTP/2 ile asenkron çalışma için güçlü bir araç seti sunar, ancak istemci havuzlarının, zaman aşım sürelerinin ve bağlantı sınırlarının dikkatlice ayarlanmasını gerektirir. Üretimdeki parseler genellikle kütüphaneden değil, mimari hatalardan kaynaklanır: istemcilerin yeniden oluşturulması, paralelliğin sınırlandırılmaması ve proxy hatalarının yanlış işlenmesi.
Eğer pazar yerleri veya sosyal medya API'leri için bir parsel oluşturuyorsanız, şüpheli etkinlikleri aktif olarak izleyen, proxy altyapısının istikrarı kod kadar önemlidir. Bu tür görevler için rezidans proxy'lerine bakmak iyi bir fikir olabilir — bunlar, ucuz veri merkezi havuzlarına göre daha yüksek IP itibarına sahip oldukları için ProxyError ve ConnectTimeout hatalarının oranını azaltır.