← 블로그로 돌아가기

왜 파서가 클린 레지던트 IP를 통해서도 TLS 지문으로 감지되는가: 확인 및 수정 방법

레지던트 IP는 TLS 파서 지문이 일반 브라우저 지문과 다르면 차단을 피할 수 없습니다. 이를 확인하고 올바르게 설정하는 방법을 알아봅니다.

📅2026년 9월 28일

비싼 레지던트 IP를 사용하고, 회전을 설정하고, 현실적인 User-Agent를 설정했지만, 파서는 여전히 CAPTCHA에 걸리거나 빈 응답을 받습니다. 문제는 거의 항상 IP가 아니라 TLS 지문에 있습니다: HTTPS 요청을 보내는 라이브러리가 실제 브라우저처럼 "들리지" 않습니다. Wildberries, Ozon, Cloudflare 및 Akamai의 안티봇 시스템은 귀하의 IP 주소를 확인하기 전에 이를 감지합니다.

TLS 지문이란 무엇이며 왜 IP보다 중요한가

클라이언트가 HTTPS 연결을 설정할 때, 그는 서버에 ClientHello 패킷을 보냅니다 — TLS 핸드셰이크의 일부입니다. 이 패킷에는 지원되는 TLS 버전 목록, 암호 스위트, 확장 순서, 타원 곡선 및 압축 알고리즘이 암호화되어 있습니다. 이 매개변수 집합은 "라이브러리 + 운영 체제 + TLS 스택 버전"의 조합마다 고유합니다.

Chrome, Firefox 및 Safari는 각기 다른 방식으로 ClientHello를 형성하며, 이 집합은 요청 간에 거의 변하지 않습니다 — IP나 User-Agent와는 달리, 이는 쉽게 조작할 수 있습니다. 그러나 표준 HTTP 라이브러리인 requests, urllib3, Java의 표준 HttpClient, Node.js의 내장 TLS 스택은 브라우저와는 다르게 OpenSSL 또는 다른 라이브러리를 사용하기 때문에 전혀 다른 ClientHello를 형성합니다.

그렇기 때문에 완벽하게 "깨끗한" 레지던트 IP를 연결하고, 실제 Chrome의 최신 User-Agent를 설정하더라도 여전히 차단될 수 있습니다. 서버는 주택에서 오는 사용자의 IP를 보고, "Chrome 124"라는 헤더를 보지만, TLS 핸드셰이크는 "이것은 Python 스크립트입니다"라고 말합니다. 불일치는 안티봇에게 직접적인 신호입니다.

안티봇 시스템이 JA3/JA4를 통해 파서를 어떻게 감지하는가

ClientHello의 매개변수를 간결한 식별자로 변환하기 위해 JA3 (그리고 그보다 최신 버전인 JA4) 알고리즘이 사용됩니다. 이 알고리즘은 TLS 버전, 암호 목록, 확장 및 곡선을 가져와서 이를 문자열로 결합하고 MD5를 통해 해시합니다. 결과는 769,47-53-5-10...,0-23-65281...,29-23-24,0와 같은 짧은 해시로, 클라이언트의 "지문"을 명확하게 식별합니다.

안티봇 제공업체 (Cloudflare, Akamai, PerimeterX, DataDome 및 Wildberries와 Ozon이 사용하는 유사 서비스)는 인기 있는 HTTP 라이브러리의 알려진 JA3/JA4 해시 데이터베이스를 유지합니다: requests, aiohttp, Scrapy, Node fetch, Java HttpClient, Go net/http. 해시가 "스크립트"의 알려진 서명과 일치하고 Chrome/Firefox/Safari의 서명과 일치하지 않으면 요청은 행동 분석 전에 의심스러운 것으로 표시됩니다.

이후 시스템은 TLS 지문과 선언된 User-Agent의 일치를 확인합니다. 헤더에 "Windows의 Chrome 124"라고 적혀 있고, TLS 지문이 Python의 표준 라이브러리에서 OpenSSL 1.1.1에 해당하면, 이는 TLS/HTTP 불일치로 불리며, 자동화 감지의 가장 신뢰할 수 있는 신호 중 하나입니다. 이는 완벽한 레지던트 IP와 올바른 헤더가 있더라도 파서를 감지하는 방법입니다.

자신의 TLS 지문을 확인하는 방법: 도구들

문제를 수정하기 전에 서버가 보는 것을 확인해야 합니다. 자신의 JA3/JA4 해시와 ClientHello의 전체 매개변수를 보여주는 몇 가지 공개 서비스가 있습니다:

  • tls.peet.ws — JA3, JA4, 암호 및 확장 목록을 JSON 형식으로 보여주며, 스크립트를 통한 자동 검증에 유용합니다.
  • ja3er.com — 특정 라이브러리 및 브라우저에 연결된 알려진 JA3 해시 데이터베이스입니다.
  • browserleaks.com/tls — 일반적인 브라우저와의 지문을 시각적으로 비교합니다.
  • Wireshark 로컬 — 스크립트에서 요청을 보낼 때 ClientHello의 원시 패킷을 보고 싶다면 사용합니다.

실용적인 테스트는 간단합니다: 일반 Chrome에서 tls.peet.ws를 열고 JA4 해시를 기록합니다. 그런 다음 같은 주소에 대해 파서에서 GET 요청을 보내고 (requests, curl_cffi 또는 다른 라이브러리를 통해) 해시를 비교합니다. 해시가 다르면 서버는 각 요청에서 "브라우저"와 "스크립트" 간의 차이를 감지하며, IP가 얼마나 깨끗하든 상관없이 이를 인식합니다.

Python에서의 확인: requests, httpx, curl_cffi

표준 Python 라이브러리가 파서를 어떻게 감지하는지 실습을 통해 알아보겠습니다. requests를 통한 일반 요청:

import requests

resp = requests.get("https://tls.peet.ws/api/all", proxies={
    "https": "http://user:pass@proxy_host:port"
})
print(resp.json()["tls"]["ja4"])
# 결과는 실제 Chrome의 JA4와 다를 것입니다,
# requests는 Python의 표준 ssl 모듈을 사용하기 때문입니다.

문제는 requests와 httpx가 시스템 OpenSSL을 ssl 모듈을 통해 사용하기 때문에 TLS의 확장 순서와 집합이 고정되어 있으며 Chrome/Firefox와 일치하지 않는다는 것입니다. 해결책은 실제 브라우저의 TLS 프로필을 정확하게 재현하는 패치된 curl을 사용하는 curl_cffi 라이브러리입니다:

from curl_cffi import requests as cffi_requests

resp = cffi_requests.get(
    "https://tls.peet.ws/api/all",
    impersonate="chrome124",
    proxies={"https": "http://user:pass@proxy_host:port"}
)
print(resp.json()["tls"]["ja4"])
# 해시는 데스크탑의 실제 Chrome 124와 동일할 것입니다.

impersonate 매개변수는 curl_cffi가 ClientHello뿐만 아니라 HTTP/2 헤더의 순서 (프레임 순서)도 재현하도록 합니다. 이는 지문에도 포함됩니다. 유사한 접근 방식을 tls-client 라이브러리 (Go)와 undetected-chromedriver (실제 브라우저를 통해 파싱하는 경우)에서 사용합니다.

헤드리스 브라우저 (Playwright, Puppeteer, Selenium)를 통해 파싱하는 경우, TLS 지문은 Chromium/Firefox 엔진에 의해 형성되며 기본적으로 실제 브라우저와 일치합니다. 그러나 여기서 또 다른 문제가 발생합니다 — JS 수준에서의 자동화 서명 (webdriver 플래그, canvas fingerprint) 때문에 헤드리스 시나리오에는 playwright-stealth와 같은 패치가 추가로 필요합니다.

TLS + HTTP/2 + 헤더: 조합이 중요한 이유

TLS 지문은 감지의 한 층일 뿐입니다. 안티봇 시스템은 여러 레이어를 동시에 확인합니다:

  • TLS ClientHello (JA3/JA4) — 암호 및 확장 집합.
  • HTTP/2 지문 — 의사 헤더의 순서 (:method, :path, :authority), SETTINGS 프레임 설정, 창 크기.
  • HTTP 헤더 — 일반 헤더의 순서 및 집합 (Accept-Language, Sec-Ch-Ua, Sec-Fetch-*).
  • User-Agent — TLS 프로필 버전과 일치해야 합니다: UA가 "Chrome 124"라고 말하고 TLS가 Chrome 110에 해당하면, 이것도 의심스럽습니다.

자주 발생하는 실수는 User-Agent를 최신 Chrome 버전으로 업데이트하면서 curl_cffi 또는 다른 라이브러리의 TLS 프로필을 업데이트하는 것을 잊는 것입니다. 이러한 버전 불일치는 안티봇에게 완전한 마스킹 부재만큼 명확하게 보입니다. impersonate의 버전과 User-Agent의 버전이 일치하는지 확인하고, 브라우저의 새로운 버전이 출시될 때마다 두 매개변수를 동기화하여 업데이트하세요.

또 다른 점은 헤더의 순서입니다. 브라우저는 헤더를 엄격한 순서로 전송하며, 많은 HTTP 라이브러리는 이를 알파벳순 또는 코드에 추가된 순서로 정렬합니다. 헤더 집합이 브라우저와 동일하더라도 잘못된 순서는 DataDome과 같은 고급 안티봇 시스템에 추가 신호가 됩니다.

프록시의 역할: 왜 깨끗한 IP가 도움이 되지 않는가

레지던트 IP는 특정 작업을 해결합니다 — 지리, ASN 및 주소의 평판에 대한 의심을 줄입니다. 데이터 센터 IP는 종종 블랙리스트에 올라 있으며, 자동화된 트래픽이 대량으로 발생하기 때문입니다. 반면 레지던트 IP는 실제 제공업체와 일반 사용자에게 속합니다. Wildberries, Ozon 또는 Avito를 파싱하는 데는 이것이 중요합니다: 깨끗한 IP 없이는 요청이 이 기준으로 차단되며, TLS를 확인하지도 않습니다.

그러나 IP와 TLS 지문은 두 개의 독립적인 보호 층이며, 서로 다른 문제를 해결합니다. IP는 서버에 요청이 "어디서" 왔는지를 말하고, TLS 지문은 "무엇으로" 전송되었는지를 말합니다. 따라서 깨끗한 IP와 올바른 TLS 프로필의 조합은 안정적인 파싱을 위한 최소한의 세트입니다. 높은 요청 빈도와 공격적인 안티봇이 있는 작업에는 레지던트 프록시를 사용하는 것이 좋습니다. 이는 IP 평판에 따른 차단 비율이 낮지만, 반드시 실제 브라우저의 TLS 프로필을 올바르게 재현하는 라이브러리와 함께 사용해야 합니다.

마켓플레이스에서 가격 모니터링을 위해 속도와 요청량이 중요한 경우, 종종 데이터 센터 프록시를 TLS 마스킹과 함께 사용하는 것이 일반적입니다. 이는 레지던트보다 저렴하고, 사이트의 안티봇 시스템이 그리 공격적이지 않다면 충분히 효과적입니다. 그리고 사이트가 모바일 네트워크를 적극적으로 확인하는 작업 (예: API를 통한 모바일 버전의 애플리케이션 파싱)에서는 모바일 프록시를 사용합니다 — 이는 통신사 네트워크의 평판 덕분에 추가적인 신뢰 수준을 제공합니다.

감지되지 않는 파서 설정 체크리스트

프로덕션에서 파서를 실행하기 전에 확인을 하나의 프로세스로 통합하세요:

  1. tls.peet.ws를 통해 스크립트의 JA4 해시를 측정하고 동일 버전의 실제 브라우저와 비교합니다.
  2. TLS 임퍼소네이션을 지원하는 라이브러리를 사용하세요: curl_cffi (Python), tls-client (Go), CycleTLS (Node.js).
  3. TLS 프로필 버전 (impersonate)을 User-Agent의 버전과 동기화합니다.
  4. HTTP 헤더의 순서를 확인하세요 — 실제 브라우저와 일치해야 하며, 알파벳순이 아니어야 합니다.
  5. 작업의 지리에 맞는 깨끗한 레지던트 또는 모바일 IP를 연결합니다.
  6. IP 회전을 TLS 프로필과 별도로 설정하세요 — 서로를 강하게 연결하지 마세요.
  7. Chrome의 새로운 버전이 출시될 때마다 TLS 프로필을 정기적으로 업데이트하세요 — 오래된 서명은 안티봇 데이터베이스에 더 빨리 들어갑니다.
  8. JS 확인이 있는 시나리오 (Cloudflare Challenge)에서는 깨끗한 HTTP 클라이언트 대신 stealth 패치가 적용된 헤드리스 브라우저를 사용하세요.

라이브러리 및 도구 비교

도구 브라우저의 TLS 지문 속도 언제 사용해야 하는가
requests / httpx 아니요, 스크립트를 반환합니다 높음 TLS 감지가 없는 사이트, 내부 API
curl_cffi 예, 정확한 복사본 높음 마켓플레이스, Cloudflare/Akamai 안티봇
tls-client (Go) 예 매우 높음 높은 부하, 대량 파싱
Playwright / Puppeteer 예, 실제 엔진 낮음 JS 렌더링, Cloudflare Challenge, 복잡한 SPA
Scrapy (표준) 아니요 높음 엄격한 안티봇 보호가 없는 사이트

결론

TLS 지문은 많은 파서가 완전히 무시하는 보호 층으로, 완벽한 IP와 User-Agent를 찾는 데 자원을 낭비하지만, TLS 핸드셰이크의 구조가 서버가 헤더를 확인하기 전에 자동화를 드러낸다는 것을 잊습니다. 해결책은 TLS 임퍼소네이션을 지원하는 라이브러리 (curl_cffi, tls-client)를 사용하고, 프로필 버전을 User-Agent와 동기화하며, 대규모로 실행하기 전에 최종 JA4 해시를 확인하는 것입니다.

IP는 여전히 중요한 요소입니다 — 깨끗한 주소 없이는 완벽한 TLS 지문도 네트워크 평판에 따른 차단을 우회하는 데 도움이 되지 않습니다. 마켓플레이스 파싱 및 가격 모니터링을 위해서는 올바른 TLS 설정을 레지던트 프록시와 결합하는 것이 합리적입니다 — 이러한 조합은 두 개의 감지 층을 모두 차단하고 긴 파싱 세션에서 차단 비율을 현저히 줄입니다.