# Python에서 회전하는 레지덴셜 프록시: requests, httpx, aiohttp

> Python에서 회전하는 레지덴셜 프록시: requests 재시도, httpx 동기 및 비동기, aiohttp, 고정 세션과 바이트 수. 구문 검사 완료 코드.

- URL: https://crawlrelay.com/ko/docs/python/
- 마지막 업데이트: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

한줄 코드가 아닌 프로덕션 패턴: Retry-After를 준수하는 재시도, 요청마다 새 IP 또는 플로우당 IP, asyncio 내 호스트별 동시성, 실제 페이지당 바이트 사용량.

**빠른 답변 — Python에서 회전형 레지덴셜 프록시는 어떻게 사용하나요?**

requests에서는 `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777`를 `http`와 `https` 모두의 프록시로, httpx 0.26+에서는 `proxy=`로, aiohttp에서는 요청별 `proxy=`로 전달하세요. **CrawlRelay** 레지덴셜 프록시는 새 연결마다 회전하며 사용자명에 `-session-<id>` 포함 시 고정됩니다. 1 GB는 US$1.50, 50 GB 이상은 US$0.50/GB 비용입니다. 암호화폐 결제, KYC 없이 이용 가능합니다.

## Python 설정 간단 요약

| 속성 | 값 |
| --- | --- |
| requests | `proxies={"http": url, "https": url}`; urllib3 `Retry`를 통한 재시도 지원 |
| httpx | `httpx.Client(proxy=url)`; 이전의 `proxies=` 인수는 httpx 0.28에서 제거됨 |
| aiohttp | `session.get(url, proxy="http://USER:PASS@host:port")`, 프록시 URL 내 자격 증명 포함 |
| SOCKS5 | `pip install "requests[socks]"` 또는 `httpx[socks]`, 이후 포트 7778에서 `socks5h://` 사용 |
| 요청마다 새로운 IP | keep-alive 비활성화: `Connection: close`, `max_keepalive_connections=0` 또는 `force_close=True` |
| 플로우 내 동일 IP 유지 | 사용자명에 `-session-<id>-ttl-<minutes>` 포함, 최대 30분 사용 |
| 프록시 URL 스킴 | HTTP 포트는 HTTPS 웹사이트라도 항상 `http://` 사용 |

## requests에서 Retry-After를 준수하는 재시도형 프록시를 어떻게 사용하나요?

`Session`에 urllib3의 `Retry`가 적용된 `HTTPAdapter`를 마운트하세요. 연결 오류와 지정한 상태 코드를 재시도하며 지수 백오프로 대기하고, 사이트에서 429 및 503 상태와 함께 보내는 `Retry-After` 헤더를 존중합니다. 남은 `HTTPS_PROXY` 변수로 프록시가 덮어쓰기 되지 않도록 `trust_env = False`를 설정하세요.

*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)
```

## 타겟 프록시 URL을 안전하게 생성하려면?

사용자명을 한 함수에서 만들고 검증하세요. 국가 코드 오타나 비인코딩 문자가 비밀번호에 있으면 자격 증명 문제처럼 보이는 [407](https://crawlrelay.com/ko/docs/errors/407-proxy-authentication-required/) 오류가 발생할 수 있습니다. 아래 헬퍼는 문서화된 순서(`USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}`)를 지키고, 알 수 없는 키를 거부하며 두 자격증명을 URL 인코딩합니다.

*사용자명 생성기와 프록시 URL*

```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"))
```

## httpx에서 요청마다 새 IP를 받으려면?

keep-alive를 끄세요. httpx는 기본적으로 연결을 풀링하며, 재사용된 연결은 동일한 출구 IP를 유지합니다. `max_keepalive_connections=0` 사용 시 각 요청이 게이트웨이에 새 연결을 열어 보통 다른 레지덴셜 IP로 나갑니다. `num_bytes_downloaded`는 트래픽 청구에 가장 근접한 실제 수신 바이트를 줍니다.

*httpx: 회전 클라이언트*

```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")
```

## asyncio로 한 사이트를 과부하하지 않고 어떻게 동시에 크롤링하나요?

전역 한도가 아닌 **호스트별 동시성**을 제한하세요. 전역 50 제한이면 URL 목록이 도메인별 정렬 시 모든 50개 요청이 같은 사이트에 집중됩니다. 호스트별 세마포어를 사용하면 각 사이트는 공손한 수준을 유지하고 전체 크롤링은 빠릅니다. 호스트당 2~4개부터 시작하며 [동시성 가이드](https://crawlrelay.com/ko/docs/concurrency/)를 읽고 제한을 늘리세요.

*httpx.AsyncClient + 호스트별 세마포어*

```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/"]))
```

## aiohttp에서 프록시를 어떻게 사용하나요?

게이트웨이를 각 요청의 `proxy=`로 전달하고 프록시 URL에 자격증명을 포함하세요. aiohttp 3.x 모든 버전에서 경고 없이 작동하며, aiohttp 3.14 이후 `BasicAuth`과 `proxy_auth=` 매개변수는 사용 중단되어 4.0에서 제거됩니다. `TCPConnector(force_close=True)`는 응답 후 모든 연결을 닫아 요청마다 보통 새 출구 IP를 받습니다. 없으면 연결이 재사용되어 같은 IP를 잠시 유지합니다.

*프록시 URL 내 자격증명과 aiohttp*

```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())
```

## 멀티스텝 플로우에서 하나의 IP를 유지하려면?

플로우당 세션 ID를 생성해 사용자명에 TTL이 플로우보다 길게 넣으세요. keep-alive는 괜찮습니다: 동일 출구 IP가 필요하니까요. 세션 뒤 레지덴셜 기기가 오프라인 되면 TTL 이전에 IP가 바뀔 수 있으니 플로우가 마지막 체크포인트부터 재시작 가능해야 합니다.

*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")
```

## Python 크롤러는 페이지당 트래픽을 얼마나 쓰나요?

볼륨 구매 전에 100개 실제 페이지에서 측정하세요. httpx에서 `num_bytes_downloaded`를 로깅하거나 자체 미들웨어에서 압축된 본문 길이를 기록해 페이지 수와 곱하세요. HTML 요청만 하는 경우 보통 페이지당 수십 KB가 들고, [대역폭 가이드](https://crawlrelay.com/ko/guides/bandwidth-calculator/)에서 CrawlRelay 가격 기준 1,000페이지당 비용과 수치를 참고하세요.

결과는 유지하면서 트래픽을 줄이는 세 가지 습관:

- `Accept-Encoding: gzip, br` 전송 (httpx, requests 기본 gzip 압축 전송)
- 사이트가 문서화하거나 허용 시 전체 HTML 대신 JSON 엔드포인트 요청
- `If-None-Match` 또는 `If-Modified-Since`로 변경 없는 페이지 스킵: `304 Not Modified`는 몇백 바이트만 소비

## 파이썬과 프록시: 궁금한 점들

### requests, httpx 또는 aiohttp: 스크래핑에 어떤 걸 써야 하나요?

간단한 스크립트와 소규모 크롤에는 requests를 사용하세요. 동기와 비동기 코드에서 같은 API와 HTTP/2를 `httpx[http2]` 통해 사용하고 싶다면 httpx를 쓰세요. 순수 asyncio로 매우 높은 동시성을 원하면 aiohttp를 사용하세요. 스케줄링, 중복 제거, 파이프라인이 필요한 크롤은 직접 구현하지 말고 [Scrapy](https://crawlrelay.com/ko/docs/scrapy/)를 쓰세요.

### 왜 파이썬 프록시에서 SSL: WRONG_VERSION_NUMBER 오류가 발생하나요?

프록시 주소 앞에 아마도 `https://`를 붙였을 겁니다. 게이트웨이 포트 7777는 일반 HTTP를 쓰고 HTTPS 사이트에는 CONNECT 터널을 열므로, 프록시 URL은 `http`와 `https` 키 모두에서 `http://`로 시작해야 합니다. 자세한 내용은 [오류 페이지](https://crawlrelay.com/ko/docs/errors/ssl-wrong-version-number/)를 참고하세요.

### 파이썬에서 SOCKS5 프록시는 어떻게 쓰나요?

추가 패키지(`pip install "requests[socks]"` 또는 `pip install "httpx[socks]"`)를 설치한 뒤 `socks5h://USERNAME:PASSWORD@gw.crawlrelay.com:7778`를 사용하세요. `h`는 호스트네임을 프록시로 보내서 DNS 확인이 종료 지점에서 이뤄지고, 지역별 방문자와 같은 응답을 받게 됩니다.

### 회전 프록시가 같은 IP를 두 번 반환하는 이유는?

클라이언트가 keep-alive 연결을 재사용했기 때문이며, 회전은 새 연결당 이뤄집니다. requests에서는 `Connection: close`을 보내고, httpx에선 `max_keepalive_connections=0`을 설정하거나 aiohttp에선 `force_close=True`를 사용하세요. 사용자 이름에 `session-` 부분이 있으면 일부러 한 IP를 고정하는 것이므로 확인해보세요.

### 프록시 요청 타임아웃은 어떻게 설정하나요?

명시적으로 설정하세요: requests는 기본 타임아웃이 없습니다. 연결 타임아웃은 약 10초, 읽기 타임아웃은 30초가 HTML 페이지에 적합하며, 모바일 단말 및 느린 대상에는 45~60초가 필요합니다. 타임아웃이 발생하면 더 오래 기다리기보다 새 연결에서 한 번만 재시도하세요. [읽기 타임아웃](https://crawlrelay.com/ko/docs/errors/read-timeout/)도 참고하세요.

### 이 프록시들을 Jupyter나 pandas와 함께 써도 되나요?

네. requests나 httpx를 사용하는 모든 것에 동일한 프록시 설정을 적용할 수 있습니다. URL에 `pandas.read_html` 또는 `read_csv`이 있을 경우, 먼저 프록시를 쓴 requests 호출로 콘텐츠를 다운로드한 뒤 텍스트나 바이트를 pandas에 넘기세요.

**레지덴셜 IP를 통해 파이썬 스크레이퍼 실행하기** US$1.50는 1 GB용, US$0.50/GB는 50 GB에서 제공합니다. [가격 확인](https://crawlrelay.com/ko/pricing/) · [Scrapy 가이드](https://crawlrelay.com/ko/docs/scrapy/)

---

CrawlRelay: CrawlRelay는 개발자 중심의 회전형 레지덴셜 프록시 및 4G/5G 모바일 프록시 서비스로, 스크래퍼와 AI 데이터 파이프라인을 위한 비만료 선불 과금 트래픽을 US$0.50/GB부터 암호화폐로 결제하며 신분증 업로드가 없습니다. KYC 없이 — 신분증, 셀카, 문서 없이 이용 가능. 암호화폐로 결제하세요.
