Panduan7 menit baca

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 requests atau pip 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.

Python
import requests
USER = "USERNAME" # ganti dengan username akunmu
PASS = "PASSWORD" # ganti dengan password akunmu
ENDPOINT = "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.

Python
import requests
def 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 s
with 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 .).

Python
BASE, PASS = "USERNAME", "PASSWORD"
def px(*params: str) -> dict:
user = "-".join([BASE, *params]) if params else BASE
url = 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.

Python
from concurrent.futures import ThreadPoolExecutor
import requests
BASE, 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 s
def 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.

Setel TTL sedikit lebih panjang dari perkiraan durasi satu alur, jangan jauh lebih panjang. TTL 600 detik untuk alur 30 detik berarti IP itu menganggur tapi tetap terikat ke sesimu, dan variasi IP yang kamu dapat jadi lebih sedikit dari yang seharusnya.

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.

Python
import asyncio, httpx
PROXY = "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.

Python
import requests
url = "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.
Python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = 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 HEAD kalau 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.
Ukur dulu sebelum membeli banyak: jumlahkan 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.