curl_cffi 프록시 가이드: 설정, 인증, 회전 및 비동기

curl_cffi는 브라우저 모양의 TLS 핸드셰이크를 제공합니다. 프록시는 깨끗한 종료 IP를 제공합니다. 두 가지를 함께 연결하는 방법과 프록시 dict의 https:// 접두사가 ErrCode 35를 발생시키는 이유를 정확히 설명합니다.

curl_cffi는 curl-impersonate 포크에 대한 파이썬 바인딩입니다: urllib3로 자신을 알리는 대신 실제 브라우저의 TLS/JA3 및 HTTP/2 지문을 재현합니다. 이는 차단의 한 축을 해결합니다. 다른 하나는 종료 IP로, 이는 curl_cffi 프록시가 필요한 이유이며 문서가 부족한 부분입니다. 공식 프록시 섹션은 약 15줄 정도이며, 2023년 2월의 GitHub 이슈는 여전히 이 주제에서 상위 5위 안에 듭니다. 이 가이드는 전체 표면을 다룹니다: proxy 매개변수, 요청 스타일의 dict 및 실제 키 이름, proxy_auth, 세션, 요청별 회전, 비동기, SOCKS5 — 그리고 검색 상자에 붙여넣을 정확한 오류 문자열들입니다.

curl_cffi 프록시 구문: proxies dict보다 proxy=를 선호

curl_cffi는 두 가지 형식을 허용합니다. 기본 형식은 v0.6.0에 추가된 단일 proxy= 문자열이며, proxies= dict는 requests 호환성을 위해 존재하며, 문서에서는 실제로 스키마별로 다른 프록시가 필요하지 않는 한 단일 매개변수를 권장합니다. 내부적으로는 동일한 것으로 축소됩니다 — proxy="..."{"all": "..."}로 변환됩니다 — 그리고 모듈 도우미, Session, AsyncSession 및 개별 요청에서 모두 작동합니다.

# pip install curl_cffi --upgrade    (Python 3.10+ since v0.14)
import curl_cffi

PROXY = "http://USER:PASS@gate.quantumproxies.io:PORT"

# Native form — one string, applies to every scheme
r = curl_cffi.get(
    "https://tls.browserleaks.com/json",
    impersonate="chrome",
    proxy=PROXY,
    timeout=30,
)
print(r.status_code, r.json()["ja3n_hash"])

# requests-compatible form
r = curl_cffi.get(
    "https://httpbin.org/ip",
    impersonate="chrome",
    proxies={"http": PROXY, "https": PROXY},
    timeout=30,
)
print(r.json())  # {'origin': '<proxy exit IP>'}

README에서 명확하지 않은 네 가지 사항이 있습니다:

임포트에 대한 한 가지 주의 사항: v0.10.0 이후 패키지는 직접 호출 가능합니다 (curl_cffi.get, curl_cffi.Session). 이전 튜토리얼에서는 from curl_cffi import requests를 사용하며, 이는 여전히 작동하지만 실제 requests 라이브러리 옆에서 읽기 나쁩니다 — 그리고 온라인의 절반의 코드 조각이 다른 프로젝트처럼 보이는 이유를 설명합니다.

https:// 함정: ErrCode 35 및 WRONG_VERSION_NUMBER

이 단일 실수는 다른 모든 것보다 더 많은 curl_cffi 프록시 질문을 생성합니다. 프로젝트 트래커의 issue #6 — 2023년 2월 같은 날에 열리고 닫혔습니다 — 여전히 페이지 1에 랭크되어 있으며, 생성된 오류가 TLS 버그처럼 보이기 때문입니다:

# WRONG: this asks curl to open a TLS connection *to the proxy itself*
proxies = {"https": "https://USER:PASS@gate.quantumproxies.io:PORT"}

# Failed to perform, ErrCode: 35, Reason:
# 'error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER'

# RIGHT: plain HTTP CONNECT, then the TLS tunnel runs through to the target
proxies = {"https": "http://USER:PASS@gate.quantumproxies.io:PORT"}

# Or skip the dict entirely
proxy = "http://USER:PASS@gate.quantumproxies.io:PORT"

키는 대상의 프로토콜을 나타내고; 값은 프록시에 도달하는 방법을 나타냅니다. 일반적인 HTTPS-over-HTTP 프록시는 평문 CONNECT를 받아들인 후 암호화된 트래픽을 그대로 터널링하므로, 프록시 URL은 모든 URL이 HTTPS일 때에도 http://로 시작합니다. HTTPS-over-HTTPS 프록시는 존재하지만 드물며 게이트웨이에서 명시적으로 지원해야 합니다. 요청은 동일한 실패를 훨씬 더 도움이 되게 설명합니다 — 당신의 프록시는 HTTP만 사용하고 HTTPS는 사용하지 않는 것 같습니다 — 그래서 동일한 구성이 curl_cffi 전용 버그처럼 보일 수 있습니다. 최근 버전은 경고와 issue #6을 링크하지만, 이는 단지 경고일 뿐입니다: 요청은 여전히 실패합니다.

인증: URL 자격 증명 또는 proxy_auth

인증된 게이트웨이는 일반적인 내장 형식 http://USER:PASS@host:port를 받아들이며, 일반적인 주의 사항이 있습니다: 비밀번호에 이스케이프되지 않은 @, : 또는 /가 있으면 URL이 잘못된 위치에서 분할되어 죽은 프록시처럼 보이는 인증 실패를 발생시킵니다. curl_cffi는 requests가 제공하지 않는 탈출구를 제공합니다 — proxy_auth 튜플을 libcurl에 별도의 사용자 이름 및 비밀번호 옵션으로 전달하여 인코딩이 전혀 필요하지 않습니다.

import curl_cffi
from urllib.parse import quote

# Option A — credentials in the URL, password URL-encoded
pw = quote("p@ss:word", safe="")
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy=f"http://USER:{pw}@gate.quantumproxies.io:PORT",
    impersonate="chrome",
    timeout=30,
)

# Option B — keep credentials out of the URL entirely
r = curl_cffi.get(
    "https://httpbin.org/ip",
    proxy="http://gate.quantumproxies.io:PORT",
    proxy_auth=("USER", "PASS"),
    impersonate="chrome",
    timeout=30,
)
print(r.json())

세 번째 옵션은 이 클래스의 버그를 완전히 제거합니다: IP 화이트리스트. 모든 QuantumProxies 주거용 플랜은 사용자:비밀번호를 보내는 대신 서버의 IP를 인증할 수 있도록 하여, 프록시 URL이 단순한 http://gate.quantumproxies.io:PORT가 되며 — 인코딩할 것이 없고, 소스 트리에 비밀이 없습니다. 자격 증명 자체가 거부되는 경우, 407 프록시 인증 필요의 모든 원인에 대한 가이드가 나머지를 다룹니다.

브라우저와 일치하는 TLS 핸드셰이크로 대상 사이트로 회전 프록시 게이트웨이를 통해 이동하는 curl_cffi 요청의 흐름 다이어그램
프록시는 핸드셰이크를 종료하지 않고 터널링하므로 브라우저 가장이 홉을 통해 살아남고 대상은 깨끗한 종료 IP를 봅니다.

세션, 쿠키 및 자격 증명 재사용 세부사항

Session은 쿠키, 연결 풀링 및 기본값을 한 곳에 보관하며, 이는 다단계 작업에 필요한 것입니다. impersonateproxy를 한 번 설정하면 모든 요청이 이를 상속받습니다:

from curl_cffi import Session

with Session(
    impersonate="chrome",
    proxy="http://USER-session-a1b2:PASS@gate.quantumproxies.io:PORT",
    timeout=30,
    retry=3,
) as s:
    s.get("https://httpbin.org/cookies/set/foo/bar")
    r = s.get("https://httpbin.org/cookies")
    print(r.json(), s.cookies.get_dict())

두 가지 동작은 주목할 만합니다. 첫째, 프록시가 구성될 때마다 curl_cffi는 libcurl의 proxy-credential-no-reuse 옵션을 켭니다: 프록시 사용자 이름이 변경될 때 새로운 연결이 강제되며, TLS 세션 캐시는 프록시 주소에 따라 키가 설정되므로 이전 종료 IP가 재사용된 세션을 통해 나중 요청에 누출될 수 없습니다. 대부분의 회전 게이트웨이가 하는 것처럼 사용자 이름에 스티키 세션 ID를 인코딩하면 무료로 이러한 격리를 얻을 수 있습니다. 둘째, retry (정수 또는 curl_cffi.requestsRetryStrategy로 지연, 백오프 및 지터 포함)는 전송 예외에서만 다시 실행됩니다. 이는 403 또는 429를 urllib3의 status_forcelist처럼 재시도하지 않습니다 — 그 루프는 여전히 작성해야 합니다. 호환성 문서에서는 재시도를 지원하지 않는다고 나와 있지만, 이는 오래된 정보입니다: 매개변수는 v0.15.0에 도입되었습니다.

curl_cffi를 회전 주거용 게이트웨이에 지정

요청별 회전 및 비동기

curl_cffi는 각 요청에서 프록시 회전을 통한 asyncio를 광고하며, 이는 문자 그대로입니다: 개별 호출의 proxy= 인수는 세션에 있는 내용을 무시합니다. 이를 활용하기 위해 프록시 목록이 거의 필요하지 않습니다 — 회전 게이트웨이는 각 연결에서 서버 측에서 새로운 종료를 할당하므로, 하나의 엔드포인트와 동시성만으로도 이미 회전입니다. 제어가 필요한 경우 (작업자별, 계정별, 장바구니별 안정적인 IP 하나) 사용자 이름에 세션 토큰을 넣고 게이트웨이가 해당 종료를 고정하도록 하십시오.

import asyncio
from curl_cffi import AsyncSession

GATE = "gate.quantumproxies.io:PORT"
URLS = ["https://httpbin.org/ip"] * 20

async def fetch(session, url, worker):
    # one sticky exit IP per worker; drop the -session- suffix for full rotation
    proxy = f"http://USER-session-{worker}:PASS@{GATE}"
    r = await session.get(url, proxy=proxy, timeout=30)
    return r.status_code, r.json()["origin"]

async def main():
    async with AsyncSession(impersonate="chrome", max_clients=10) as s:
        return await asyncio.gather(
            *(fetch(s, u, i % 5) for i, u in enumerate(URLS))
        )

for status, ip in asyncio.run(main()):
    print(status, ip)

max_clients는 풀의 동시 curl 핸들을 제한합니다 (기본값 10), 따라서 이는 실제 동시성 다이얼입니다 — 무제한 gather에 세마포어를 붙이는 것이 일반적인 실수입니다. 동일한 크기 조정 논리는 httpx 및 aiohttp를 사용한 비동기 파이썬 스크래핑에서 다룬 모든 비동기 클라이언트에 적용됩니다. 요청별 회전 또는 세션 고정 여부는 사이트가 요청 간 상태를 추적하는지 여부에 따라 다릅니다; 스티키 세션 대 회전 프록시의 트레이드오프가 있습니다.

작동하는 curl_cffi 프록시 구성과 이를 깨뜨리는 다섯 가지 실수를 대조하는 체크리스트
대부분의 curl_cffi 프록시 실패는 다섯 가지 중 하나입니다 — 그 중 네 가지는 문자열의 단일 문자입니다.

SOCKS5, HTTP/3 및 안전 스위치

SOCKS는 추가 설치가 필요 없습니다 — libcurl이 컴파일되어 있으므로, requests와 달리 기억해야 할 [socks] 추가 기능이 없습니다. socks5h://USER:PASS@gate.quantumproxies.io:PORT를 사용하십시오: h는 DNS 해석을 프록시로 밀어내어, 네트워크에서의 누출을 방지하고 종료 위치에서 지리적으로 제한된 호스트 이름을 해석합니다. curl_cffi는 socks 접두사를 감지하고 HTTP 터널링 플래그를 건너뜁니다, SOCKS 프로토콜이 이를 자체적으로 처리하기 때문입니다. 여기의 모든 플랜은 동일한 게이트웨이에서 HTTP 및 SOCKS5 엔드포인트를 노출하므로, 전환은 새로운 주문이 아닌 스키마 교체입니다.

curl_cffi와 프록시가 충분할 때

사람들이 예상하는 것보다 더 자주. 대상이 내부 API에서 JSON을 제공하거나 서버 렌더링된 HTML을 제공하고, 유일한 장애물이 지문 검사인 경우, 일치하는 핸드셰이크와 주거용 종료가 브라우저의 비용과 지연의 일부로 이를 해결합니다. 프로젝트의 FAQ는 한계를 명확히 합니다: 지문은 여러 요소 중 하나이며, IP 품질, 요청 속도 및 JavaScript 검사와 함께 있으며, 더 높은 보호 계층은 더 나은 프록시 풀과 실제 브라우저 자동화가 필요합니다. 가장이 올바르게 구성되었고 여전히 차단되는 경우, 남은 변수는 거의 항상 종료 IP입니다 — 이를 5분 내에 격리하는 것이 curl_cffi 대 requests의 주제입니다. 둘 다 실행하고 싶지 않다면, Scraper API는 지문, 프록시 및 선택적 JS 렌더링을 한 번의 호출로 처리합니다.

자주 묻는 질문

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

proxy="http://USER:PASS@host:port"를 요청 메서드, 세션 또는 비동기 세션에 전달하십시오. 요청 스타일의 proxies={"http": ..., "https": ...} dict도 작동하지만, 프로젝트는 실제로 스키마별로 다른 프록시가 필요하지 않는 한 단일 매개변수를 권장합니다. 둘 다 전달하면 TypeError가 발생합니다.

curl_cffi가 ErrCode 35 WRONG_VERSION_NUMBER를 던지는 이유는 무엇인가요?

프록시 URL이 https://로 시작하기 때문입니다. 표준 프록시는 평문 CONNECT 요청을 기대한 후 TLS를 터널링하므로, https:// 접두사는 curl이 프록시 자체와 TLS 핸드셰이크를 시도하게 만듭니다, 이는 평문 HTTP로 응답합니다. 값을 http://로 변경하십시오 — https 키는 홉이 아닌 대상을 나타냅니다.

curl_cffi가 SOCKS5 프록시를 지원하나요?

예, 기본적으로 지원합니다 — libcurl이 번들되어 있으므로 설치할 추가 옵션이 없습니다. socks5h:// 스키마를 사용하여 호스트 이름이 기계가 아닌 프록시에 의해 해석되도록 하십시오. SOCKS4, SOCKS4a 및 평문 socks5://도 허용됩니다; 라이브러리는 스키마가 socks로 시작하는 모든 프록시에 대해 HTTP 터널링을 건너뜁니다.

curl_cffi가 모든 요청에서 프록시를 회전할 수 있나요?

예. 개별 호출의 proxy= 인수는 AsyncSession 내에서도 세션 기본값을 무시합니다, 이는 README에서 요청별 회전과 함께 asyncio를 의미합니다. 회전 게이트웨이와 함께라면 논리가 거의 필요하지 않습니다: 동일한 엔드포인트가 연결당 다른 종료 IP를 제공합니다.

curl_cffi가 Cloudflare를 우회할 수 있나요?

때때로 가능합니다. 이는 TLS 및 HTTP/2 지문을 제거하여 기본 보호 수준에 충분합니다. JavaScript 챌린지를 실행하거나 Turnstile을 해결하거나 이미 평판 데이터베이스에 플래그가 지정된 데이터센터 IP를 복구할 수 없습니다. 가장을 세 가지 요구 사항 중 하나로 취급하고, 답이 아닙니다.

전체 구성은 명성보다 작습니다: 하나의 proxy 문자열, 세션에 한 번 설정된 impersonate, 모든 호출에 대한 타임아웃, URL 인코딩된 자격 증명 또는 proxy_auth 튜플로 전달된 자격 증명. 이를 올바르게 설정하고 남은 변수는 IP 품질입니다 — 플래그가 지정된 데이터센터 주소에서 완벽한 Chrome 핸드셰이크는 여전히 플래그가 지정된 데이터센터 주소입니다. JA3 및 JA4 지문에 대한 우리의 분석은 두 가지 검사가 독립적임을 설명합니다.

가장에 맞는 주거용 IP를 얻으세요