
Playwright와 Node.js로 웹 스크래핑 시 가장 큰 난관인 봇 감지 회피·우회 방법을 집중적으로 다룬 실전 가이드입니다. navigator.webdriver, fingerprinting, CAPTCHA 탐지 원리를 이해하고 Medium·Advanced 전략으로 실제 사이트 차단을 우회하는 방법을 단계별로 정리했습니다.
참고: 이 글은 파이썬과 n8n을 기준으로 작성된 N8N 웹 스크래핑 시 Bot 탐지 기술 및 우회 의 핵심 개념과 우회 전략을 바탕으로, Node.js + Playwright 환경에 맞게 포팅하여 정리한 글입니다.
왜 웹 스크래핑이 차단될까?
Node.js로 Playwright를 사용해 웹사이트에 접속했는데 갑자기 접근이 차단되거나 CAPTCHA가 뜨는 경험, 다들 한 번쯤 있으실 겁니다. 😅
웹사이트 입장에서는 이런 고민이 있습니다:
- 서버 부하: 자동화된 봇이 초당 수백 건의 요청을 보내면 서버가 다운될 수 있습니다
- 데이터 도용: 경쟁사가 가격 정보나 상품 데이터를 무단으로 수집할 수 있습니다
- 보안 위협: 악의적인 봇이 DDoS 공격이나 계정 탈취를 시도할 수 있습니다
그래서 많은 사이트들이 봇 탐지 시스템을 도입했습니다. 문제는 여러분의 정당한 웹 스크래핑까지 막힌다는 것입니다.
합법적인 웹 스크래핑이란?
시작하기 전에 꼭 기억해야 할 원칙들이 있습니다:
✅ 허용되는 경우:
- 공개된 정보 수집 (뉴스 기사, 제품 정보 등)
- 웹사이트 이용약관에서 허용하는 범위 내
- robots.txt를 준수하는 스크래핑
- 적절한 요청 간격 (Rate Limiting 준수)
❌ 금지되는 경우:
- 로그인이 필요한 개인정보 수집
- 저작권이 있는 콘텐츠 무단 복제
- 서버에 과도한 부담을 주는 공격적 크롤링
- 이용약관 위반
웹사이트는 어떻게 봇을 감지할까?
웹사이트가 봇을 탐지하는 다양한 기법을 이해하면, 왜 우회가 필요한지 명확해집니다. 초보자분들도 쉽게 이해할 수 있도록 하나씩 설명드리겠습니다!
1️⃣ navigator.webdriver 체크 (난이도: ⭐)
가장 기본적인 탐지 방법입니다. 브라우저가 자동화 도구로 제어되고 있는지 확인하는 속성입니다.
// 일반 사용자의 Chrome 브라우저
console.log(navigator.webdriver); // undefined
// Playwright로 실행한 브라우저 (기본 설정)
console.log(navigator.webdriver); // true ⚠️왜 문제일까요?
웹사이트 입장에서는 navigator.webdriver === true인 브라우저를 발견하면, “아, 이건 자동화 도구네!”라고 즉시 알 수 있습니다.
2️⃣ Chrome 객체 누락 (난이도: ⭐)
실제 Chrome 브라우저에는 window.chrome이라는 특별한 객체가 있습니다.
// 진짜 Chrome
console.log(window.chrome);
// { runtime: {...}, loadTimes: function, ... }
// Playwright의 Chromium
console.log(window.chrome);
// undefined ⚠️차이가 생기는 이유
Playwright는 순수 Chromium을 사용하는데, window.chrome은 Google이 추가한 기능이라서 포함되어 있지 않습니다.
3️⃣ 플러그인 개수 체크 (난이도: ⭐)
일반 브라우저는 PDF 뷰어 같은 기본 플러그인이 설치되어 있습니다.
// 일반 브라우저
console.log(navigator.plugins.length); // 3~5개
// Playwright (기본)
console.log(navigator.plugins.length); // 0개 ⚠️플러그인이 하나도 없다? 매우 의심스럽습니다!
4️⃣ 언어 설정 부족 (난이도: ⭐)
실제 사용자는 보통 여러 언어를 선호 언어로 설정합니다.
// 한국 사용자의 실제 브라우저
console.log(navigator.languages);
// ['ko-KR', 'ko', 'en-US', 'en']
// Playwright (기본)
console.log(navigator.languages);
// ['en-US'] ⚠️5️⃣ 고급 탐지 기법 (난이도: ⭐⭐⭐)
더 똑똑한 사이트들은 이런 것들도 체크합니다:
- Canvas Fingerprinting: 브라우저가 그림을 그리는 방식으로 기기 식별
- WebGL Fingerprinting: GPU 정보로 고유 ID 생성
- 행동 패턴 분석: 마우스 움직임, 타이핑 속도 등
- IP 기반 Rate Limiting: 같은 IP에서 너무 많은 요청
Playwright 기본 설정의 문제점
자, 이제 실제로 Playwright를 사용할 때 어떤 문제가 발생하는지 살펴보겠습니다.
기본 코드 (❌ 봇으로 탐지됨)
import { chromium } from 'playwright';
async function scrapeWebsite() {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
// 이 시점에서 봇으로 탐지될 수 있습니다!
const title = await page.title();
console.log(title);
await browser.close();
}이 코드의 문제점:
navigator.webdriver === truewindow.chrome객체 없음navigator.plugins.length === 0- 언어 설정이 단순함
- User-Agent가 Headless Chrome으로 표시
웹사이트 입장에서는 빨간불이 5개나 켜진 셈입니다! 🚨
단계별 봇 탐지 우회 전략
좋은 소식은, 이 문제들을 단계적으로 해결할 수 있다는 것입니다! 저는 4단계 전략을 추천합니다.
📊 전략 비교표
| 레벨 | 우회 성공률 | 성능 | 추천 대상 |
|---|---|---|---|
| Level 0: 기본 | 0% | 최고 | 봇 탐지 없는 내부 시스템 |
| Level 1: Basic | ~30% | 빠름 | 간단한 User-Agent 체크만 하는 사이트 |
| Level 2: Medium ⭐ | ~70% | 보통 | 대부분의 일반 웹사이트 (추천!) |
| Level 3: Advanced | ~85% | 느림 | Cloudflare 등 고급 방어 시스템 |
대부분의 경우 Medium 레벨이면 충분합니다! 처음부터 Advanced를 쓰면 성능만 떨어지고 오히려 의심받을 수 있습니다.
실전 코드 예제
자, 이제 실제로 어떻게 구현하는지 보여드리겠습니다!
Level 1: Basic - User-Agent 변경하기
가장 기본적인 방법은 User-Agent를 진짜 Chrome처럼 바꾸는 것입니다.
import { chromium } from 'playwright';
async function basicStealth() {
const browser = await chromium.launch({
headless: true,
args: [
// 자동화 탐지 비활성화!
'--disable-blink-features=AutomationControlled',
]
});
const context = await browser.newContext({
// 최신 Chrome의 User-Agent
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
// 일반적인 화면 해상도
viewport: { width: 1920, height: 1080 },
// 한국 설정
locale: 'ko-KR',
timezoneId: 'Asia/Seoul',
// 실제 브라우저처럼 헤더 설정
extraHTTPHeaders: {
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
});
const page = await context.newPage();
await page.goto('https://example.com');
await browser.close();
}✅ 개선 효과:
- User-Agent가 진짜 Chrome처럼 보입니다
- 화면 크기와 언어 설정이 자연스럽습니다
- 간단한 봇 탐지는 통과 가능합니다!
Level 2: Medium - JavaScript 주입으로 속성 숨기기 ⭐
이제 진짜 실력을 발휘할 시간입니다! 브라우저 속성을 직접 수정해보겠습니다.
async function applyStealthScripts(page) {
// 페이지 로드 전에 실행되는 스크립트
await page.addInitScript(() => {
// 🎭 1단계: navigator.webdriver 숨기기
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined // true → undefined로 변경!
});
// 🎨 2단계: Chrome 객체 만들기
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {}
};
// 🔌 3단계: 플러그인 추가
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5] // 5개의 더미 플러그인
});
// 🌍 4단계: 언어 설정 확장
Object.defineProperty(navigator, 'languages', {
get: () => ['ko-KR', 'ko', 'en-US', 'en']
});
// ⚡ 5단계: Permissions API 수정
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
// 📡 6단계: 네트워크 정보 추가
Object.defineProperty(navigator, 'connection', {
get: () => ({
effectiveType: '4g',
rtt: 100,
downlink: 10,
saveData: false
})
});
});
}
// 실제 사용 예제
async function scrapeWithMediumStealth(url) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
// 여기가 핵심! 스크립트 주입
await applyStealthScripts(page);
// 이제 안전하게 접속
await page.goto(url);
const title = await page.title();
console.log('페이지 제목:', title);
await browser.close();
}🎯 핵심 포인트:
Object.defineProperty(): JavaScript에서 객체의 속성을 재정의하는 강력한 도구입니다page.addInitScript(): 페이지가 로드되기 전에 실행됩니다 - 봇 탐지 스크립트보다 먼저 실행됩니다!- 속성 위조: 실제 브라우저처럼 보이도록 여러 속성을 추가합니다
Level 3: Advanced - 고급 Fingerprinting 방지
정말 까다로운 사이트를 만났다면? 이 레벨까지 올라가야 합니다.
async function applyAdvancedStealth(page) {
// Medium 레벨 먼저 적용
await applyStealthScripts(page);
// 추가 고급 기법
await page.addInitScript(() => {
// 🎨 Canvas Fingerprinting 방지
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
// 작은 canvas는 fingerprinting 시도로 간주
if (type === 'image/png' && this.width === 16 && this.height === 16) {
return originalToDataURL.apply(this, arguments);
}
return originalToDataURL.apply(this, arguments);
};
// 🎮 WebGL 정보 수정
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// GPU 정보 위조
if (parameter === 37445) return 'Intel Inc.';
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
return getParameter.apply(this, arguments);
};
// 🔋 Battery API 제거 (Headless 탐지에 사용됨)
if ('getBattery' in navigator) {
navigator.getBattery = undefined;
}
// 🖥️ 하드웨어 정보 정규화
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8 // 일반적인 CPU 코어 수
});
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8 // 8GB RAM
});
});
}⚠️ 주의사항:
- Advanced 레벨은 성능이 느립니다
- 모든 사이트에 사용할 필요는 없습니다
- Medium으로 안 되는 경우에만 사용하세요!
테스트 및 디버깅 방법
코드를 작성했으면 테스트해봐야겠죠? 여기 유용한 사이트들을 소개합니다!
🧪 테스트 사이트 TOP 4
| 사이트 | URL | 특징 |
|---|---|---|
| Sannysoft | bot.sannysoft.com | 가장 포괄적인 봇 탐지 테스트 |
| Are You Headless | arh.antoinevastel.com | Headless 모드 특화 테스트 |
| BrowserScan | browserscan.net | 브라우저 Fingerprint 분석 |
| Pixelscan | pixelscan.net | Canvas/WebGL 테스트 |
간단한 테스트 코드
import { chromium } from 'playwright';
async function testMyBrowser() {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// 여기에 여러분의 stealth 스크립트 적용
// await applyStealthScripts(page);
// 빈 페이지에서 속성 확인
await page.goto('about:blank');
const properties = await page.evaluate(() => {
return {
webdriver: navigator.webdriver,
chrome: !!window.chrome,
plugins: navigator.plugins.length,
languages: navigator.languages,
userAgent: navigator.userAgent
};
});
console.log('🔍 브라우저 속성 검사 결과:');
console.log('- webdriver:', properties.webdriver);
console.log('- Chrome 객체:', properties.chrome ? '✅ 있음' : '❌ 없음');
console.log('- 플러그인 개수:', properties.plugins);
console.log('- 지원 언어:', properties.languages);
await browser.close();
}
testMyBrowser();결과 해석:
- ✅
webdriver: undefined- 좋습니다! - ✅
Chrome 객체: 있음- 완벽합니다! - ✅
플러그인 개수: 5- 자연스럽습니다 - ❌
webdriver: true- 개선 필요합니다!
실전 활용 패턴
패턴 1: 뉴스 기사 스크래핑
import { chromium } from 'playwright';
async function scrapeNews() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'ko-KR'
});
const page = await context.newPage();
await applyStealthScripts(page);
await page.goto('https://news-example.com');
// 기사 제목 수집
const articles = await page.$$eval('article h2', elements =>
elements.map(el => el.textContent.trim())
);
console.log('오늘의 뉴스:', articles);
await browser.close();
return articles;
}패턴 2: 가격 비교 (Rate Limiting 준수)
async function comparePrices(urls) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 }
});
const prices = [];
for (const url of urls) {
const page = await context.newPage();
await applyStealthScripts(page);
await page.goto(url);
const price = await page.$eval('.price', el => el.textContent);
prices.push({ url, price });
await page.close();
// ⏰ 중요: 요청 간격 두기 (2-3초)
await page.waitForTimeout(2000);
}
await browser.close();
return prices;
}자주 묻는 질문 (FAQ)
Q1: 이거 불법 아닌가요?
A: 합법적인 범위 내에서 사용하면 괜찮습니다!
✅ 합법적 사용:
- 공개된 정보 수집
- 개인 연구/학습 목적
- 웹사이트 이용약관 준수
❌ 불법적 사용:
- 개인정보 무단 수집
- 저작권 침해
- 서버 과부하 유발 (DDoS)
- 이용약관 위반
Q2: Medium과 Advanced 중 뭘 써야 하나요?
A: Medium으로 시작하세요!
1단계: Medium 레벨로 테스트
↓
차단되나요?
↓
No → Medium 계속 사용 (70%의 경우)
Yes → Advanced로 업그레이드 (30%의 경우)처음부터 Advanced를 쓰면:
- 성능 저하 (2배 느림)
- 오히려 의심스러울 수 있습니다
- 불필요한 리소스 낭비
Q3: playwright-extra는 뭔가요?
A: 더 강력한 플러그인 시스템입니다!
npm install playwright-extra puppeteer-extra-plugin-stealthimport { chromium } from 'playwright-extra';
import stealth from 'puppeteer-extra-plugin-stealth';
// 플러그인만 추가하면 끝!
chromium.use(stealth());
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// 자동으로 stealth 적용됨장점:
- 코드가 간단합니다
- 지속적으로 업데이트됩니다
- Cloudflare 같은 고급 방어도 우회 가능합니다
단점:
- 외부 라이브러리 의존성
- 커스터마이징이 어렵습니다
Q4: 메모리 사용량이 너무 높아요!
A: 이렇게 최적화하세요:
// ❌ 나쁜 예: 매번 새 브라우저
for (const url of urls) {
const browser = await chromium.launch();
// ... 작업
await browser.close();
}
// ✅ 좋은 예: 브라우저 재사용
const browser = await chromium.launch();
for (const url of urls) {
const page = await browser.newPage();
// ... 작업
await page.close(); // 페이지만 닫기
}
await browser.close(); // 마지막에 브라우저 닫기추가 팁:
// 이미지 로딩 비활성화
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// 주기적으로 브라우저 재시작
if (count % 100 === 0) {
await browser.close();
browser = await chromium.launch();
}Q5: CAPTCHA가 나타나면 어떡하죠?
A: CAPTCHA는 “자동화 차단의 최종 보스”입니다.
단계별 대응:
- Level 1: Stealth 레벨 올리기
- Level 2: 요청 간격 늘리기 (3-5초)
- Level 3: User-Agent 로테이션
- Level 4: 프록시 사용
- 최종: 공식 API 문의
솔직한 조언:
reCAPTCHA v3 같은 고급 시스템은 우회가 매우 어렵습니다. 웹사이트에서 공식 API를 제공하는지 먼저 확인하세요!
Q6: 실무에서는 어떻게 쓰나요?
A: 실무 패턴 예시입니다:
// 프로덕션 레벨 설정
const RETRY_COUNT = 3;
const REQUEST_DELAY = 2000;
async function productionScraper(url) {
let attempt = 0;
while (attempt < RETRY_COUNT) {
try {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: getRandomUserAgent(), // UA 로테이션
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
await applyStealthScripts(page);
// 타임아웃 설정
await page.goto(url, {
waitUntil: 'networkidle',
timeout: 30000
});
const data = await page.$eval('...', el => el.textContent);
await browser.close();
return data;
} catch (error) {
console.error(`시도 ${attempt + 1} 실패:`, error.message);
attempt++;
if (attempt < RETRY_COUNT) {
// 지수 백오프
await new Promise(r => setTimeout(r, REQUEST_DELAY * attempt));
}
}
}
throw new Error('최대 재시도 횟수 초과');
}마무리하며
웹 스크래핑은 강력한 도구지만, 책임감 있게 사용해야 합니다.
✨ 핵심 정리
- 시작은 Medium 레벨로 - 70% 성공률에 좋은 성능
- 테스트는 필수 - Sannysoft 등에서 검증
- Rate Limiting 준수 - 요청 간격 2-3초
- 에러 처리 철저히 - Retry 로직 구현
- 이용약관 확인 - 법적 문제 예방
🎓 다음 단계
더 깊이 공부하고 싶다면:
참고 자료
이 글은 다음 리소스를 참고하여 작성되었습니다: