# Proxy residensial berputar di Python: requests, httpx dan aiohttp

> Proxy residensial berputar di Python: requests dengan retries, httpx sync dan async, aiohttp, sesi sticky dan hitungan byte. Kode yang telah dicek sintaksnya.

- URL: https://crawlrelay.com/id/docs/python/
- Terakhir diperbarui: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Pola produksi, bukan satu baris: retries yang menghormati Retry-After, IP baru per permintaan atau satu IP per alur, concurrency per-host di asyncio, dan byte yang benar-benar dipakai tiap halaman.

**Jawaban cepat — Bagaimana cara menggunakan proxy residensial berputar di Python?**

Gunakan `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777` sebagai proxy untuk `http` dan `https` di requests, sebagai `proxy=` di httpx 0.26+, atau sebagai `proxy=` per permintaan di aiohttp. **CrawlRelay** proxy residensial berputar di setiap koneksi baru dan tetap sticky dengan `-session-<id>` pada username. Biayanya US$1,50 per 1 GB dan US$0,50/GB dari 50 GB. Dibayar pakai crypto, tanpa KYC.

## Setup Python sekilas

| Atribut | Nilai |
| --- | --- |
| requests | `proxies={"http": url, "https": url}`; retries lewat urllib3 `Retry` |
| httpx | `httpx.Client(proxy=url)`; argumen `proxies=` lama dihapus di httpx 0.28 |
| aiohttp | `session.get(url, proxy="http://USER:PASS@host:port")`, kredensial di URL proxy |
| SOCKS5 | `pip install "requests[socks]"` atau `httpx[socks]`, lalu `socks5h://` di port 7778 |
| IP baru per permintaan | Nonaktifkan keep-alive: `Connection: close`, `max_keepalive_connections=0` atau `force_close=True` |
| IP sama untuk satu alur | `-session-<id>-ttl-<minutes>` di username, hingga 30 menit |
| Skema URL proxy | Selalu `http://` untuk port HTTP, bahkan untuk website HTTPS |

## Bagaimana cara menggunakan proxy dengan requests, dengan retries yang menghormati Retry-After?

Pasang sebuah `HTTPAdapter` dengan urllib3's `Retry` pada `Session`. Ini mengulang jika ada kesalahan koneksi dan status yang Anda daftar, menunggu dengan backoff eksponensial, dan menghormati header `Retry-After` yang dikirim situs pada 429 dan 503. Set `trust_env = False` supaya variabel `HTTPS_PROXY` sisa tidak menimpa proxy Anda.

*requests + urllib3 Retry*

```python
import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

USER = os.environ.get("CRAWLRELAY_USER", "USERNAME-country-us")
PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD")
PROXY = "http://%s:%s@gw.crawlrelay.com:7777" % (USER, PASSWORD)

retry = Retry(
    total=4,
    connect=2,
    backoff_factor=1.0,  # waits of 0 s, 2 s, 4 s, 8 s (Retry-After wins on 429/503)
    status_forcelist=(429, 502, 503, 504),
    allowed_methods=frozenset({"GET", "HEAD"}),
    respect_retry_after_header=True,
)

session = requests.Session()
session.trust_env = False  # ignore HTTP(S)_PROXY from the environment
session.mount("http://", HTTPAdapter(max_retries=retry))
session.mount("https://", HTTPAdapter(max_retries=retry))
session.proxies = {"http": PROXY, "https": PROXY}
session.headers["User-Agent"] = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"
session.headers["Connection"] = "close"  # new connection per request, so usually a new exit IP

r = session.get("https://api.ipify.org", timeout=(10, 30))
print(r.status_code, r.text)
```

## Bagaimana cara membangun URL proxy yang ditargetkan dengan aman?

Buat username dalam satu fungsi dan validasi. Salah ketik kode negara atau karakter yang tidak terencode di password menghasilkan [407](https://crawlrelay.com/id/docs/errors/407-proxy-authentication-required/) yang tampak seperti masalah kredensial. Bantuan di bawah menjaga urutan yang didokumentasikan (`USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}`), menolak kunci tidak dikenal dan melakukan URL-encode pada kedua kredensial.

*Pembuat username dan URL proxy*

```python
import re
from urllib.parse import quote

ORDER = ("country", "state", "city", "zip", "asn", "session", "ttl")
VALUE = re.compile(r"^[a-z0-9_]+$")


def build_username(base, **params):
    """build_username("USERNAME", country="us", city="new_york", session="job1", ttl=30)"""
    parts = [base]
    for key in ORDER:
        value = params.pop(key, None)
        if value is None:
            continue
        value = str(value).lower()
        if not VALUE.match(value):
            raise ValueError("bad value for %s: %r" % (key, value))
        if key == "ttl" and not 1 <= int(value) <= 30:
            raise ValueError("ttl must be 1 to 30 minutes")
        parts.append("%s-%s" % (key, value))
    if params:
        raise ValueError("unknown parameters: %s" % ", ".join(sorted(params)))
    return "-".join(parts)


def proxy_url(username, password, socks=False):
    scheme, port = ("socks5h", 7778) if socks else ("http", 7777)
    return "%s://%s:%s@gw.crawlrelay.com:%d" % (
        scheme, quote(username, safe=""), quote(password, safe=""), port)


print(proxy_url(build_username("USERNAME", country="us", state="ny", city="new_york", session="job1", ttl=20), "p@ss:word"))
```

## Bagaimana cara mendapatkan IP baru di setiap permintaan dengan httpx?

Matikan keep-alive. httpx mengelola koneksi secara default, dan koneksi yang dipakai ulang mempertahankan IP keluarannya. Dengan `max_keepalive_connections=0`, tiap permintaan membuka koneksi baru ke gateway dan biasanya keluar lewat proxy residensial berbeda. `num_bytes_downloaded` memberikan byte yang diterima di jaringan, yang paling mendekati bagaimana biaya trafik dihitung.

*httpx: client berputar*

```python
import httpx

PROXY = "http://USERNAME-country-gb:PASSWORD@gw.crawlrelay.com:7777"

# No keep-alive: every request opens a new connection, so it usually gets a new exit IP
with httpx.Client(
    proxy=PROXY,
    timeout=httpx.Timeout(30.0, connect=10.0),
    limits=httpx.Limits(max_keepalive_connections=0),
    follow_redirects=True,
) as client:
    for _ in range(3):
        r = client.get("https://api.ipify.org")
        print(r.text, r.num_bytes_downloaded, "bytes on the wire")
```

## Bagaimana cara scrape secara konkuren dengan asyncio tanpa membebani satu situs?

Batasi concurrency **per host**, bukan hanya secara global. Batas global 50 membiarkan semua 50 permintaan menarget situs yang sama saat daftar URL diurutkan per domain. Semaphore per hostname menjaga tiap situs tetap sopan sementara crawling tetap cepat. Mulai dari 2 sampai 4 per host dan baca panduan [concurrency](https://crawlrelay.com/id/docs/concurrency/) sebelum menaikkannya.

*httpx.AsyncClient dengan semaphore per host*

```python
import asyncio
from collections import defaultdict
from urllib.parse import urlsplit

import httpx

PROXY = "http://USERNAME-country-us:PASSWORD@gw.crawlrelay.com:7777"
PER_HOST = 4  # parallel requests per target host
host_slots = defaultdict(lambda: asyncio.Semaphore(PER_HOST))


async def fetch(client, url):
    async with host_slots[urlsplit(url).hostname]:
        try:
            r = await client.get(url)
            return url, r.status_code, r.num_bytes_downloaded
        except httpx.HTTPError as exc:
            return url, type(exc).__name__, 0


async def main(urls):
    limits = httpx.Limits(max_connections=50, max_keepalive_connections=0)
    timeout = httpx.Timeout(30.0, connect=10.0)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=timeout) as client:
        for result in await asyncio.gather(*(fetch(client, u) for u in urls)):
            print(*result)


asyncio.run(main(["https://example.com/", "https://example.org/", "https://example.net/"]))
```

## Bagaimana cara menggunakan proxy dengan aiohttp?

Lewatkan gateway sebagai `proxy=` setiap permintaan, dengan kredensial di URL proxy. Ini bekerja di setiap rilis aiohttp 3.x tanpa peringatan; `BasicAuth` dan parameter `proxy_auth=` sudah deprecated sejak aiohttp 3.14 dan akan dihapus di versi 4.0. `TCPConnector(force_close=True)` menutup setiap koneksi setelah responnya, sehingga tiap permintaan biasanya mendapat exit IP baru. Tanpa ini, aiohttp memakai ulang koneksi dan IP tetap sama selama beberapa waktu.

*aiohttp dengan kredensial di URL proxy*

```python
import asyncio

import aiohttp

# Credentials go in the proxy URL; percent-encode a password that contains @ or :
PROXY = "http://USERNAME-country-fr:PASSWORD@gw.crawlrelay.com:7777"


async def main():
    # force_close=True: one connection per request, so each request usually gets a new exit IP
    connector = aiohttp.TCPConnector(limit=20, force_close=True)
    timeout = aiohttp.ClientTimeout(total=60, sock_connect=10)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        for _ in range(3):
            async with session.get("https://api.ipify.org", proxy=PROXY) as resp:
                print(resp.status, await resp.text())


asyncio.run(main())
```

## Bagaimana cara mempertahankan satu IP untuk alur multi-langkah?

Buat satu session ID per alur dan masukkan ke username dengan TTL lebih lama dari alur. Keep-alive baik digunakan di sini: Anda ingin exit yang sama. Jika perangkat residensial di balik sesi offline, IP bisa berubah sebelum TTL habis, jadi buat alurnya bisa diulang dari checkpoint terakhir.

*Sesi sticky dengan requests*

```python
import uuid

import requests

session_id = uuid.uuid4().hex[:10]  # one ID per flow, lowercase letters and digits
user = "USERNAME-country-us-state-ca-session-%s-ttl-20" % session_id
proxy = "http://%s:PASSWORD@gw.crawlrelay.com:7777" % user

with requests.Session() as s:
    s.proxies = {"http": proxy, "https": proxy}
    first = s.get("https://api.ipify.org", timeout=(10, 30)).text
    second = s.get("https://api.ipify.org", timeout=(10, 30)).text
    print(first, second, "same exit" if first == second else "exit changed")
```

## Berapa banyak trafik yang digunakan scraper Python per halaman?

Ukur di 100 halaman nyata sebelum beli volume. Catat `num_bytes_downloaded` di httpx, atau panjang body terkompresi di middleware Anda sendiri, lalu kalikan dengan jumlah halaman. Permintaan hanya HTML biasanya menghabiskan puluhan kilobyte per halaman; panduan [bandwidth](https://crawlrelay.com/id/guides/bandwidth-calculator/) memberikan angka dan biaya per 1.000 halaman dengan harga CrawlRelay.

Tiga kebiasaan yang mengurangi trafik tanpa mengubah hasil:

- Kirim `Accept-Encoding: gzip, br` (httpx dan requests kirim gzip secara default) supaya halaman dikirim terkompresi.
- Ambil endpoint JSON yang dimuat halaman, saat situs mendokumentasikan atau mengizinkannya, daripada HTML lengkap.
- Lewati halaman yang tidak berubah dengan `If-None-Match` atau `If-Modified-Since`: `304 Not Modified` berbiaya beberapa ratus byte.

## Python dan proxy: pertanyaan

### requests, httpx atau aiohttp: mana yang harus saya gunakan untuk scraping?

Gunakan requests untuk skrip sederhana dan crawl kecil. Gunakan httpx ketika Anda ingin API yang sama dalam kode sinkron dan asinkron, serta HTTP/2 melalui `httpx[http2]`. Gunakan aiohttp untuk konkurensi sangat tinggi dengan asyncio murni. Untuk crawl dengan penjadwalan, deduplikasi, dan pipeline, gunakan [Scrapy](https://crawlrelay.com/id/docs/scrapy/) daripada menulis bagian-bagian itu sendiri.

### Mengapa saya mendapatkan SSL: WRONG_VERSION_NUMBER dengan proxy saya di Python?

Anda mungkin menulis `https://` di depan alamat proxy. Port gateway 7777 menggunakan HTTP biasa dan membuka koneksi CONNECT untuk situs HTTPS, jadi URL proxy harus dimulai dengan `http://` pada kunci `http` dan `https`. Lihat [halaman error](https://crawlrelay.com/id/docs/errors/ssl-wrong-version-number/).

### Bagaimana cara menggunakan proxy SOCKS5 di Python?

Instal tambahan (`pip install "requests[socks]"` atau `pip install "httpx[socks]"`), lalu gunakan `socks5h://USERNAME:PASSWORD@gw.crawlrelay.com:7778`. `h` mengirim hostname ke proxy, jadi DNS diresolusikan di exit dan Anda melihat jawaban regional yang sama seperti pengunjung lokal.

### Mengapa proxy rotasi saya mengembalikan IP yang sama dua kali?

Klien Anda menggunakan kembali koneksi keep-alive, dan rotasi terjadi per koneksi baru. Kirim `Connection: close` pada requests, atur `max_keepalive_connections=0` di httpx, atau gunakan `force_close=True` di aiohttp. Periksa juga bahwa username tidak memiliki bagian `session-`, yang sengaja mempertahankan satu IP.

### Bagaimana cara mengatur timeout untuk permintaan melalui proxy?

Atur secara eksplisit: requests tidak memiliki timeout secara default. Timeout koneksi sekitar 10 detik dan timeout baca 30 detik cocok untuk halaman HTML; exit seluler dan target lambat memerlukan 45 hingga 60 detik. Coba ulangi timeout sekali pada koneksi baru daripada menunggu lebih lama. Lihat [timeout baca](https://crawlrelay.com/id/docs/errors/read-timeout/).

### Bisakah saya menggunakan proxy ini di Jupyter atau dengan pandas?

Ya. Apa pun yang menggunakan requests atau httpx di bawahnya menggunakan pengaturan proxy yang sama. Untuk `pandas.read_html` atau `read_csv` pada URL, unduh konten dengan panggilan requests yang melalui proxy dulu, lalu berikan teks atau byte ke pandas.

**Jalankan scraper Python Anda melalui IP residensial** US$1,50 untuk 1 GB, US$0,50/GB dari 50 GB. [Lihat harga](https://crawlrelay.com/id/pricing/) · [Panduan Scrapy](https://crawlrelay.com/id/docs/scrapy/)

---

CrawlRelay: CrawlRelay adalah layanan proxy residensial dan proxy seluler 4G/5G yang berfokus pada pengembang untuk scraper dan pipeline data AI: trafik pay-as-you-go tanpa kedaluwarsa mulai dari US$0,50/GB, dibayar dengan crypto, tanpa perlu unggah ID. Tanpa KYC — tanpa ID, tanpa selfie, tanpa dokumen. Bayar dengan crypto.
