aiohttp, bir veri toplayıcının yüzlerce aktif istek gerektirdiği ve olay döngüsü üzerinde tam kontrol sahibi olmak istediğinizde tercih edeceğiniz istemcidir. Proxy üzerinden yönlendirmek birkaç anahtar kelime argümanı ile mümkündür, ancak kimlik doğrulama, bağlantı sınırları ve zaman aşımları ile ilgili varsayılan ayarlar requests ve httpx'ten farklıdır ve çoğu aiohttp proxy kurulumunda sorunların kaynağı da budur. Bu kılavuzun sonunda, dönen bir konut ağ geçidi üzerinden kimlik doğrulaması yapan, sınırlı sayıda paralel istek çalıştıran, zaman aşımlarını öngörülebilir şekilde gerçekleştiren ve proxy hatalarını türüne göre raporlayan bir ClientSession'ya sahip olacaksınız.
Örnekler, Python 3.11 veya daha yeni sürümlerde aiohttp 3.14'ü hedeflemektedir. Son sürümlerde davranışta değişiklik olduğu durumlarda, sürüm belirtilmiştir.
Oturumda bir aiohttp proxy'si yapılandırma
proxy anahtar sözcüğü hem istek bazında hem de oturum düzeyinde kabul edilir. İstek bazında kullanım, orijinal API'dir ve hâlâ çalışmaktadır; oturum düzeyinde kullanım ise bir veri toplayıcı için tercih etmeniz gereken seçenektir, çünkü bu sayede tek bir çağrının proxy'den kaçma olasılığını ortadan kaldırır.
import asyncio
import aiohttp
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
async def main() -> None:
async with aiohttp.ClientSession(proxy=PROXY) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(resp.status, await resp.json())
asyncio.run(main())
9000 numaralı bağlantı noktası, Geonode'nun HTTP(S) dönen ağ geçididir; bu sayede her yeni bağlantı farklı bir ev IP adresinden yapılır. Tek bir çağrı, kendi proxy= argümanıyla varsayılan ayarı geçersiz kılabilir; bu, bir isteğin doğrudan, örneğin dahili metrik uç noktasına gitmesi gerektiğinde kullanışlıdır.
İstek Başına Biçim
async with aiohttp.ClientSession() as session:
async with session.get("https://api.ipify.org?format=json", proxy=PROXY) as resp:
print(await resp.json())
Proxy'nin çağrıdan çağrıya değiştiği durumlarda bunu kullanın; örneğin, bir havuz yöneticisinin görev başına farklı bir yapışkan oturum tahsis etmesi gibi.
Kimlik Doğrulama: URL kimlik bilgileri veya proxy_headers
Proxy kimlik bilgilerini göndermek için desteklenen iki yöntem ve eski öğreticilerde hâlâ görebileceğiniz, artık kullanılmayan bir yöntem bulunmaktadır.
| Yöntem | Örnek | Durum |
|---|
| Proxy URL'sinde kimlik bilgileri | proxy="http://user:pass@host:9000" | Destekleniyor |
proxy_headers ile encode_basic_auth | proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")} | Destekleniyor, önerilir |
proxy_auth=aiohttp.BasicAuth("user", "pass") | | 3.14 sürümünden itibaren kullanımdan kaldırıldı, 4.0 sürümünde tamamen kaldırıldı |
URL biçimi en kısadır. Başlık biçimi, gizli bilgiyi URL'nin dışında tutar; bu, proxy URL'lerinin günlüklerde veya istisna mesajlarında yer alması durumunda önemlidir. aiohttp.encode_basic_auth, hazır Basic ... dizesini döndürür:
import asyncio
import os
import aiohttp
PROXY = "http://premium-residential.geonode.com:9000"
async def main() -> None:
proxy_headers = {
"Proxy-Authorization": aiohttp.encode_basic_auth(
os.environ["GEONODE_USER"], os.environ["GEONODE_PASS"]
)
}
async with aiohttp.ClientSession(proxy=PROXY, proxy_headers=proxy_headers) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(await resp.json())
asyncio.run(main())
Kod tabanınız hâlâ proxy_auth=aiohttp.BasicAuth(...) kullanıyorsa, 3.14 sürümünde DeprecationWarning ile çalışır. Hemen geçiş yapın: parametre ve sınıf, aiohttp 4.0 sürümünde kaldırılmıştır ve değiştirme işlemi tek satırlık bir değişiklikle gerçekleştirilebilir.
asyncio.gather ve bir Semaphore ile paralel istekler
aiohttp’yi kullanmanın nedeni eşzamanlılıktır; eşzamanlılığın proxy’leri bozmasının nedeni ise varsayılan olarak bunu sınırlayan hiçbir şeyin olmamasıdır. Bin adetten fazla korutin içeren bir asyncio.gather, bin adet bağlantı açmaya çalışacaktır; konektör bunu 100 ile sınırlar ve geri kalanı aiohttp içinde kuyruğa alınır; bu, hiç olmamasından iyidir ancak hız ayarı üzerinde kontrol sağlamaz. Semaphore, sınırı açıkça belirtir ve konektörden bağımsız olarak ayarlamanıza olanak tanır.
import asyncio
import aiohttp
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
CONCURRENCY = 20
async def fetch(session: aiohttp.ClientSession, sem: asyncio.Semaphore, url: str) -> tuple[str, int]:
async with sem:
async with session.get(url) as resp:
await resp.read()
return url, resp.status
async def main() -> None:
urls = [f"https://httpbin.org/get?i={i}" for i in range(100)]
sem = asyncio.Semaphore(CONCURRENCY)
connector = aiohttp.TCPConnector(limit=CONCURRENCY, ttl_dns_cache=300)
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
async with aiohttp.ClientSession(
proxy=PROXY, connector=connector, timeout=timeout
) as session:
results = await asyncio.gather(
*(fetch(session, sem, u) for u in urls), return_exceptions=True
)
ok = sum(1 for r in results if not isinstance(r, Exception))
print(f"{ok}/{len(urls)} succeeded")
asyncio.run(main())
return_exceptions=True, tek bir başarısız isteğin tüm grubu iptal etmesini engeller. Semaforu konektör sınırıyla eşleştirin; böylece hiçbir taraf, diğer tarafın tüketemeyeceği işleri sessizce kuyruğa almaz.
TCPConnector: limit ve limit_per_host
Bağlayıcı, soket havuzunu yönetir. aiohttp proxy trafiği için iki parametre önemlidir.
limit, eşzamanlı bağlantıların toplam sayısıdır; varsayılan değer 100’dür. limit_per_host, bağlantıları bir (host, port, is_ssl) üçlüsüyle sınırlar; varsayılan değer 0'dır ve sınırsız anlamına gelir. Bir proxy aracılığıyla, her hedefe yapılan her istek, proxy'nin ana bilgisayarını ve bağlantı noktasını paylaşır; bu nedenle, ana bilgisayar başına sınır, hedef sitelere değil ağ geçidine uygulanır. Tamamen proxy iş yükü için, limit'yu istediğiniz eşzamanlılık sayısına ayarlayın ve limit_per_host'yu 0 olarak bırakın; limit_per_host'yu yalnızca aynı oturumun kaynak sunucularla doğrudan iletişim kurduğu ve bunları korumak istediğiniz durumlarda ayarlayın.
connector = aiohttp.TCPConnector(limit=30, limit_per_host=10, ttl_dns_cache=300)
ttl_dns_cache=300, ağ geçidinin DNS yanıtını varsayılan on saniye yerine beş dakika boyunca saklar; bu da çoğu yeni bağlantıda çözümleyicinin gidiş-dönüş süresini ortadan kaldırır. Oturum, konektörün sahipliğini alır ve çıkışta onu kapatır; connector_owner=False değerini yalnızca bir konektörü oturumlar arasında kasıtlı olarak paylaşıyorsanız atayın.
5 dakikalık varsayılan süre yerine ClientTimeout
aiohttp'nin varsayılan toplam zaman aşımı süresi 300 saniyedir. Bu süre, doğrudan istekler için yeterli olsa da, tıkanan bir ev tipi çıkış sunucusunun hızlı bir şekilde hata vermesi ve başka bir IP adresinden yeniden deneme yapılabilmesi gereken proxy trafiği için çok uzundur.
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
total, yönlendirmeler ve gövde indirme dahil olmak üzere tüm isteği sınırlar. connect, havuzdan bağlantı kurulması ile TCP ve proxy el sıkışmasını sınırlar. sock_read, ardışık baytlar arasındaki aralığı sınırlar; bu, bağlantıyı kabul edip ardından veriyi yavaş yavaş ileten hedefleri yakalar. Varsayılan değer için nesneyi oturuma, geçersiz kılmak için ise tek bir isteğe aktarın. Süre dolumu, aşağıda ele alınacak olan asyncio.TimeoutError'yu tetikler.
trust_env ve ortam vekilleri
requests'dan farklı olarak, aiohttp, trust_env=True ile etkinleştirilmedikçe HTTP_PROXY, HTTPS_PROXY ve NO_PROXY'yu yok sayar. Etkinleştirildiğinde urllib.request.getproxies()'yu kullanır, no_proxy'yu dikkate alır ve varsa ~/.netrc'dan proxy kimlik bilgilerini de okur.
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(await resp.json())
Her zaman belirli bir ağ geçidini kullanması gereken bir veri toplayıcı için, trust_env'yu devre dışı bırakın ve proxy='yu açıkça ayarlayın. Bu şekilde, bir CI çalıştırıcısındaki kurumsal proxy yönlendirmeyi ele geçiremez ve no_proxy bir hedefi gizlice hariç tutamaz.
SOCKS5 ile aiohttp-socks
aiohttp’te yerleşik SOCKS desteği bulunmadığından, SOCKS5 üzerinden çalışan bir aiohttp proxy’si için üçüncü taraf bir bağlayıcı gereklidir. aiohttp-socks paketi, TCPConnector'nun yerini alan ve SOCKS el sıkışmasını yöneten bir ProxyConnector sağlar. Bunu pip install aiohttp-socks ile birlikte kurun. Geonode'nun SOCKS5 dönen bağlantı noktası 11000'dir:
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector
SOCKS_URL = "socks5://USERNAME:PASSWORD@premium-residential.geonode.com:11000"
async def main() -> None:
connector = ProxyConnector.from_url(SOCKS_URL)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(await resp.json())
asyncio.run(main())
Oturumda ProxyConnector ile proxy='yu birleştirmeyin; konektör zaten her şeyi yönlendirir. ProxyConnector, TCPConnector ile aynı limit ve limit_per_host anahtar kelimelerini kabul eder.
TLS-in-TLS sınırlaması
aiohttp, HTTP proxy’leriyle iletişim kurar ve https:// hedeflerine CONNECT komutuyla bu proxy’ler üzerinden tünel oluşturur. Ancak, TLS-in-TLS'nin Python sürümleri arasında farklılık gösteren asyncio aktarım desteğine bağlı olması nedeniyle, proxy'nin kendisine (yani bir https:// proxy URL'sine) TLS üzerinden güvenilir bir şekilde bağlanamaz. Belgelerde bunun varsayılmaması gerektiği açıkça belirtilmiştir. Uygulamada bu durum Geonode için önemli değildir: ağ geçidine 9000/10000 numaralı bağlantı noktalarında düz HTTP üzerinden erişilir ve https:// trafiğiniz CONNECT tüneli içinde yine de uçtan uca şifrelenir; bu nedenle proxy URL şemasını http:// olarak koruyun.
Çok adımlı akışlar için sabit oturumlar
Dönen bağlantı noktası, her bağlantı için yeni bir IP adresi verir. Oturum açma akışları veya sayfalandırılmış listeler için birkaç dakika boyunca aynı IP adresine ihtiyacınız vardır; Geonode, bu adresi HTTP(S) sabit bağlantı noktası 10000 üzerinden kullanıcı adı aracılığıyla sağlar:
import secrets
import aiohttp
def sticky_proxy(country: str = "us", lifetime: int = 600) -> str:
session_id = secrets.token_hex(4) # 8 alphanumeric characters
user = f"USERNAME-country-{country}-session-{session_id}-lifetime-{lifetime}"
return f"http://{user}:PASSWORD@premium-residential.geonode.com:10000"
session = aiohttp.ClientSession(proxy=sticky_proxy())
Geçerlilik süresi 180 ile 86400 saniye arasında değişir. Eyalet ve şehir hedeflemeleri birleştirilemez; asn ise ülke seçilmesini gerektirir. İsteğe bağlı olarak rotasyon yapmak için yeni bir oturum kimliği oluşturmanız yeterlidir.
Tanılama: Hangi istisna ne anlama gelir
| İstisna | Anlamı | Tipik çözüm |
|---|
aiohttp.ClientProxyConnectionError | Proxy sunucusuna TCP bağlantısı açılamadı | Sunucu adını, bağlantı noktasını ve güvenlik duvarını kontrol edin; connect zaman aşımı süresini artırın |
aiohttp.ClientHttpProxyError ve .status == 407 | Proxy, CONNECT sırasında kimlik bilgilerini reddetti | Kullanıcı adını veya şifreyi düzeltin; kullanıcı adı seçeneğinin dilbilgisini kontrol edin |
aiohttp.ClientHttpProxyError ile 502/503 | Ağ geçidi tüneli kuramadı | Yeni bir oturum kimliği ile yeniden deneyin; bağlantı noktasının protokol ile eşleştiğini doğrulayın |
asyncio.TimeoutError | Bir ClientTimeout alanı süresi doldu | Yeni bir IP adresiyle yeniden deneyin; sock_read'yu ayarlayın |
aiohttp.ClientConnectorError | Genel bağlantı hatası (DNS, reddedildi) | .os_error'yu inceleyin |
ClientProxyConnectionError, ClientConnectorError'nun bir alt sınıfıdır ve ClientHttpProxyError, ClientResponseError'nun bir alt sınıfıdır; bu nedenle önce spesifik olanları yakalayın.
import asyncio
import aiohttp
PROXY = "http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
async def fetch_ip(session: aiohttp.ClientSession) -> str | None:
try:
async with session.get("https://api.ipify.org?format=json") as resp:
resp.raise_for_status()
return (await resp.json())["ip"]
except aiohttp.ClientHttpProxyError as e:
if e.status == 407:
print("proxy auth failed: check credentials")
else:
print("proxy returned", e.status)
except aiohttp.ClientProxyConnectionError as e:
print("cannot reach proxy:", e)
except asyncio.TimeoutError:
print("timed out through proxy")
return None
async def main() -> None:
timeout = aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)
async with aiohttp.ClientSession(proxy=PROXY, timeout=timeout) as session:
print(await fetch_ip(session))
await asyncio.sleep(0.25) # let SSL transports close before the loop stops
asyncio.run(main())
Dikkate alın ki, düz bir http:// hedefi için proxy'nin 407 kodu bir istisna yerine sıradan bir yanıt olarak gelir; bu nedenle orada da resp.status'yu kontrol edin.
Düzgün Kapatma
Bir oturum kapandığında, asyncio hala altta yatan SSL aktarımlarını sonlandırıyor olabilir. Olay döngüsü önce durursa, ResourceWarning: unclosed transport hatası alırsınız. Belgelenmiş çözüm, oturumun async with bloğundan sonra kısa bir duraklama yapmaktır: düz HTTP oturumları için await asyncio.sleep(0), oturumun HTTPS istekleri yaptığı durumlarda ise await asyncio.sleep(0.25); bu durum, CONNECT tüneli üzerinden yapıldığı için neredeyse her zaman geçerlidir. Yukarıdaki örnekte bu işlem yer almaktadır.
Sık yapılan hatalar
Her istek için bir oturum oluşturmak
ClientSession, bağlantı havuzunu ve çerez havuzunu barındırır. Her istek için bir tane açmak, ağ geçidine yönelik keep-alive özelliğini etkisiz hale getirir, proxy el sıkışma işlemlerini katlar ve her çağrıda kapatma uyarısını tetikler. Her işçi için bir oturum oluşturun ve bunu paylaşın.
Proxy'nin tek "ana bilgisayar" olduğunu unutmak
limit_per_host düşük bir sayıya ayarlandığında, proxy üzerinden geçen her istek ağ geçidinin tek (host, port) girdisine sayılır; bu nedenle, ana bilgisayar başına 5 bağlantılık sınır, tüm kazıyıcınızı 5 bağlantıyla sınırlar. Bunun yerine limit'yu ayarlayın veya limit_per_host'yu 0 olarak bırakın.
Varsayılan zaman aşımına güvenmek
Beş dakika, tıkanmış bir ev tipi çıkış sunucusunun tüm zaman aralığı boyunca bir semafor yuvasını tutmasına yetecek kadar uzun bir süredir. Her zaman ClientTimeout değerini connect ve sock_read değerleriyle birlikte geçirin.
Hala proxy_auth iletiliyor
Şu anda bir uyarıyla çalışıyor ancak 4.0 sürümünde hata veriyor. Bunu proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(user, pw)} ile değiştirin veya kimlik bilgilerini proxy URL'sine ekleyin.
Sonraki adım
Yukarıdaki oturumda, aiohttp proxy trafiğinin kimlik doğrulamasını, sınırlandırılmasını ve izlenebilirliğini sağladınız; geriye kalan ise trafiğin geçtiği havuzdur. Geonode ev tipi proxy’ler, burada kullanılan uç noktalara hizmet vermektedir: 2,5 milyon çevrimiçi IP, 195'ten fazla ülkede aylık olarak dönüşümlü olarak kullanılan 30–50 milyon benzersiz IP; kullanıcı adı aracılığıyla ülke, eyalet, şehir ve ASN hedefleme ile HTTP, HTTPS ve SOCKS5 üzerinden 24 saate kadar kalıcı oturumlar. Fiyatlandırma, toplu alımlarda GB başına 0,27 $'dan başlar; giriş seviyesi planı aylık 7,92 $ karşılığında 10 GB'dir ve 3 günlük deneme sürümü sözleşmesiz olarak 5 $'dır. Web kazıma ve fiyat istihbaratı kullanım örnekleri, ekiplerin bu konektör kurulumunu yeniden deneme ve zamanlama mantığıyla nasıl birleştirdiğini göstermektedir.
SSS
Bir oturumun tamamı için aiohttp proxy'sini nasıl ayarlayabilirim?
proxy= değerini ClientSession oluşturucusuna (aiohttp.ClientSession(proxy="http://user:pass@host:port")) aktarın. Çağrı, proxy= değerini başka bir değerle geçersiz kılmadıkça, o oturumdan gelen her istek bunu kullanır. Oturum düzeyinde proxy, aiohttp 3.11 sürümünden itibaren kullanılabilir; daha eski sürümlerde ise bunun yerine her get() veya post() çağrısında proxy= parametresini geçmeniz gerekir.
aiohttp'de proxy_auth neden kullanımdan kaldırıldı ve yerine ne geçti?
aiohttp 3.14 sürümünden itibaren proxy_auth parametresi ve aiohttp.BasicAuth sınıfı kullanımdan kaldırıldı ve 4.0 sürümünde tamamen kaldırılması planlanıyor. Bunun yerine, basit bir başlık olan proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")} kullanılmaktadır. Bu, yardımcı sınıf olmadan aynı Base64 kodlamasını gerçekleştirir ve hem istek bazında hem de oturum düzeyinde çalışır.
aiohttp, HTTPS proxy'lerini veya SOCKS5'yu destekliyor mu?
aiohttp, HTTP proxy'leriyle doğal olarak iletişim kurar ve CONNECT komutunu kullanarak https:// hedeflerini bu proxy'ler üzerinden tünelleştirir. TLS üzerinden proxy'ye bağlanmak (TLS-in-TLS), Python derlemenizdeki asyncio desteğine bağlıdır ve bu yönteme güvenilmemelidir. SOCKS4 ve SOCKS5 için üçüncü taraf aiohttp-socks paketi ve buna ait ProxyConnector gereklidir.
Bir aiohttp proxy üzerinden kaç tane eşzamanlı istek çalıştırabilirim?
TCPConnector, varsayılan olarak ana bilgisayar başına sınır olmaksızın 100 eşzamanlı bağlantıya izin verir. Proxy üzerinden her istek aynı ana bilgisayarı ve bağlantı noktasını paylaşır; bu nedenle limit'yu açıkça ayarlayın, doğrudan çağrılar da yapıyorsanız limit_per_host'yu ekleyin ve kuyruğa alınan görevlerin hepsinin aynı anda soket açmaması için istek korutinlerini bir asyncio.Semaphore içine alın.
Proxy üzerinden yaptığım aiohttp isteği neden hata vermek yerine donuyor?
ClientSession'nun varsayılan olarak 5 dakikalık toplam zaman aşımı süresi vardır; bu, ev tipi çıkış sunucusu tıkanırsa donma gibi görünür. aiohttp.ClientTimeout(total=30, connect=10, sock_read=20)'yu oturuma veya isteğe aktarın; böylece tıkanmalar hızlı bir şekilde asyncio.TimeoutError olarak ortaya çıkar ve beklemek yerine yeni bir oturum kimliği ile yeniden deneyin.
Kazıyıcıları konut tipi proxy'ler üzerinden yönlendirmenin pratik sınırları nelerdir?
Bir proxy yalnızca kaynak IP'yi değiştirir. Robots.txt dosyasını, site şartlarını veya hız sınırlamalarını göz ardı etme izni vermez ve kişisel verilerin toplanmasını yasal hale getirmez. Eşzamanlılığı makul seviyede tutun, 429 hatalarında geri çekilin ve Geonode yapışkan oturumlarının, IP adresinin serbest bırakılmasından önce en fazla 24 saat sürdüğünü unutmayın.