왜 당신의 Competitive Intelligence 스크래퍼가 실패하는가: Browser Fingerprinting에 대한 심층 분석
Source: Dev.to
경쟁사의 가격을 추적하기 위해 스크래퍼를 구축했습니다
고품질 주거용 프록시, 회전하는 User‑Agent, 그리고 논리적인 구현을 사용하고 있습니다. 첫 주에는 데이터가 완벽하게 흐릅니다. 그런데 갑자기 벽이 생깁니다. 403 Forbidden 오류가 뜨거나, 모든 페이지에 CAPTCHA가 나타나거나, 더 심한 경우 ghosting—사이트가 오류를 내보내지 않고 약간 오래되었거나 가짜 데이터를 제공하는 현상이 발생합니다.
프록시를 교체했지만 차단은 계속됩니다. 요청 속도를 늦췄지만 사이트는 여전히 당신을 인식합니다.
현대 웹 스크래핑의 현실은 browser fingerprinting 가 IP 추적을 대체하여 Cloudflare, Akamai, DataDome 같은 안티‑봇 플랫폼의 주요 무기가 되었다는 점입니다. 실시간에 가까운 경쟁 정보 수집을 위해 고주파 “Intel Mode” 스크래퍼를 운영한다면, 차단되는 이유는 IP가 아니라 당신이 어떻게 보이는가 때문입니다.
이 가이드는 표준 스크래핑 기법이 높은 감시 하에서 왜 실패하는지, 그리고 고급 탐지를 우회하기 위해 브라우저의 하드웨어·소프트웨어 신호를 어떻게 맞춰야 하는지 살펴봅니다.
“Intel Mode” 역설
데이터 추출에서는 한 달에 한 번 블로그를 스크랩하는 것과 매시간 전자상거래 거인을 모니터링하는 것 사이에 큰 차이가 있습니다. 후자를 Intel Mode 라고 부릅니다.
요청 빈도와 양을 늘리면 고감시 영역으로 이동합니다. 안티‑봇 시스템은 모든 방문자에게 신뢰 점수(Trust Score) 를 부여합니다. 약간 지저분한 지문을 가진 저볼륨 방문자는 통과될 수 있지만, 시스템이 특정 “유형”의 디바이스에서 10 000개의 요청을 감지하면 깊은 조사 단계로 전환됩니다.
역설은 많은 개발자가 모든 것을 무작위화하려고 시도한다는 점입니다—요청마다 화면 해상도, GPU 문자열, 폰트 목록을 회전시키는 것이죠. 이 “혼돈 전략”은 실제로 신뢰 점수를 낮춥니다. 실제 인간은 5분마다 하드웨어를 바꾸지 않습니다. 정교한 방어 시스템에게 “독특한” 지문은 차단된 지문만큼이나 의심스러운 것입니다.
목표: 수백만 명의 실제 사용자와 같은, 표준적이고 지루한 “버킷”이 되는 것—일회성 이상 현상이 아니라.
첫 번째 누수: 헤더 무결성 및 TLS
HTML 한 줄도 파싱하기 전에, 스크레이퍼는 이미 네트워크 계층에서 자신을 드러냈을 가능성이 높습니다.
헤더 불일치와 클라이언트 힌트
대부분의 개발자는 User-Agent(UA) 문자열을 설정한다는 것을 알고 있습니다. 그러나 최신 브라우저는 클라이언트 힌트(Client Hints, CH), 즉 보다 세밀한 정보를 제공하는 Sec-CH-UA 헤더 집합도 함께 전송합니다.
Chrome 124 UA를 보내면서 해당 Sec-CH-UA-Platform 헤더를 누락하거나 버전이 일치하지 않으면, 서버는 여러분이 Python requests와 같은 수동 라이브러리를 사용하고 있음을 알게 됩니다.
TLS 지문 (JA3/JA4)
코드가 HTTPS 연결을 시작하면 TLS 핸드쉐이크가 수행됩니다. 이 과정에서 클라이언트는 지원하는 암호화 스위트, 확장 기능, 타원 곡선 목록을 전송합니다.
Python의 urllib이나 Node.js의 http 모듈은 실제 Google Chrome 브라우저와는 크게 다른 고유의 TLS 서명을 가지고 있습니다. 안티‑봇 서비스는 JA3 지문을 이용해 이러한 서명을 식별합니다. 헤더에서는 Chrome이라고 주장하지만 TLS 핸드쉐이크가 Python처럼 보이면 즉시 플래그가 지정됩니다.
| 기능 | 표준 라이브러리 (Requests) | 현대 브라우저 (Chrome) |
|---|---|---|
| 헤더 순서 | 대부분 알파벳 순서이거나 고정됨 | 특정 순서, 알파벳 순서 아님 |
| TLS 암호 | 제한적이며 오래된 스위트 | 최신, GREASE 암호 |
| 클라이언트 힌트 | 보통 없음 | 존재하며 UA와 일치함 |
| HTTP 버전 | 대부분 HTTP/1.1 기본 | HTTP/2 또는 HTTP/3 기본 |
두 번째 누출: Device‑Type Coherence
네트워크 계층을 통과하면, 안티‑봇이 JavaScript를 실행해 Device Coherence를 확인합니다 — 소프트웨어가 주장하는 내용과 실제 하드웨어가 일치하는지 여부를 검사합니다.
흔히 저지르는 실수는 “Frankenstein Fingerprint”를 만드는 것입니다. 예를 들어, 개발자가 UA를 “Windows 10”으로 설정했지만 실제로는 Linux 서버에서 스크래퍼를 실행하는 경우가 있습니다.
// A simple anti‑bot check for coherence
const isBot = () => {
const userAgent = navigator.userAgent;
const platform = navigator.platform;
// If UA says Windows but platform says Linux, it's a bot
if (userAgent.includes("Win") && !platform.includes("Win")) {
return true;
}
// Check for the 'webdriver' property used by automated tools
if (navigator.webdriver) {
return true;
}
return false;
};
Font Enumeration
서버‑사이드 봇을 감지하는 가장 효과적인 방법 중 하나는 사용 가능한 폰트를 확인하는 것입니다. Windows 머신은 매우 특정한 폰트 집합(예: Arial, Calibri)이 설치되어 있습니다. 헤드리스 Linux 서버는 이러한 폰트가 없거나 다른 버전을 가지고 있는 경우가 많습니다. 스크립트가 Windows 사용자를 가장하고 있지만 Windows 전용 폰트를 렌더링할 수 없다면, 신뢰 점수는 0으로 떨어집니다.
The Third Leak: Canvas and Hardware Realism
가장 진보된 형태의 지문 수집은 Canvas Fingerprinting입니다. 웹사이트는 브라우저에 숨겨진 2D 또는 3D 이미지를 그리도록 요청합니다. GPU 드라이버, OS 서브‑버전, 하드웨어의 미세한 차이 때문에 생성된 픽셀 데이터는 해당 장치에 고유합니다.
The Trap of Randomization
많은 “스텔스” 플러그인이 Canvas 출력에 무작위 노이즈를 추가하여 이를 우회하려 합니다. 이렇게 하면 지문이 고유해지지만 불가능해지기도 합니다. 안티‑봇 시스템은 합법적인 하드웨어 서명 데이터베이스를 유지합니다. Canvas 출력이 알려진 실제 GPU/드라이버 조합과 일치하지 않으면 비정상적인 방문자로 표시됩니다.
WebGL and GPU Signatures
마찬가지로 WebGL의 unmaskedRenderer와 unmaskedVendor 속성은 사용자의 실제 신원을 드러낼 수 있습니다. 이 값이 Google SwiftShader, Mesa Offscreen 또는 기타 소프트웨어 렌더러를 반환하면, 사이트는 프록시나 UA와 관계없이 사용자가 서버에서 헤드리스 브라우저를 실행하고 있음을 알게 됩니다.
구현: Configuri… (여기서 가이드를 계속하세요)
(가이드의 나머지는 동일한 깔끔한 마크다운 구조를 유지하며, 필요에 따라 헤딩, 코드 블록, 표, 그리고 글머리표를 사용하세요.)
Source:
스텔스 모드로 전환하기
이러한 누수를 해결하려면 단순 HTTP 클라이언트에서 벗어나 특정 설정을 갖춘 브라우저 오케스트레이션으로 이동해야 합니다.
1. 네트워크 계층 정렬
Python requests나 aiohttp를 사용할 경우 TLS 지문을 위조할 수 있는 라이브러리, 예를 들어 curl_cffi 혹은 맞춤 SSL 컨텍스트를 적용한 httpx 를 사용하십시오. 하지만 고빈도 스크래핑의 경우 브라우저 기반 접근이 일반적으로 더 안전합니다.
2. 일관된 프로파일을 갖춘 Playwright
Playwright를 사용할 때는 모든 속성을 무작위로 바꾸지 말고, 내부적으로 일관된 프로파일을 생성하십시오.
from playwright.sync_api import sync_playwright
def run_stealth_scraper():
with sync_playwright() as p:
# 일관된 뷰포트와 사용자 에이전트로 실행
# 실제 해상도(1920x1080)를 사용
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
viewport={'width': 1920, 'height': 1080},
device_scale_factor=1,
is_mobile=False,
has_touch=False,
locale="en-US",
timezone_id="America/New_York"
)
page = context.new_page()
# 최신 Playwright가 일부를 처리하지만,
# 특수 플러그인이 webdriver 플래그를 숨기는 데 더 효과적일 때가 많습니다.
page.goto("https://bot.sannysoft.com/")
page.screenshot(path="check.png")
browser.close()
run_stealth_scraper()
3. 지문 관리 외주화
TLS, Canvas, 폰트 등 완벽한 정렬을 관리하는 일은 전담 업무에 해당합니다. 대규모 경쟁 정보 수집의 경우 ScrapeOps 와 같은 전용 스크래핑 API를 이용하는 것이 비용 효율적일 수 있습니다. 이러한 도구는 실제 브라우저 인스턴스를 사용하고 통계적으로 정상적인 지문을 회전시켜 하드웨어 현실성을 대신 처리해 줍니다.
import requests
API_KEY = 'YOUR_SCRAPEOPS_KEY'
TARGET_URL = 'https://competitor.com/prices'
# 브라우저 지문을 관리하는 프록시로 요청 전송
response = requests.get(
url='https://proxy.scrapeops.io/v1/',
params={
'api_key': API_KEY,
'url': TARGET_URL,
'render_js': 'true', # JS 기반 지문 처리
'wait_for_selector': '.price-table'
}
)
print(response.text)
마무리
IP‑only 차단 시대는 끝났습니다. 경쟁 정보 수집용 스크래퍼가 실패한다면, 브라우저 지문이 **“봇!”**이라고 외치고 프록시가 **“사용자.”**라고 속삭이고 있기 때문일 가능성이 높습니다.
2024년에 탄탄한 스크래퍼를 만들려면 다음 기본 원칙을 기억하세요:
- 일관성이 왕이다 – User‑Agent, Client Hints, TLS 서명, 하드웨어 신호가 모두 같은 이야기를 해야 합니다.
- 과도한 무작위화 피하기 – 독특해지는 것이 목표가 아니라 눈에 띄지 않는 것이 목표입니다.
- 지문 확인하기 – CreepJS 와 같은 도구를 사용해 서버가 보는 스크래퍼의 모습을 정확히 확인하세요.
- 갭 메우기 – WebGL, Canvas, TLS 관리에 드는 엔지니어링 비용이 너무 크다면, 지문 레이어를 대신 처리해 주는 특수 스크래핑 브라우저나 API를 활용하세요.
반봇 시스템이 AI 기반 행동 분석으로 이동함에 따라, 다음 전쟁터는 마우스를 어떻게 움직이고 버튼을 클릭하느냐가 될 것입니다. 하지만 지문을 고치지 않으면, 문 앞까지도 못 들어갈 겁니다.