Async स्क्रेपिंग वह जगह है जहाँ प्रॉक्सी सेटअप चुपचाप बिखर जाते हैं। requests के साथ आप प्रति कॉल एक IP रोटेट करते हैं और आगे बढ़ जाते हैं; asyncio के साथ अचानक आपके पास 200 अनुरोध उड़ान में होते हैं, सभी को अपने स्वयं के exit, अपनी स्वयं की session affinity, और एक target द्वारा 403 फेंकने पर अपने स्वयं के retry की ज़रूरत होती है। ग़लती करें और आप या तो एक IP को बैन तक पीट देते हैं या मध्य-फ़्लो रोटेट करके हर लॉगिन session को चीर देते हैं।
यह गाइड वे पैटर्न दिखाती है जो वास्तव में concurrency पर टिकते हैं: httpx और aiohttp में प्रति-अनुरोध रोटेशन, एक सीमित वर्कर पूल, stateful फ़्लो के लिए sticky sessions, और block-aware retries — काम करने वाले कोड के साथ जिसे आप पेस्ट कर सकते हैं।
एक सिंक्रोनस स्क्रेपर एक समय में एक प्रॉक्सी को छूता है, इसलिए "हर अनुरोध पर रोटेट करें" तुच्छ रूप से सही है। Async एक साथ तीन धारणाएँ तोड़ देता है:
httpx.AsyncClient प्रति क्लाइंट एक प्रॉक्सी बाँधता है, इसलिए रोटेट करने के लिए आप प्रति अनुरोध प्रॉक्सी चुनते हैं और exit द्वारा कीड किए गए क्लाइंटों के एक छोटे पूल से रूट करते हैं:
import asyncio, itertools, httpx
PROXIES = [
"socks5h://USERNAME:PASSWORD@us.jibaoproxy.com:913",
"socks5h://USERNAME:PASSWORD@de.jibaoproxy.com:913",
"socks5h://USERNAME:PASSWORD@jp.jibaoproxy.com:913",
]
pool = itertools.cycle(PROXIES)
# One reusable client per exit (connection pooling stays intact)
clients = {p: httpx.AsyncClient(proxy=p, timeout=30) for p in PROXIES}
async def fetch(url):
proxy = next(pool)
r = await clients[proxy].get(url)
return r.status_code, r.text
async def main(urls):
results = await asyncio.gather(*(fetch(u) for u in urls))
for client in clients.values():
await client.aclose()
return results
प्रति exit एक क्लाइंट को पुनः उपयोग करना HTTP/2 और connection pooling को जीवित रखता है, बजाय हर अनुरोध पर एक नया handshake चुकाने के। ध्यान दें कि httpx>=0.28 ने क्लाइंट पर proxies= का नाम बदलकर proxy= कर दिया।
aiohttp यहाँ सरल है — एक एकल ClientSession प्रति अनुरोध एक proxy= तर्क लेता है, इसलिए आप इनलाइन रोटेट करते हैं:
import asyncio, itertools, aiohttp
pool = itertools.cycle(PROXIES) # http:// or socks5h:// (needs aiohttp-socks for SOCKS)
async def fetch(session, url):
proxy = next(pool)
async with session.get(url, proxy=proxy, timeout=aiohttp.ClientTimeout(total=30)) as r:
return r.status, await r.text()
async def main(urls):
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*(fetch(session, u) for u in urls))
aiohttp HTTP प्रॉक्सी को नेटिव रूप से समझता है; socks5h:// के लिए aiohttp-socks जोड़ें और एक ProxyConnector पास करें।
असीमित gather एक साथ आपके पूल के हर IP को फ़्लैग करवाने का सबसे तेज़ तरीका है। इसे सीमित करें ताकि हर exit समानांतर अनुरोधों की एक इंसान-संभावित संख्या ढोए:
sem = asyncio.Semaphore(10) # at most 10 in flight
async def fetch_capped(session, url):
async with sem:
return await fetch(session, url)
अंगूठे का नियम: concurrency को अपने पास मौजूद विशिष्ट exit की संख्या के बराबर या उससे कम रखें, ताकि आप एक एकल रेज़िडेंशियल IP पर कई समानांतर अनुरोध न ढेर करें।
रोटेशन अंदर पहुँचने के लिए है; sticky sessions अंदर बने रहने के लिए हैं। एक लॉगिन, एक कार्ट, कुछ भी जो cookie-बद्ध है, उसे पूरे क्रम के लिए एक exit पकड़े रखना चाहिए। प्रति अनुरोध के बजाय प्रति task प्रॉक्सी पिन करें:
async def run_account(account, proxy):
# Same exit IP for every step of this account's flow
async with httpx.AsyncClient(proxy=proxy, timeout=30) as client:
await client.post("https://target.site/login", data=account.creds)
await client.get("https://target.site/dashboard")
await client.post("https://target.site/cart", json=account.order)
async def main(accounts):
# One sticky exit per account, accounts run concurrently
await asyncio.gather(*(run_account(a, p)
for a, p in zip(accounts, itertools.cycle(PROXIES))))
एक ऐसे प्रदाता का उपयोग करें जो sticky रेज़िडेंशियल sessions का समर्थन करता है ताकि वही exit task के पूरे जीवनकाल के लिए पकड़ा रहे, आपके नीचे चुपचाप रोटेट न हो।
Concurrency पर आप एक 200 पर भरोसा नहीं कर सकते — एंटी-बॉट सिस्टम एक challenge बॉडी के साथ 200 लौटाते हैं। ब्लॉक का पता लगाएँ और एकल फ़ेल हुए task को एक नए exit पर retry करें:
BLOCK_MARKERS = ("just a moment", "/cdn-cgi/challenge-platform",
"datadome", "px-captcha", "access denied")
def looks_blocked(status, text):
return status in (403, 429, 503) or any(m in text[:4000].lower() for m in BLOCK_MARKERS)
async def fetch_retry(url, attempts=4):
for _ in range(attempts):
proxy = next(pool)
async with httpx.AsyncClient(proxy=proxy, timeout=30) as c:
r = await c.get(url)
if not looks_blocked(r.status_code, r.text):
return r
await asyncio.sleep(0.5)
raise RuntimeError(f"blocked after {attempts} exits: {url}")
एक पकड़ जिसे async हल नहीं करता: httpx और aiohttp दोनों Python के TLS स्टैक पर बैठते हैं, इसलिए वे एक ऐसा JA3 भेजते हैं जो कोई असली ब्राउज़र नहीं बनाता। एक साफ़ रेज़िडेंशियल IP पर एक परफ़ेक्ट रोटेशन पूल भी handshake पर ऑटोमेशन के रूप में फ़िंगरप्रिंट हो जाता है। async रोटेशन को TLS impersonation के साथ जोड़ें — देखें curl_cffi के साथ TLS फ़िंगरप्रिंटिंग को बायपास करें, और अपने exit का JA3 + ASN www.jibaoproxy.com/tools/fingerprint.html पर सत्यापित करें।
नए यूज़र्स को साइन अप पर 500MB मिलते हैं, साथ ही पहली रिचार्ज पर बोनस। सीमित समय की पेशकश।