Ang async scraping ang lugar kung saan tahimik na nasisira ang mga proxy setup. Sa requests ay umiikot ka ng isang IP bawat call at tuloy lang; sa asyncio ay biglang may 200 request kang lumilipad, lahat nangangailangan ng sariling exit, sariling session affinity, at sariling retry kapag nagtapon ang target ng 403. Mali mo ito at alinman ay pinapahirapan mo ang isang IP hanggang ma-ban o sinisira mo ang bawat login session sa pag-ikot sa gitna ng flow.
Ipinapakita ng gabay na ito ang mga pattern na talagang humahawak sa concurrency: per-request rotation sa httpx at aiohttp, isang bounded worker pool, sticky sessions para sa stateful flows, at block-aware retries — may gumaganang code na puwede mong i-paste.
Ang isang synchronous na scraper ay humahawak ng isang proxy sa isang pagkakataon, kaya ang "umikot sa bawat request" ay tama nang walang kahirap-hirap. Sinisira ng async ang tatlong assumption nang sabay-sabay:
Ang httpx.AsyncClient ay nag-bibind ng isang proxy bawat client, kaya para makaikot ay pinipili mo ang proxy bawat request at iniruruta sa isang maliit na pool ng clients na naka-key sa exit:
import asyncio, itertools, httpx
PROXIES = [
"socks5h://USERNAME:[email protected]:913",
"socks5h://USERNAME:[email protected]:913",
"socks5h://USERNAME:[email protected]: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
Ang muling paggamit ng isang client bawat exit ay pinapanatiling buhay ang HTTP/2 at connection pooling sa halip na magbayad ng sariwang handshake sa bawat request. Tandaan na pinalitan ng httpx>=0.28 ang proxies= sa proxy= sa client.
Mas simple ang aiohttp dito — ang isang ClientSession ay tumatanggap ng proxy= argument bawat request, kaya umiikot ka nang inline:
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))
Ang aiohttp ay nagsasalita ng HTTP proxies nang natural; para sa socks5h:// ay idagdag ang aiohttp-socks at ipasa ang isang ProxyConnector.
Ang unbounded na gather ang pinakamabilis na paraan para mafa-flag nang sabay-sabay ang bawat IP sa pool mo. Limitahan ito kaya ang bawat exit ay nagdadala ng isang bilang ng parallel requests na kapani-paniwala para sa tao:
sem = asyncio.Semaphore(10) # at most 10 in flight
async def fetch_capped(session, url):
async with sem:
return await fetch(session, url)
Rule of thumb: panatilihin ang concurrency sa o sa ibaba ng bilang ng natatanging exits na mayroon ka, kaya hindi ka nagpapatong ng maraming parallel request sa isang residential IP.
Ang rotation ay para sa pagpasok; ang sticky sessions ay para sa pananatili. Ang isang login, isang cart, anumang cookie-bound ay kailangang humawak ng isang exit para sa buong sequence. I-pin ang proxy bawat task sa halip na bawat request:
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))))
Gumamit ng provider na sumusuporta sa sticky residential sessions kaya ang parehong exit ay hawak para sa buong buhay ng task, hindi tahimik na iniikot sa ilalim mo.
Sa concurrency ay hindi mo mapagkakatiwalaan ang isang 200 — ang anti-bot systems ay nagbabalik ng 200 na may challenge body. I-detect ang mga block at i-retry ang nag-iisang nabigong task sa isang sariwang exit:
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}")
Isang catch na hindi nilulutas ng async: ang httpx at aiohttp ay parehong nakaupo sa TLS stack ng Python, kaya nagpapadala sila ng JA3 na walang totoong browser na gumagawa. Ang isang perpektong rotation pool sa malinis na residential IP ay nafa-fingerprint pa rin bilang automation sa handshake. Isama ang async rotation sa TLS impersonation — tingnan ang Bypass TLS Fingerprinting with curl_cffi, at i-verify ang JA3 + ASN ng exit mo sa www.jibaoproxy.com/tools/fingerprint.html.
Ang mga bagong user ay makakatanggap ng 500MB sa pagrehistro, kasama ang bonus sa unang recharge. Limitadong oras lang ang alok.