curl_cffi vs requests: 무엇을 해결하고 무엇을 해결하지 못하는가
requests를 curl_cffi로 교체하면 많은 403이 200으로 바뀝니다. 그러나 소진된 IP에 대해서는 아무런 효과가 없습니다. 두 가지의 차이점과 문제의 원인을 알려주는 15분 테스트를 소개합니다.
curl_cffi vs requests 질문은 보통 사건 중간에 도착합니다: 몇 달 동안 실행되던 스크래퍼가 첫 번째 호출에서 403을 반환하기 시작하고, Reddit에서 클라이언트를 교체하라는 말을 듣고 실제로 작동합니다. 이는 실제 효과와 설명이 있는 현상입니다 — 그러나 '그냥 curl_cffi를 사용하세요'라는 방식으로 반복되면서 실제로 무슨 일이 일어나고 있는지와 어디서 도움이 멈추는지를 숨깁니다. requests는 느리거나 잘못 작성된 것이 아닙니다. 스크래핑 컨텍스트에서 정확히 하나의 단점이 있으며, 이는 API와는 무관합니다. 두 클라이언트가 진정으로 다른 점, 교체 시 변화하는 점, 그리고 어떤 라이브러리를 선택하든 위장이 절대 해결하지 못할 한 가지를 설명합니다.
중요한 한 가지 차이점
두 라이브러리는 동일한 헤더를 보냅니다. 차이점은 한 단계 아래에 있으며, 이는 HTTP 바이트가 이동하기 전에 연결을 여는 TLS 핸드셰이크에 있습니다. requests는 urllib3와 OpenSSL 위에 있으며, 이는 Python에 속하는 암호 목록, 확장 세트 및 순서를 광고합니다. curl_cffi는 브라우저의 ClientHello 바이트를 그대로 재현하는 curl의 패치된 포크에 대한 바인딩이며, HTTP/2 SETTINGS 프레임도 함께 재현합니다 — 따라서 JA3, JA3N 및 Akamai 해시는 스크립팅 라이브러리보다 실제 Chrome과 일치합니다. 안티봇 벤더는 이러한 서명을 데이터베이스에 저장합니다; Chrome User-Agent 헤더와 Python 핸드셰이크 간의 불일치는 해결할 수 없는 모순입니다. 우리는 JA3 및 JA4 지문 인식에서 이 메커니즘을 분석했으며, 동일한 효과가 curl이 브라우저가 200을 받는 동일한 URL에서 403을 받는 이유를 설명합니다.
비교의 나머지 모든 것은 구현에서 비롯됩니다. curl_cffi는 libcurl을 감싸고 있기 때문에 HTTP/2, HTTP/3, 웹소켓 및 asyncio를 상속받으며, requests는 이를 지원한 적이 없습니다. requests는 순수 Python이기 때문에 어디서든 설치할 수 있으며, 10년의 생태계를 가지고 있습니다. 두 진술은 동시에 참이며, 어느 쪽이 우세한지는 전적으로 대상에 달려 있습니다.
5분 안에 실행할 수 있는 재현 가능한 테스트
우리의 통과율 표를 포함하여 누구의 말을 맹신하지 마십시오. 지문 차이는 직접 관찰할 수 있습니다: 두 클라이언트를 TLS-echo 엔드포인트에 연결하고 그들이 보고하는 해시를 비교하십시오. 두 줄이 일치하면, 당신의 빌드는 아무것도 위장하지 않고 있는 것입니다.
# pip install requests curl_cffi
import requests
import curl_cffi
URL = "https://tls.browserleaks.com/json"
a = requests.get(URL, timeout=30).json()
b = curl_cffi.get(URL, impersonate="chrome", timeout=30).json()
print("requests ja3n:", a["ja3n_hash"], "| akamai:", a.get("akamai_hash", "-"))
print("curl_cffi ja3n:", b["ja3n_hash"], "| akamai:", b.get("akamai_hash", "-"))
# Two different ja3n hashes = impersonation is working. The curl_cffi README
# documents aa56c057ad164ec4fdcb7a5a283be9fc as a Chrome-matched ja3n value.
# The akamai (HTTP/2) fingerprint is usually empty for requests: it is
# HTTP/1.1 only, so there is no SETTINGS frame to fingerprint in the first place.
v0.15부터 동일한 검사를 위한 단일 명령어 버전이 있습니다: curl-cffi get tls.browserleaks.com/json --impersonate chrome. 다른 디버깅을 시작하기 전에 실행하십시오 — 이는 "내 위장이 잘못 구성되어 있다"와 "내 위장은 괜찮고 다른 것이 나를 차단하고 있다"를 구분합니다, 이는 완전히 다른 오후입니다.

curl_cffi가 해결하지 못하는 것: IP 평판
여기서 라이브러리 교체 조언이 빠뜨린 부분이 있습니다. TLS 지문은 "이것이 어떤 소프트웨어인가?"라는 질문에 답합니다. "어디서 오는가?"에 대해서는 아무것도 말하지 않습니다 — 그리고 두 번째 질문은 당신의 출구 IP에 대한 별도의 조회를 통해 답변됩니다: 어떤 ASN이 소유하고 있는지, 호스팅 제공자인지 소비자 ISP인지, 남용 피드에 나타났는지, 지난 한 시간 동안 같은 주소에서 이 사이트에 몇 개의 다른 세션이 있었는지. 데이터센터 범위의 클라우드 VM에서 도착한 완벽한 Chrome 핸드셰이크는 서버 랙에 설치된 Chrome 브라우저입니다. 이는 python-requests보다 더 설득력 있는 것이 아닙니다. 어떤 경우에는 모순이 더 날카롭기 때문에 덜 설득력 있습니다.
프로젝트 자체의 FAQ는 IP 품질을 요청 속도와 JavaScript 지문보다 우선시하며, 위장만으로는 충분하지 않을 수 있는 이유를 설명합니다. 그 순서는 우연이 아닙니다: 평판은 방어자가 평가하기에 가장 저렴한 신호이며, 공격자가 위조하기에 가장 어려운 신호입니다, 헤더나 암호 목록과 달리 로컬에서 생성할 수 없기 때문입니다. requests 기반 스크래퍼가 이미 데이터센터 풀을 통해 실행되고 차단되고 있었다면, 동일한 풀에서 curl_cffi로 이동하면 실패한 두 가지 검사 중 하나가 변경됩니다. 부드러운 대상에서는 부분적인 개선을, 어려운 대상에서는 전혀 개선을 보지 못할 것입니다 — 이는 사람들이 보고하는 혼란스러운 결과와 정확히 일치합니다.
그 축을 위한 해결책은 코드가 아닌 주소 품질입니다: 실제 소비자 ISP 할당에서 나온 주거 IP, 이는 200개 이상의 국가에서 9천만 개 이상의 주소를 제공합니다. 변경하기 전에 현재 출구가 어떻게 보이는지 확인하고 싶다면, 우리의 무료 IP 품질 검사기가 대상이 볼 수 있는 ASN 및 분류를 보고합니다.
어느 축이 고장났는지를 알려주는 2x2
추측하지 말고 실제 대상에 대해 두 변수를 독립적으로 테스트하십시오. 네 개의 요청, 네 개의 출력 줄, 그리고 결과가 문제를 명명합니다:
import requests
import curl_cffi
TARGET = "https://your-target.example/api/items"
DC = "http://USER:PASS@your-datacenter-gateway:PORT"
RES = "http://USER:PASS@gate.quantumproxies.io:PORT"
def probe(label, fn):
try:
print(f"{label:26} -> {fn().status_code}")
except Exception as e:
print(f"{label:26} -> {type(e).__name__}")
probe("requests + datacenter",
lambda: requests.get(TARGET, proxies={"https": DC}, timeout=30))
probe("curl_cffi + datacenter",
lambda: curl_cffi.get(TARGET, proxy=DC, impersonate="chrome", timeout=30))
probe("requests + residential",
lambda: requests.get(TARGET, proxies={"https": RES}, timeout=30))
probe("curl_cffi + residential",
lambda: curl_cffi.get(TARGET, proxy=RES, impersonate="chrome", timeout=30))
네 개의 결과를 진리표처럼 읽으십시오:
- 데이터센터 행만 실패 — IP 평판 문제입니다. 클라이언트는 무관합니다; 더 나은 출구를 구매하십시오.
- requests 행만 실패 — TLS 지문 문제입니다. 클라이언트를 교체하고 기존 풀을 유지하십시오.
- 마지막 행만 통과 — 두 검사가 모두 활성화되었습니다. 위장과 깨끗한 IP가 함께 필요합니다; 이는 심각한 대상에서 흔한 경우입니다.
- 네 개 모두 실패 — HTTP 클라이언트가 할 수 있는 것을 넘어섰습니다. 이는 JavaScript 챌린지, 생성하지 않은 토큰, 또는 계정 수준의 차단을 의미합니다. 실제 브라우저나 관리되는 Scraper API를 사용하십시오.
- 네 개 모두 통과 — 지문 문제는 없었습니다. 종속성을 추가하지 마십시오.
한 번이 아니라 몇 번 실행하십시오. 차단 레이어는 확률적이며, 단일 200은 거의 아무것도 말해주지 않습니다.

추가 종속성이 가치가 없을 때
솔직함은 재작성보다 저렴합니다. 다음과 같은 경우 requests에 머무르십시오:
- 당신이 호출할 권한이 있는 API를 호출하는 경우. 문서화된 엔드포인트는 당신의 키로 지문을 남기지 않습니다. 거기에 위장을 추가하는 것은 미신입니다.
- 배포 대상이 까다로운 경우. curl_cffi는 컴파일된 휠을 제공하며 v0.14부터 Python 3.10 이상이 필요합니다. requests는 오래된 이미지와 제한된 임베디드 환경을 포함하여 거의 모든 곳에서 실행됩니다.
- requests 생태계에 의존하는 경우. 커스텀 어댑터, requests-cache, requests-oauthlib 등은 curl_cffi가 의도적으로 노출하지 않는 전송 레이어를 훅합니다.
- 상태 코드 재시도가 필요한 경우. urllib3의
Retry는status_forcelist로 429 및 503에서 재시도합니다; curl_cffi의retry매개변수는 전송 예외에서만 재실행합니다. - 차단이 행동적인 경우. 속도 제한, 계정 차단 및 세션별 할당량은 핸드셰이크와 전혀 관련이 없습니다.
그리고 대부분의 사람들이 놓치는 중간 경로가 있습니다: 핸드셰이크를 얻기 위해 requests를 포기할 필요가 없습니다. 유지보수자들은 curl-adapter를 가리키며, 이는 curl_cffi를 requests 전송 어댑터로 장착하고, PyPI의 httpx-curl-cffi는 httpx에 대해 동일한 작업을 수행합니다. 기존 코드와 생태계를 유지하고, 오직 전송되는 바이트만 변경됩니다.
알아두면 좋은 마이그레이션 주의사항
API는 대부분의 스크립트가 import를 변경한 후 실행될 만큼 충분히 가깝지만, 호환성 페이지에는 실제 차이점이 나열되어 있으며, 대규모 포트를 하기 전에 읽어두는 것이 좋습니다. 리다이렉트 응답 본문은 Response.history에 유지되지 않습니다. 빈 도메인이 있는 쿠키는 리다이렉트를 통해 손실될 수 있습니다. 스트리밍 응답 객체는 피클링할 수 없지만, 일반 응답은 가능합니다. 파일 API는 약간 다릅니다. 그리고 전송이나 어댑터는 전혀 없습니다, 왜냐하면 라이브러리는 의도적으로 libcurl-impersonate에 고정되어 있기 때문입니다. 프록시 구성도 사람들을 혼란스럽게 만드는 작은 방식으로 다릅니다:
# requests: dict, and retries mounted on an adapter
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=Retry(total=4, status_forcelist=[429, 503])))
r = s.get(url, proxies={"https": "http://USER:PASS@gate.quantumproxies.io:PORT"}, timeout=30)
# curl_cffi: single proxy string preferred, retries are a session parameter
from curl_cffi import Session
from curl_cffi.requests import RetryStrategy
s = Session(
impersonate="chrome",
proxy="http://USER:PASS@gate.quantumproxies.io:PORT",
retry=RetryStrategy(count=4, delay=1.0, backoff="exponential"),
timeout=30,
)
r = s.get(url) # note: retry fires on transport errors, not on 429/503
전체 프록시 표면 — dict 키, proxy_auth, 요청별 회전, 비동기 및 잘못된 WRONG_VERSION_NUMBER 오류를 생성하는 https:// 접두사 —는 우리의 curl_cffi 프록시 가이드에서 단계별로 다루어집니다. 그대로 유지할 경우, 다른 쪽에 대한 동등한 참조는 우리의 Python Requests 프록시 가이드입니다.
자주 묻는 질문
curl_cffi는 requests보다 빠른가요?
예, 그리고 프로젝트의 벤치마크는 이를 requests가 아닌 aiohttp 및 pycurl과 동등하게 놓습니다. 이점은 C에서 작업을 수행하는 libcurl과 HTTP/2 멀티플렉싱에서 비롯되며, 똑똑한 Python에서 비롯된 것이 아닙니다. 몇 개의 순차적 호출에서는 차이가 보이지 않지만, 높은 동시성, 특히 비동기에서는 상당합니다.
curl_cffi는 안전하게 사용할 수 있나요?
MIT 라이선스이며, 널리 배포되고 사전 컴파일된 휠을 제공하므로 빌드 단계가 없습니다. 주의할 만한 한 가지 경고는 v0.15.0 권고 사항으로, 리다이렉트 기반 SSRF를 다룹니다. 다른 사람들이 제공한 URL을 가져오는 경우, allow_redirects="safe"를 설정하거나 리다이렉트를 비활성화하십시오. 브라우저를 위장하는 것은 기술적 조치이지, 사이트의 약관을 무시할 수 있는 허가가 아닙니다.
curl_cffi는 Cloudflare를 우회하나요?
TLS 및 HTTP/2 지문을 제거하여 기본 보호 수준을 제거합니다. JavaScript 챌린지를 실행하거나 Turnstile을 해결하거나 플래그된 출구 IP를 수정할 수 없습니다. 유지보수자들은 FAQ에서 이를 명확히 하고 있으며, 더 나은 프록시 풀과 브라우저 자동화를 더 높은 수준에 추천합니다.
curl_cffi vs httpx 또는 tls_client — 무엇을 사용해야 하나요?
httpx는 HTTP/2와 비동기를 제공하지만 지문 위장은 제공하지 않으므로, requests와 curl_cffi 사이에 위치합니다. tls_client도 TLS 프로필을 위장하며 유사한 벤치마크를 보입니다; curl_cffi는 더 큰 커뮤니티를 가지고 있으며 HTTP/3 및 웹소켓을 추가합니다. httpx가 이미 스택에 있다면, httpx-curl-cffi 전송은 재작성 없이 위장을 제공합니다.
간단히 말해: 대상이 핸드셰이크를 읽을 때 curl_cffi로 전환하고, 그렇지 않을 때 requests에 머무르며, 어느 선택도 데이터센터 IP를 정화할 것이라고 기대하지 마십시오. 클라이언트는 한 축에서 다르고, 프록시는 다른 축에서 다르며, 차단된 스크래퍼는 거의 항상 두 가지에 대한 이야기입니다. 네 개의 프로브를 실행하고, 진리표를 읽고, 인터넷이 소리쳤던 것이 아니라 데이터가 가리키는 축을 수정하십시오.