봇 감지 테스트 사이트들
- https://bot-detector.rebrowser.net/
- https://bot.sannysoft.com/
- https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html
- https://arh.antoinevastel.com/bots/areyouheadless
LLM 추천 옵션들
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-accelerated-2d-canvas",
"--no-first-run",
"--no-zygote",
"--disable-gpu",
"--disable-web-security",
"--disable-features=VizDisplayCompositor",
"--disable-blink-features=AutomationControlled",
"--exclude-switches=enable-automation",
"--disable-plugins-discovery",
"--disable-default-apps",
"--disable-infobars",
"--disable-extensions",
"--disable-component-extensions-with-background-pages",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-field-trial-config",
"--disable-back-forward-cache",
"--disable-ipc-flooding-protection",
"--password-store=basic",
"--use-mock-keychain",
"--no-default-browser-check",
"--no-pings",
"--disable-sync",
"--disable-translate",
"--disable-background-networking",
"--disable-client-side-phishing-detection",
"--disable-component-update",
"--disable-domain-reliability",
"--disable-features=TranslateUI",
"--disable-hang-monitor",
"--disable-prompt-on-repost",
"--metrics-recording-only",
"--safebrowsing-disable-auto-update",
"--enable-automation=false",
],
뷰포트 설정
await this.page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 1,
hasTouch: false,
isLandscape: true,
isMobile: false,
});
기타 한국 특화 설정
// 한국 사용자 User-Agent 설정
await this.page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
);
// 고급 봇 감지 우회 스크립트
await this.page.evaluateOnNewDocument(() => {
// webdriver 속성 완전 제거
Object.defineProperty(navigator, "webdriver", {
get: () => undefined,
configurable: true,
});
// plugins 배열을 실제 브라우저처럼 설정
Object.defineProperty(navigator, "plugins", {
get: () => ({
0: {
0: {
description: "Portable Document Format",
enabledPlugin: {},
suffixes: "pdf",
type: "application/pdf",
},
description: "Portable Document Format",
filename: "internal-pdf-viewer",
length: 1,
name: "Chrome PDF Plugin",
},
length: 1,
}),
configurable: true,
});
// languages 설정 (한국어 우선)
Object.defineProperty(navigator, "languages", {
get: () => ["ko-KR", "ko", "en-US", "en"],
configurable: true,
});
// language 설정
Object.defineProperty(navigator, "language", {
get: () => "ko-KR",
configurable: true,
});
// platform 설정
Object.defineProperty(navigator, "platform", {
get: () => "Win32",
configurable: true,
});
// hardwareConcurrency 설정
Object.defineProperty(navigator, "hardwareConcurrency", {
get: () => 8,
configurable: true,
});
// deviceMemory 설정
Object.defineProperty(navigator, "deviceMemory", {
get: () => 8,
configurable: true,
});
// permissions API 우회
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) =>
parameters.name === "notifications"
? Promise.resolve({ state: "granted" })
: originalQuery(parameters);
// Chrome runtime 제거
if (window.chrome && window.chrome.runtime) {
delete window.chrome.runtime.onConnect;
delete window.chrome.runtime.onMessage;
}
// WebGL 정보 설정
const getParameter = WebGLRenderingContext.getParameter;
WebGLRenderingContext.prototype.getParameter = function (parameter) {
if (parameter === 37445) {
return "Intel Inc.";
}
if (parameter === 37446) {
return "Intel(R) Iris(TM) Graphics 6100";
}
return getParameter(parameter);
};
// 마우스 이벤트 시뮬레이션을 위한 설정
let mouseX = 0;
let mouseY = 0;
document.addEventListener("mousemove", (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
// 스크롤 이벤트 시뮬레이션
let scrollY = 0;
window.addEventListener("scroll", () => {
scrollY = window.scrollY;
});
});
// 한국 사용자 헤더 설정
await this.page.setExtraHTTPHeaders({
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0",
"sec-ch-ua":
'"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
});
// 한국 시간대 설정
await this.page.emulateTimezone("Asia/Seoul");
사람 같이 행동하게?
async simulateHumanBehavior() {
// 1. 자연스러운 마우스 움직임 (베지어 곡선 사용)
await this.simulateNaturalMouseMovement();
// 2. 페이지 읽기 시뮬레이션 (위에서 아래로 천천히 스크롤)
await this.simulateReading();
// 3. 랜덤한 일시정지 (사람이 생각하는 시간)
await this.simulateThinking();
// 4. 가끔 뒤로 스크롤 (다시 읽기)
if (Math.random() < 0.3) {
await this.simulateReReading();
}
}
async simulateNaturalMouseMovement() {
const viewport = await this.page.viewport();
const startX = Math.random() * viewport.width;
const startY = Math.random() * viewport.height;
// 여러 단계로 나누어 자연스럽게 마우스 이동
const steps = 3 + Math.floor(Math.random() * 5); // 3-7단계
for (let i = 0; i < steps; i++) {
const targetX = Math.random() * viewport.width;
const targetY = Math.random() * viewport.height;
// 베지어 곡선처럼 중간 지점들을 거쳐 이동
const currentX = startX + (targetX - startX) * (i / steps);
const currentY = startY + (targetY - startY) * (i / steps);
// 약간의 랜덤 노이즈 추가 (손떨림 효과)
const noiseX = (Math.random() - 0.5) * 20;
const noiseY = (Math.random() - 0.5) * 20;
await this.page.mouse.move(currentX + noiseX, currentY + noiseY);
// 각 이동 사이에 자연스러운 지연
await new Promise((resolve) =>
setTimeout(resolve, 50 + Math.random() * 100)
);
}
}
async simulateReading() {
// 페이지 높이 확인
const pageHeight = await this.page.evaluate(
() => document.body.scrollHeight
);
const viewportHeight = await this.page.evaluate(() => window.innerHeight);
if (pageHeight <= viewportHeight) return; // 스크롤할 필요 없음
// 읽기 속도 시뮬레이션 (한국인 평균 읽기 속도 고려)
const readingSpeed = 200 + Math.random() * 100; // 200-300 글자/분
const scrollSteps = 5 + Math.floor(Math.random() * 8); // 5-12단계로 나누어 스크롤
for (let i = 0; i < scrollSteps; i++) {
// 각 스크롤 단계마다 다른 속도로 스크롤
const scrollAmount =
(pageHeight / scrollSteps) * (0.8 + Math.random() * 0.4);
await this.page.evaluate((amount) => {
window.scrollBy({
top: amount,
behavior: "smooth",
});
}, scrollAmount);
// 읽는 시간 시뮬레이션 (내용을 읽는 시간)
const readingTime = 1000 + Math.random() * 2000; // 1-3초
await new Promise((resolve) => setTimeout(resolve, readingTime));
// 가끔 마우스도 함께 움직임
if (Math.random() < 0.4) {
const viewport = await this.page.viewport();
const mouseX = Math.random() * viewport.width;
const mouseY = Math.random() * viewport.height;
await this.page.mouse.move(mouseX, mouseY);
}
}
}
async simulateThinking() {
// 사람이 생각하거나 망설이는 시간
const thinkingTime = 500 + Math.random() * 2000; // 0.5-2.5초
await new Promise((resolve) => setTimeout(resolve, thinkingTime));
// 생각하는 동안 가끔 마우스를 작게 움직임
if (Math.random() < 0.6) {
const viewport = await this.page.viewport();
const smallMoveX = (Math.random() - 0.5) * 50;
const smallMoveY = (Math.random() - 0.5) * 50;
const currentX = Math.random() * viewport.width;
const currentY = Math.random() * viewport.height;
await this.page.mouse.move(
Math.max(0, Math.min(viewport.width, currentX + smallMoveX)),
Math.max(0, Math.min(viewport.height, currentY + smallMoveY))
);
}
}
async simulateReReading() {
// 가끔 위로 다시 스크롤해서 다시 읽기
const currentScroll = await this.page.evaluate(() => window.pageYOffset);
if (currentScroll > 200) {
const backScrollAmount = 100 + Math.random() * 300;
await this.page.evaluate((amount) => {
window.scrollBy({
top: -amount,
behavior: "smooth",
});
}, backScrollAmount);
// 다시 읽는 시간
await new Promise((resolve) =>
setTimeout(resolve, 800 + Math.random() * 1200)
);
// 다시 아래로 스크롤
await this.page.evaluate((amount) => {
window.scrollBy({
top: amount * 0.7, // 조금 덜 내려감
behavior: "smooth",
});
}, backScrollAmount);
}
}
으악