Proxy di Python requests: setup, httpx, dan sesi sticky
Kode siap tempel untuk pakai residential proxy di Python: requests, Session, httpx async, SOCKS5, sesi sticky per worker, retry, dan cara menghemat kuota GB.
Kalau kamu scraping halaman publik atau menguji situs dari beberapa negara pakai Python, bagian proxy-nya sebenarnya cuma satu dict. Yang bikin repot justru hal-hal setelahnya: bagaimana supaya satu worker tetap keluar dari IP yang sama, kenapa tiba-tiba muncul 407, dan kenapa kuota GB habis lebih cepat dari perkiraan. Panduan ini isinya kode siap tempel untuk requests dan httpx, plus pola yang bikin tagihan bandwidth tetap masuk akal.
Yang perlu kamu siapkan
- Akun WaroengProxy. Daftar cukup sekali klik: tanpa email, tanpa nomor HP. Kamu dapat satu token akses, simpan di password manager karena cuma ditampilkan sekali.
- Kuota GB. Top up saldo lewat QRIS minimal Rp 10.000, lalu tukar jadi GB. Harga dasarnya Rp 10.000 per GB dengan minimum 1 GB, dan diskon volume mulai jalan dari 100 GB. Rinciannya di halaman harga.
- Kredensial proxy. Host, port, username, dan password ada di kartu kredensial di dashboard. Satu endpoint yang sama,
proxy.waroengproxy.com:8000, melayani HTTP, HTTPS, dan SOCKS5. - Python 3 dan satu pustaka HTTP.
pip install requestsataupip install httpx, tergantung mana yang kamu pakai.
Akun baru yang belum pernah top up akan dihapus otomatis setelah 48 jam, jadi selesaikan deposit pertama di sesi yang sama saat kamu daftar.
Setup paling singkat di requests
requests menerima proxy dalam bentuk dict dengan kunci per skema. Poin yang sering keliru: untuk HTTPS kamu tetap menulis http:// di nilai proxy-nya. Itu bukan salah tulis — http:// menandakan cara kamu tersambung ke proxy, dan proxy lalu membuat tunnel CONNECT ke target sehingga TLS-nya tetap ujung ke ujung antara skripmu dan situs tujuan.
import requestsUSER = "USERNAME" # ganti dengan username akunmuPASS = "PASSWORD" # ganti dengan password akunmuENDPOINT = "proxy.waroengproxy.com:8000"proxies = {"http": f"http://{USER}:{PASS}@{ENDPOINT}","https": f"http://{USER}:{PASS}@{ENDPOINT}",}r = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=30)print(r.status_code, r.json())
Jalankan dua kali. Karena mode bawaannya rotating, IP yang muncul di respons akan berbeda tiap permintaan. Kalau responsnya menampilkan IP dan kota yang wajar, berarti jalurnya sudah benar dan sisa panduan ini cuma soal merapikan.
Pakai Session supaya rapi dan lebih cepat
Menempelkan proxies= di setiap panggilan cepat jadi berulang, dan tiap panggilan requests.get juga membuka koneksi baru. Session menyimpan proxy, header, dan cookie sekali saja lalu memakai ulang koneksi TCP yang sama. Untuk puluhan permintaan ke host yang sama, ini terasa jelas di waktu total.
import requestsdef make_session(username: str, password: str) -> requests.Session:s = requests.Session()url = f"http://{username}:{password}@proxy.waroengproxy.com:8000"s.proxies = {"http": url, "https": url}s.headers.update({"User-Agent": "riset-harga/1.0 (+https://situskamu.example/bot)","Accept-Encoding": "gzip, deflate",})return swith make_session("USERNAME", "PASSWORD") as s:for path in ("/json", "/json"):print(s.get(f"https://ipinfo.io{path}", timeout=30).json()["ip"])
Accept-Encoding: gzip bukan hiasan. Respons yang terkompresi berarti byte yang lebih sedikit lewat proxy, dan byte itulah yang dihitung sebagai kuota GB-mu.
Pilih negara, kota, atau ISP dari dalam kode
Targeting di WaroengProxy ditulis sebagai parameter di username, bukan sebagai host atau port yang berbeda. Formatnya USERNAME-key-value-key-value, dan kamu bisa merangkainya dengan f-string biasa. Beberapa key yang paling sering dipakai: cc (kode negara ISO, atau EU untuk 27 negara Uni Eropa), co (benua: NA, SA, EU, AS, AF, OC), st (kode wilayah ISO 3166-2, maks. 3 karakter), ct (nama kota tanpa spasi dan aksen), pc (kode pos), asn (nomor sistem otonom), isp (slug nama ISP), dan tz (zona IANA dengan / ditulis .).
BASE, PASS = "USERNAME", "PASSWORD"def px(*params: str) -> dict:user = "-".join([BASE, *params]) if params else BASEurl = f"http://{user}:{PASS}@proxy.waroengproxy.com:8000"return {"http": url, "https": url}TARGETS = {"Indonesia": px("cc", "ID"),"Jakarta": px("cc", "ID", "ct", "Jakarta"),"California": px("cc", "US", "st", "CA"),"Asia (benua)": px("co", "AS"),"Telkomsel": px("cc", "ID", "isp", "telkomsel"),}for label, proxies in TARGETS.items():d = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=30).json()print(label, "->", d["ip"], d.get("city"), d.get("country"))
Semua filter berlaku bersamaan, jadi tiap tambahan mempersempit kumpulan IP. Kombinasi yang terlalu sempit (misalnya kota plus ISP plus kode pos sekaligus) bisa berujung tidak ada IP yang cocok. Daftar lengkap key-nya ada di dokumentasi, dan kalau mau menyusunnya sambil melihat hasilnya, pakai generator proxy lalu salin username yang jadi.
Sesi sticky: satu worker, satu IP
Untuk alur yang butuh kesinambungan — buka halaman daftar lalu halaman detail, atau mengikuti paginasi — IP yang ganti di tengah jalan bikin situs tujuan melihat dua pengunjung berbeda. Tambahkan s (label sesi) dan ttl (umur sesi dalam detik, 1 sampai 86400, bawaannya 60). Label yang sama akan memberi IP yang sama selama TTL belum habis.
from concurrent.futures import ThreadPoolExecutorimport requestsBASE, PASS = "USERNAME", "PASSWORD"def worker_session(worker_id: int, ttl: int = 600) -> requests.Session:user = f"{BASE}-cc-ID-s-w{worker_id}-ttl-{ttl}"url = f"http://{user}:{PASS}@proxy.waroengproxy.com:8000"s = requests.Session()s.proxies = {"http": url, "https": url}return sdef crawl(worker_id, urls):with worker_session(worker_id) as s:return [s.get(u, timeout=30).status_code for u in urls]batches = [["https://example.com/a"], ["https://example.com/b"]]with ThreadPoolExecutor(max_workers=len(batches)) as pool:print(list(pool.map(crawl, range(len(batches)), batches)))
Kuncinya: label sesi dibuat per worker, bukan satu label untuk seluruh program. Kalau semua thread memakai label yang sama, mereka semua keluar dari satu IP dan keuntungan paralelnya hilang. Perbandingan lengkap kedua mode ada di sticky atau rotating.
httpx: async dan HTTP/2
httpx memakai argumen proxy= berisi satu string, bukan dict, dan klien async-nya cocok kalau kamu perlu ratusan permintaan bersamaan. Batasi jumlah koneksi lewat httpx.Limits supaya tidak membanjiri situs tujuan.
import asyncio, httpxPROXY = "http://USERNAME-cc-ID:PASSWORD@proxy.waroengproxy.com:8000"async def main(urls):limits = httpx.Limits(max_connections=10)async with httpx.AsyncClient(proxy=PROXY, limits=limits,timeout=30.0, http2=True) as client:rs = await asyncio.gather(*(client.get(u) for u in urls))for r in rs:print(r.status_code, r.url)asyncio.run(main(["https://ipinfo.io/json"] * 5))
HTTP/2 butuh pip install httpx[http2]. Kalau versi httpx-mu masih lama dan menolak proxy=, pakai proxies= dengan nilai string yang sama.
SOCKS5 dari Python
Endpoint yang sama juga melayani SOCKS5, tanpa port lain. Di requests kamu perlu pip install "requests[socks]", lalu tulis skemanya socks5h://. Huruf h di belakang penting: dengan socks5h, resolusi DNS dikerjakan di sisi proxy, jadi nama host tujuan tidak bocor ke resolver lokalmu dan hasil resolusinya sesuai dengan lokasi IP keluar.
import requestsurl = "socks5h://USERNAME-cc-ID:PASSWORD@proxy.waroengproxy.com:8000"proxies = {"http": url, "https": url}print(requests.get("https://ipinfo.io/json", proxies=proxies, timeout=30).json())
Untuk httpx, SOCKS5 tersedia lewat pip install httpx[socks] dengan bentuk proxy= yang sama. Kapan sebaiknya memilih SOCKS5 dan kapan HTTP sudah cukup dibahas terpisah di HTTP, HTTPS, atau SOCKS5?
Tangani error tanpa menebak
Empat kegagalan ini menutupi hampir semua kasus, dan masing-masing punya penanganan yang berbeda:
- 407 Proxy Authentication Required — username atau password salah, atau salah satu parameter targeting tidak valid. Satu nilai keliru membuat seluruh username ditolak, jadi cek dulu ejaan key-nya sebelum menyalahkan password.
- Koneksi ditolak saat filter dipakai — tidak ada IP yang memenuhi semua filter sekaligus. Longgarkan satu tingkat, misalnya dari kota ke negara.
- Timeout — normal terjadi pada residential IP. Selalu pasang
timeout=, lalu ulangi permintaannya; jangan biarkan skrip menggantung tanpa batas. - 403 atau halaman kosong dari situs tujuan — ini penolakan dari situsnya, bukan dari proxy. Turunkan laju permintaan, periksa
robots.txt, dan hormati ketentuan layanan situs tersebut.
import requestsfrom requests.adapters import HTTPAdapterfrom urllib3.util.retry import Retryretry = Retry(total=3, backoff_factor=1.5,status_forcelist=[429, 500, 502, 503, 504],allowed_methods=["GET", "HEAD"])s = requests.Session()url = "http://USERNAME-cc-ID:PASSWORD@proxy.waroengproxy.com:8000"s.proxies = {"http": url, "https": url}s.mount("https://", HTTPAdapter(max_retries=retry))try:r = s.get("https://example.com", timeout=(10, 30))r.raise_for_status()except requests.exceptions.ProxyError as e:print("masalah di sisi proxy / kredensial:", e)except requests.exceptions.ReadTimeout:print("target lambat, coba IP lain")
Perhatikan status_forcelist yang tidak memuat 403. Mengulangi 403 dari IP yang sama hampir selalu sia-sia dan cuma membakar kuota. Panduan error yang lebih lengkap, termasuk cara membedakan penolakan proxy dari penolakan target, ada di Proxy error 407, timeout, atau IP salah negara?
Bikin kuota GB awet
Kamu ditagih per GB yang benar-benar lewat, jadi setiap byte yang tidak perlu diambil adalah rupiah yang tidak perlu keluar. Kabar baiknya, requests dan httpx sudah jauh lebih hemat daripada browser otomatis karena keduanya tidak mengunduh gambar, font, CSS, dan skrip pihak ketiga.
- Minta respons terkompresi lewat header
Accept-Encoding: gzip, deflate. - Pakai
HEADkalau kamu hanya butuh tahu halamannya ada atau berubah, bukan isinya. - Panggil endpoint JSON atau sitemap situs tujuan kalau tersedia — jauh lebih kecil daripada HTML lengkapnya.
- Batasi retry dan jangan mengulang permintaan yang responsnya besar.
- Simpan hasil ke disk saat pengembangan, supaya tiap percobaan parsing tidak mengambil ulang halaman yang sama.
len(r.content) dari seratus halaman pertama, bagi seratus, lalu kalikan dengan jumlah halaman yang kamu rencanakan. Cara menghitungnya dibahas lebih detail di Berapa GB yang sebenarnya kamu butuhkan?Ringkasnya: satu dict proxy untuk mulai, Session supaya rapi, parameter username untuk lokasi, label sesi per worker untuk sticky, dan retry yang tahu kapan harus berhenti. Sudah siap mencoba? Buat akun, top up seperlunya, lalu tempel skrip pertama di atas — dan pastikan apa pun yang kamu ambil sesuai robots.txt serta ketentuan layanan situs tujuan.