Perpustakaan httpx telah menjadi standar untuk permintaan HTTP asinkron di Python, tetapi saat bekerja dengan proxy, ada nuansa yang dapat merusak parser di lingkungan produksi. Mari kita bahas cara mengatur rotasi proxy asinkron, mengaktifkan HTTP/2, dan mengapa skrip Anda sering gagal setelah 50-100 permintaan.
Mengapa httpx, bukan requests atau aiohttp
requests bersifat sinkron dan tidak mendukung HTTP/2 secara langsung. aiohttp bersifat asinkron, tetapi pengaturan proxy di sana kurang ramah, dan dukungan untuk HTTP/2 sama sekali tidak ada. httpx menjawab kedua masalah tersebut: klien sinkron dan asinkron dengan API tunggal, dukungan native untuk HTTP/2 melalui ekstensi h2, dan pengelolaan proxy yang fleksibel di tingkat transportasi.
Untuk tugas parsing marketplace (Wildberries, Ozon, Avito), pengumpulan data dari API media sosial, atau pemantauan harga pesaing, asinkronitas sangat penting — ini memberikan peningkatan kecepatan yang signifikan dibandingkan dengan permintaan berurutan. Namun, tanpa rotasi proxy yang tepat dan pengelolaan koneksi, asinkronitas dapat menjadi sumber masalah baru: kehabisan kolam koneksi, korutin yang terjebak, kebocoran deskriptor file.
Poin penting kedua — HTTP/2 dapat memmultiplex beberapa permintaan dalam satu koneksi TCP, yang mengurangi beban pada server target dan membantu menghindari beberapa sistem anti-bot yang naif, yang melihat jumlah handshake TCP dari satu IP.
Pengaturan dasar proxy di httpx
Sejak versi httpx 0.26+, sintaks pengaturan proxy telah berubah — alih-alih menggunakan kamus proxies, sekarang menggunakan parameter proxy. Ini adalah sumber kebingungan yang sering terjadi jika Anda melihat tutorial lama.
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"))
Jika perlu memisahkan proxy untuk lalu lintas HTTP dan HTTPS atau mengatur proxy yang berbeda untuk domain yang berbeda, gunakan httpx.Client(mounts=...) — transportasi dipasang berdasarkan pola URL:
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")
Untuk otentikasi dengan nama pengguna dan kata sandi, format http://user:pass@host:port bekerja dengan baik untuk proxy HTTP. Untuk SOCKS5, Anda perlu menginstal tambahan httpx[socks], jika tidak, Anda akan mendapatkan ImportError saat runtime, bukan saat menulis kode — ini adalah jebakan yang sering dialami banyak orang.
Rotasi proxy asinkron: kode yang berfungsi
Kesalahan utama dalam rotasi proxy adalah membuat AsyncClient baru untuk setiap permintaan. Ini membunuh kinerja, karena klien menyimpan kolam koneksi, dan pembuatannya kembali menghilangkan semua keuntungan dari koneksi keep-alive. Pendekatan yang benar adalah membuat kolam klien sebelumnya, satu untuk setiap proxy, dan mendistribusikan tugas di antara mereka.
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
# untuk pemilihan acak: 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) # membatasi paralelisme
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())
Pola ini menyelesaikan tiga masalah sekaligus: menggunakan kembali koneksi TCP melalui keep-alive, mendistribusikan beban di proxy dengan cara round-robin, dan membatasi paralelisme melalui semaphore, agar tidak membebani server target dan tidak memicu larangan karena batas kecepatan.
Untuk parser produksi yang bekerja dengan Wildberries, Ozon, atau Instagram API, penting agar kolam proxy cukup besar dan beragam secara geografis — jika tidak, rotasi dalam satu subnet tidak akan menyelamatkan dari pemblokiran. Di sini, proxy residensial sangat cocok, karena mereka memiliki IP pengguna nyata dan lebih jarang terjebak dalam filter sistem anti-bot yang menganalisis ASN dan reputasi rentang.
HTTP/2 di httpx: mengapa dan cara mengaktifkannya
Secara default, httpx menggunakan HTTP/1.1. Untuk mengaktifkan HTTP/2, Anda perlu menginstal ekstensi dan secara eksplisit mengirimkan bendera:
# 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) # diharapkan "HTTP/2"
Nuansa penting: tidak semua proxy mendukung tunneling HTTP/2 dengan benar. Beberapa proxy dari pusat data yang murah hanya berfungsi sebagai proxy HTTP/1.1 meskipun server akhir mendukung HTTP/2 — dalam hal ini, httpx akan secara diam-diam kembali ke HTTP/1.1 melalui tunnel CONNECT, dan Anda tidak selalu akan menyadarinya tanpa pemeriksaan eksplisit response.http_version.
HTTP/2 memberikan keuntungan nyata dalam tiga skenario: ketika Anda melakukan banyak permintaan ke satu domain (multiplexing dalam satu koneksi mengurangi latensi), ketika penting untuk mengurangi keterlihatan permintaan untuk perlindungan anti-bot (lebih sedikit handshake TCP dari satu IP terlihat lebih alami), dan ketika situs target sendiri memerlukan HTTP/2 dan mengembalikan kesalahan atau konten yang dipotong kepada klien di HTTP/1.1 — ini kadang-kadang terjadi pada situs di Cloudflare.
Untuk tugas di mana kecepatan dan stabilitas koneksi HTTP/2 melalui proxy penting, proxy data center yang didedikasikan lebih cocok — mereka lebih cepat daripada residensial dan biasanya mendukung tunneling protokol modern dengan benar.
5 kesalahan umum dengan proxy di httpx
1. Membuat klien baru untuk setiap permintaan
Kode seperti async with httpx.AsyncClient(...) as client: await client.get(url) di dalam loop adalah antipola. Setiap pembuatan klien adalah koneksi TCP baru, handshake TLS baru, dan mengabaikan sepenuhnya connection pooling. Pada 1000 permintaan, perbedaan kecepatan antara pendekatan yang benar dan yang salah dapat mencapai 5-10 kali lipat.
2. Tidak ada timeout atau timeout yang terlalu besar secara default
Secara default, httpx menetapkan timeout 5 detik untuk semuanya — connect, read, write, pool. Saat bekerja melalui proxy yang tidak stabil, ini terlalu sedikit untuk connect, tetapi terlalu banyak untuk read, jika proxy hanya "menggantung". Pisahkan timeout secara eksplisit:
timeout = httpx.Timeout(
connect=5.0,
read=10.0,
write=10.0,
pool=5.0
)
client = httpx.AsyncClient(proxy=PROXY_URL, timeout=timeout)
3. Mengabaikan batasan kolam koneksi
Dengan banyaknya tugas paralel (gather pada 500+ korutin tanpa semaphore), httpx mulai melempar PoolTimeout, karena batas koneksi dalam kolam (default 100) telah habis. Atur batas secara eksplisit melalui httpx.Limits(max_connections=200, max_keepalive_connections=50) dan selalu gunakan semaphore untuk mengontrol paralelisme.
4. Menonaktifkan verifikasi tanpa memahami konsekuensinya
Banyak yang menyalin verify=False dari Stack Overflow untuk menghindari kesalahan SSL saat bekerja melalui proxy dengan sertifikat MITM. Ini membuka jalan untuk serangan "man-in-the-middle" dan dapat menyebabkan kebocoran data melalui proxy yang dikompromikan. Jika proxy memerlukan sertifikat khusus — tambahkan secara eksplisit melalui verify="/path/to/cert.pem", dan jangan menonaktifkan verifikasi sepenuhnya.
5. Klien yang tidak ditutup dan kebocoran deskriptor file
Jika Anda membuat kolam klien secara manual (seperti dalam contoh di atas), jangan lupa untuk memanggil await client.aclose() untuk masing-masing saat menyelesaikan pekerjaan. Skrip yang berjalan lama tanpa penutupan koneksi yang jelas akan mencapai batas deskriptor file terbuka dari sistem operasi (biasanya 1024 di Linux secara default), dan parser akan gagal dengan kesalahan misterius Too many open files setelah beberapa jam beroperasi.
Jenis proxy apa yang dipilih untuk parsing
Pemilihan proxy secara langsung mempengaruhi seberapa banyak kesalahan dari daftar di atas yang akan Anda lihat — proxy yang tidak stabil memicu timeout dan pemutusan koneksi, bahkan jika kode ditulis dengan sempurna.
| Jenis Proxy | Kecepatan | Risiko Pemblokiran | Kapan Menggunakan |
|---|---|---|---|
| Data Center | Tinggi | Tinggi di situs yang dilindungi | Open API, perlindungan anti-bot yang lemah |
| Residen | Sedang | Rendah | Wildberries, Ozon, media sosial |
| Mobile | Sedang-rendah | Minimal | Instagram, TikTok API, perlindungan ketat |
Untuk parsing asinkron dengan kecepatan permintaan tinggi ke API dengan perlindungan sedang, proxy data center tetap menjadi pilihan yang ekonomis — yang penting adalah mendistribusikan beban secara merata di kolam, seperti yang ditunjukkan dalam kode di atas.
Daftar periksa sebelum produksi
- Klien httpx dibuat sekali dan digunakan kembali, bukan untuk setiap permintaan
- Timeout dibagi menjadi connect/read/write/pool, bukan menggunakan default
- Paralelisme dibatasi melalui
asyncio.Semaphore - Batas kolam koneksi diatur sesuai dengan beban nyata
- Ada logika retry dengan penanganan ProxyError, ConnectTimeout, ReadTimeout
- Semua klien ditutup secara eksplisit melalui
aclose()saat selesai - verify=False tidak digunakan tanpa kebutuhan mendesak
- HTTP/2 diaktifkan dengan sadar dan diperiksa melalui response.http_version
Kesimpulan
httpx memberikan alat yang kuat untuk bekerja secara asinkron dengan proxy dan HTTP/2, tetapi memerlukan pengaturan kolam klien, timeout, dan batas koneksi yang hati-hati. Sebagian besar kegagalan parser di produksi tidak terkait dengan perpustakaan, tetapi dengan kesalahan arsitektur: pembuatan ulang klien, kurangnya pembatasan paralelisme, dan penanganan pengecualian proxy yang tidak benar.
Jika Anda membangun parser untuk marketplace atau API media sosial yang secara aktif memantau aktivitas mencurigakan, stabilitas infrastruktur proxy sama pentingnya dengan kode. Untuk tugas semacam itu, sebaiknya pertimbangkan proxy residensial — mereka mengurangi jumlah kesalahan ProxyError dan ConnectTimeout karena reputasi IP yang lebih tinggi dibandingkan dengan kolam data center yang murah.