대부분의 스크래퍼는 header를 단 하나도 읽기 전에 실패합니다.
서버는 TLS handshake(cipher suite 순서, extension 순서, curve 선호도)를 확인하고, 요청이 실제 브라우저인지 아니면 브라우저인 척하는 클라이언트 라이브러리인지 판단합니다. Python requests, Go의 net/http, 일반 curl 모두 연결을 시작하는 순간 고유한 fingerprint를 전달합니다. 보안을 신경 쓰는 사이트(Datadome, Akamai, Imperva, Cloudflare의 관리형 보안 서비스 등)는 User-Agent 문자열을 확인하기도 전에 연결을 끊거나 challenge 페이지를 보여줍니다.
이것이 FourA에서 unblocker: true가 해결하는 문제입니다. 지난 한 달 동안 우리는 이 기능이 안정적으로 작동하도록 만드는 핵심 요소들을 조율했습니다.
업데이트 사항
unblocker: true는 모든 /api/single 호출에서 사용할 수 있는 단일 플래그입니다. 이 플래그를 활성화하면 브라우저 header 세트 주입, curl-impersonate를 통한 실제 브라우저 TLS fingerprint로 request 전송, 서버가 반환하는 모든 데이터(gzip, brotli, deflate)의 압축 해제라는 세 가지 작업이 수행됩니다. 처음 두 기능은 베타 버전부터 제공되었습니다. 세 번째 기능(brotli 자동 압축 해제)은 3월 25일에 출시되었으며, header와 TLS의 동기화를 유지하기 위한 브라우저 버전 고정 작업은 바로 다음 날 적용되었습니다.
작동 원리
request 예시는 다음과 같습니다:
curl -X POST "https://api.foura.ai/api/single" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"url": "https://example.com/products",
"method": "GET",
"unblocker": true
}'
내부적으로는 세 가지 레이어가 작동합니다.
Header 주입. User-Agent, Sec-Ch-Ua, Sec-Ch-Ua-Platform, Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, Accept, Accept-Language, Accept-Encoding을 포함한 전체 브라우저 header 번들을 설정합니다. 순서가 중요합니다. 실제 브라우저는 이를 특정 순서로 전송하며, 탐지 라이브러리는 이 순서를 검사합니다.
TLS fingerprint. curl-impersonate 0.8.2는 BoringSSL을 기반으로 libcurl을 컴파일하고, 대상 브라우저 버전이 실제로 전송하는 것과 일치하도록 TLS extension 순서를 조정합니다. 이를 통해 실제 브라우저 세션과 동일한 JA3 및 JA4 해시가 생성됩니다. 일반 curl, Python requests, Go의 net/http는 보안이 적용된 인프라에서 수 밀리초 내에 시스템에 의해 자동으로 감지 및 차단되는 fingerprint를 생성합니다.
자동 압축 해제. unblocker가 활성화되면 Accept-Encoding을 gzip, deflate, br로 설정하고 libcurl이 body의 압축을 풀도록 합니다. 디코딩된 문자열(또는 returnBuffer: true를 전달한 경우 Buffer)을 반환받게 됩니다. 수동으로 brotli를 처리할 필요가 없으며, 사이트가 gzip 대신 deflate를 선택할 때 발생하는 header와 body 간의 불일치 문제도 방지할 수 있습니다.
버전 고정이 중요한 이유
TLS fingerprint는 버전에 종속적입니다. 이번 달 브라우저의 cipher 순서는 지난달과 다르며, fingerprint를 정밀하게 검사하는 사이트는 이러한 변화를 감지합니다. curl-impersonate는 특정 브라우저 빌드에 대한 프로필을 제공하며, 우리는 실제 브라우저 바이너리를 curl-impersonate가 현재 타겟팅하는 빌드에 고정합니다. header, navigator 객체, TLS가 모두 동일한 버전을 보고하도록 합니다.
번거롭게 들릴 수 있지만, 실제로 그렇습니다. 지난 3월 모노레포 마이그레이션 과정에서 브라우저가 자동 업데이트되면서 header가 curl-impersonate와 동기화되지 않는 문제가 발생했습니다. 해결책은 두 번의 커밋이었습니다. 브라우저 바이너리를 고정하고, 패키지 관리자가 이 둘을 알아서 동기화해 줄 것이라 절대 믿지 않는 것이었습니다.
효과
fingerprint 검사가 엄격한 대상(금융, 여행, 보안이 강화된 이커머스)에 대한 내부 테스트 결과, unblocker: false와 unblocker: true의 차이는 challenge 페이지와 200 응답의 차이였습니다. 일반 curl로 보안이 적용된 Cloudflare에 접속하면 첫 시도에 바로 403 에러가 발생합니다. 동일한 URL에 unblocker: true를 적용하면 TLS hello가 실제 브라우저의 handshake처럼 보이기 때문에 정상적으로 통과합니다.
하지만 fingerprint를 검사하지 않는 사이트(대부분의 공개 API, 구형 CMS 템플릿, IP rate limit만 적용된 사이트 등)에서는 unblocker를 비활성화해 두는 것이 좋으며, 이를 통해 TLS 협상에 소요되는 몇 밀리초를 절약할 수 있습니다. 필요한 곳에만 선택적으로 사용하세요.
고급 사용자를 위한 팁
알아두면 유용한 몇 가지 패턴을 소개합니다.
대상 사이트가 IP 평판도 검사하는 경우, unblocker를 residential proxy와 함께 사용하세요. 데이터센터 IP를 사용하면 TLS handshake가 완벽하더라도 사이트가 블랙리스트에 등록한 ASN으로 인해 차단될 수 있습니다. FourA의 proxy endpoint(/api/proxy)는 대상 도메인별로 교체되므로, request에 "proxy": "residential"을 추가하는 것만으로도 충분합니다.
브라우저 여부를 신경 쓰지 않는 JSON API를 호출할 때는 unblocker를 사용하지 마세요. 프로그래밍 방식의 클라이언트를 예상하는 API(예: 자체 마이크로서비스를 호출하는 백엔드)에는 추가된 header가 오히려 의심스럽게 보일 수 있습니다.
사이트에서 자바스크립트 기반 안티봇(Turnstile 대화형 challenge, 가장 엄격한 단계의 Perimeter X, 휴리스틱이 극대화된 Akamai Bot Manager 등)을 실행하는 경우, unblocker만으로는 충분하지 않습니다. 실제 Chromium을 실행하여 challenge를 해결할 수 있는 browser endpoint가 필요합니다. 이는 크레딧 요금 체계가 다른 별도의 제품이며, 자세한 내용은 Browser Tasks: JavaScript가 많은 사이트를 스크래핑하는 방법 글에 정리해 두었습니다.
또한 unblocker를 validate 블록과 결합하여, 기술적으로는 200을 반환하지만 실제로는 challenge 페이지가 포함된 응답을 거부할 수 있습니다:
{
"url": "https://example.com/products",
"method": "GET",
"unblocker": true,
"validate": {
"data": { "fail": ["captcha", "Access Denied"] }
}
}
이를 통해 감지되지 않는 실패를 분류된 실패로 전환할 수 있으며, 이는 dashboard에서 성공률을 추적할 때 중요한 역할을 합니다.
향후 계획
브라우저는 4주마다 새로운 안정 버전을 출시합니다. curl-impersonate의 메인테이너는 보통 한 달 후에 이를 반영하며, 반영이 완료되면 우리도 스택을 업데이트합니다. 사용자 측에서는 아무것도 변경할 필요가 없습니다. unblocker: true는 우리가 종단 간 검증을 마친 브라우저 버전을 계속해서 가리킵니다.
더 어려운 과제들이 남아 있습니다. HTTP/3 fingerprinting은 이미 상용 안티봇 솔루션에 도입되고 있으며, QUIC 전송은 TLS 1.3보다 스푸핑하기가 더 까다롭습니다. 또한 정적 header 번들에서 벗어나 진정한 동적 에뮬레이션으로의 전환이 시작되고 있습니다. 보안이 적용된 사이트들은 HTTP/2 프레임 순서 및 JA4+ 변형을 검사하기 시작했으며, "브라우저처럼 보이는 curl"과 "실제 브라우저" 사이의 격차는 양쪽 모두에서 좁혀질 것입니다. 이 기능이 출시되면 다시 블로그를 통해 공유해 드리겠습니다.