← 전체 글

브라우저 프로필 내부 분석: `unblocker: true`의 실제 동작 원리

단일 플래그로 실제 브라우저 헤더, 일치하는 연결 서명, gzip 및 brotli 자동 압축 해제 등 세 가지 기능을 활성화합니다. 이 기능의 동작 방식과 이유를 설명합니다.

대부분의 스크래퍼는 단 하나의 header가 읽히기도 전에 실패합니다.

서버는 클라이언트가 전송하는 연결 수준의 시그니처를 확인하여 실제 브라우저인지, 아니면 브라우저인 척하는 클라이언트 라이브러리인지 판단합니다. Python requests, Go의 net/http, 기본 curl 모두 통신을 시작하는 즉시 고유한 핑거프린트를 드러냅니다. 보안에 신경 쓰는 사이트(Datadome, Akamai, Imperva, Cloudflare의 관리형 서비스 등)는 User-Agent 문자열을 확인하기도 전에 연결을 끊거나 챌린지 페이지를 반환합니다.

FourA의 unblocker: true이 바로 이 문제를 해결합니다. 지난 한 달 동안 이를 안정적으로 동작하게 만드는 핵심 요소들을 구현했습니다.

새로운 기능

unblocker: true는 모든 /api/single 호출에서 사용할 수 있는 단일 플래그입니다. 이를 활성화하면 브라우저 header 세트 주입, 실제 브라우저 전송 계층과 일치하는 transport를 통한 request 전송, 서버가 반환하는 응답(gzip, brotli, deflate) 자동 압축 해제라는 세 가지 작업이 수행됩니다. 앞의 두 기능은 베타 버전부터 제공되었습니다. 세 번째 기능인 brotli 자동 압축 해제는 3월 25일에 배포되었으며, header와 transport의 일관성을 유지하기 위한 버전 고정 작업은 다음 날 적용되었습니다.

동작 방식

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
  }'

그 아래에서는 3개의 계층이 작동합니다.

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 번들을 설정합니다. 순서가 중요합니다. 실제 브라우저는 특정 순서로 이를 전송하며, 탐지 라이브러리는 이 순서를 확인합니다.

연결 서명. 전송 계층은 최신 브라우저 세션의 바이트 레벨 형태를 그대로 따릅니다. 동일한 확장 순서, 동일한 암호화 제품군 우선순위, 동일한 핸드셰이크 특성을 유지합니다. 표준 cURL, Python requests, Go의 net/http가 생성하는 서명은 보안 인프라에서 수 밀리초 만에 기계적으로 플래그 처리됩니다.

자동 압축 해제. unblocker 옵션이 활성화되면 Accept-Encoding을 gzip, deflate, br로 설정하고 전송 계층이 body를 압축 해제합니다. 디코딩된 문자열을 직접 반환받을 수 있습니다(returnBuffer: true를 전달하면 Buffer 반환). 수동으로 brotli를 처리할 필요가 없으며, 사이트가 gzip 대신 deflate를 선택했을 때 발생하는 header와 body 불일치 문제도 없습니다.

버전 고정이 중요한 이유

연결 서명은 특정 버전에 종속됩니다. 이번 달 브라우저의 와이어 레벨 세부 사양은 지난달과 다르며, 세밀하게 핑거프린팅하는 사이트는 이러한 변화를 감지합니다. 우리는 변경되는 요소들을 함께 고정하여 header, navigator 객체, 연결 서명이 모두 동일한 브라우저 버전을 가리키도록 맞춥니다.

다소 번거로워 보일 수 있지만, 실제로 그렇습니다. 지난 3월 모노레포 마이그레이션 당시 한 컴포넌트가 자동 업데이트되면서 다른 부분과 동기화가 깨지는 문제를 겪었습니다. 해결책은 두 번의 커밋이었습니다. 변경 요소를 고정하고, 패키지 관리자가 버전을 알아서 맞춰줄 것이라 기대하지 않는 것입니다.

영향

요청 주체를 검증하는 사이트(금융, 여행, 대형 전자상거래)를 대상으로 한 내부 테스트에서, unblocker: false와 unblocker: true의 차이는 보안 챌린지 페이지와 200 상태 코드의 차이로 나타납니다. 일반 HTTP 클라이언트는 첫 요청부터 403을 받는 경우가 많습니다. 동일한 URL에 unblocker: true를 적용하면 요청이 실제 브라우저처럼 인식되어 정상적으로 페이지를 수신합니다.

반면 핑거프린팅을 수행하지 않는 사이트(대부분의 공개 API, 구형 CMS 템플릿, IP rate limit만 적용된 대상)는 unblocker 설정을 꺼두어도 무방하며 협상 시간을 수 밀리초 단축할 수 있습니다. 필요한 곳에만 선택적으로 사용하세요.

고급 사용자를 위한 팁

알아두면 유용한 몇 가지 패턴입니다.

대상 사이트가 IP 평판도 검증한다면 unblocker를 주거용 proxy와 함께 사용하세요. 완벽한 연결 서명을 갖추었더라도 데이터센터 IP는 사이트가 블랙리스트에 등록한 ASN에서 차단될 수 있습니다. FourA의 proxy endpoint(/api/proxy)는 대상 도메인별로 로테이션되므로, request에 "proxy": "residential"만 추가해도 충분합니다.

브라우저 환경을 신경 쓰지 않는 JSON API를 호출할 때는 unblocker를 건너뛰세요. 프로그래밍 방식의 클라이언트를 예상하는 API(예: 자체 마이크로서비스를 호출하는 백엔드)에는 추가 header가 오히려 의심스러운 신호가 될 수 있습니다.

사이트가 페이지를 표시하기 전에 JavaScript로 방문자를 검증하는 경우, unblocker만으로는 충분하지 않습니다. 전체 브라우저 환경에서 페이지의 JavaScript를 실행하는 브라우저 endpoint가 필요합니다. 이는 크레딧 가격 정책이 다른 별도의 제품이며, 관련 내용은 Browser Tasks: How to Scrape JavaScript-Heavy Sites에 정리해 두었습니다.

또한 unblocker를 validate 블록과 조합하여 기술적으로 200 상태 코드를 반환하지만 챌린지 페이지가 포함된 response를 거부하도록 설정할 수 있습니다:

{
  "url": "https://example.com/products",
  "method": "GET",
  "unblocker": true,
  "validate": {
    "data": { "fail": ["captcha", "Access Denied"] }
  }
}

이를 통해 조용한 실패(silent failure)가 명확히 분류된 실패로 전환되며, 이는 dashboard에서 성공률을 추적하는 데 중요합니다.

향후 계획

브라우저는 4주마다 새로운 안정 버전을 출시합니다. FourA는 이에 맞춰 스택을 업데이트합니다. 사용자 측에서는 아무것도 수정할 필요가 없습니다. unblocker: true는 FourA가 엔드투엔드로 검증을 완료한 브라우저 버전을 항상 가리킵니다.

더 까다로운 과제들이 남아 있습니다. 규모가 큰 사이트에서는 이미 HTTP/3 검사가 나타나고 있으며, QUIC 전송은 기존 전송 계층보다 모방하기가 더 까다롭습니다. 정적 header 번들에서 진정한 동적 에뮬레이션으로의 전환도 시작되고 있습니다. 보호 수준이 높은 사이트들은 HTTP/2 프레임 순서 검사로 전환했으며, "브라우저처럼 보이는 라이브러리"와 "실제 브라우저" 사이의 격차는 양방향에서 좁혀질 것입니다. 해당 기능이 배포되면 관련 내용을 다시 공유하겠습니다.