셀레니움 네이버 캡차 2026년 해결법 — 삽질 7단계 후 찾은 $cdc_ 변수
1년 넘게 아무 문제 없이 잘 돌아가던 셀레니움 네이버 자동 로그인이 어느 날 갑자기 캡차를 쏟아내기 시작했다. 처음에는 "네이버 서버 문제겠지" 싶어서 다음날 다시 돌렸는데 여전히 캡차. 그다음 날도. 그 다음날도. 수동으로 같은 컴퓨터, 같은 IP에서 로그인하면 캡차가 전혀 안 뜨는데 셀레니움으로 돌리면 50% 확률로 캡차가 등장했다. 7번의 삽질 끝에 chromedriver가 DOM에 심어놓는 $cdc_ 변수가 원인이라는 걸 알아냈다.
~50%
셀레니움 캡차 발생률
0%
수동 로그인 캡차 발생률
7단계
삽질 횟수
$cdc_
진짜 원인
1갑자기 캡차가 뜨기 시작했다
2025년 초에 만든 자동화 스크립트였다. 네이버 로그인 후 특정 페이지에서 데이터를 수집하는 용도였는데, 1년 내내 아무 탈 없이 돌아가다가 2026년 3월 어느 날부터 갑자기 매 실행마다 50% 확률로 캡차가 떴다.
이상한 건 패턴이 불규칙했다는 거다. 연속 2번 성공하다가 갑자기 캡차, 다시 3번 성공하다가 캡차. IP 차단도 아니고 계정 잠금도 아니었다. 수동 로그인은 몇 십 번을 해도 캡차가 단 한 번도 안 떴다. 그 시점에 코드를 딱히 건드린 것도 없었다.
가장 먼저 의심한 것들
- 네이버 서버 불안정 → 며칠 지나도 동일
- IP 차단 → 다른 IP에서도 동일
- 계정 이상 → 수동 로그인은 문제없음
- Chrome 업데이트 → 이게 핵심이었는데 처음엔 몰랐다
21~3단계: UA, stealth 스크립트, 영구 프로필
1단계: User-Agent 하드코딩 수정 — 오히려 역효과
처음 시도는 User-Agent를 최신 Chrome 버전으로 바꾸는 거였다. 기존 코드에 Chrome/114.0.0.0이 하드코딩되어 있었는데, 당시 실제 Chrome은 146이었다. "버전 불일치가 원인이겠다" 싶어서 146으로 바꿨더니... 효과 없음. 오히려 캡차 발생률이 약간 올랐다. (이유는 나중에 알았다. 하드코딩 자체가 문제였던 것이다.)
# 기존 코드 (Chrome 114 하드코딩 — 문제의 시작)
options.add_argument("user-agent=Mozilla/5.0 ... Chrome/114.0.0.0 Safari/537.36")
# 1단계 시도 (Chrome 146으로 변경 — 여전히 하드코딩이라 문제)
options.add_argument("user-agent=Mozilla/5.0 ... Chrome/146.0.0.0 Safari/537.36")2단계: stealth CDP 스크립트 — 효과 미미
셀레니움 봇 탐지 회피로 널리 알려진 방법이다.navigator.webdriver를 false로 바꾸고, 자동화 도구 흔적을 CDP(Chrome DevTools Protocol)로 제거하는 스크립트를 주입했다. 캡차가 약간 줄긴 했는데 완전히 없어지지 않았다. 이미 많이 알려진 방법이라 네이버가 대응했을 것이다.
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
window.navigator.chrome = { runtime: {} };
"""
})3단계: 영구 프로필 도입 — 쿠키/세션 유지
매번 새 Chrome 인스턴스를 띄우는 대신 영구 프로필 디렉토리를 지정해서 로그인 쿠키와 브라우저 히스토리를 유지했다. 실제 사람처럼 "자주 방문한 사이트" 흔적을 남기는 전략이다. 캡차 발생률이 확실히 낮아졌다. 하지만 처음 프로필 생성 시나 프로필이 비어있을 때는 여전히 캡차가 떴다.
options.add_argument("--user-data-dir=/home/user/chrome-profile")
options.add_argument("--profile-directory=Default")34~6단계: 딜레이, WebDriverWait 속도전, CDP 직접 실행
4단계: 랜덤 딜레이 삽입 — 효과 미미
클릭과 입력 사이에 1~3초 랜덤 딜레이를 넣어서 사람처럼 행동하게 만들었다. 페이지 로드 후에도 추가 대기를 넣었다. 체감상 효과가 거의 없었다. 이미 $cdc_ 변수로 봇으로 판정된 이후에는 아무리 느리게 행동해도 의미가 없었던 것 같다.
5단계: WebDriverWait로 즉시 입력 — 약간 개선
발상을 바꿨다. "느리게 행동"이 아니라 "빠르게 행동"으로. 네이버의 봇 감지 스크립트가 로드되기 전에 입력을 완료해버리면 어떨까 싶었다. WebDriverWait로 요소가 나타나는 즉시 입력하도록 바꿨더니 성공률이 약간 올랐다. 하지만 이건 운에 가까웠다. 타이밍이 맞으면 성공, 아니면 실패.
6단계: CDP 직접 실행 (chromedriver 없이) — 시작 자체가 불안정
chromedriver를 완전히 제거하고 Chrome을 --remote-debugging-port로 직접 실행한 뒤 CDP로 제어하는 방식을 시도했다. 이론상 chromedriver가 없으면 $cdc_ 변수도 없을 테니까. 그런데 이 방법은 Chrome 시작 자체가 불안정했다. 가끔 포트 바인딩에 실패하고, remote debugging 연결도 간헐적으로 끊겼다. 안정성이 너무 낮아서 포기했다.
47단계: undetected-chromedriver — 완전 해결
6번의 실패 후 스택오버플로우와 GitHub 이슈들을 뒤지다가 undetected-chromedriver(UC)를 발견했다. chromedriver 바이너리 자체를 패치해서 봇 감지 마커를 제거하는 라이브러리다. 반신반의하면서 설치했더니 그날부로 캡차가 완전히 사라졌다. 100번을 돌려도 캡차 0.
# undetected-chromedriver 설치
pip install undetected-chromedriver
# 기존 코드 → UC로 변경 (드롭인 대체)
# 기존 from selenium import webdriver driver = webdriver.Chrome(options=options) # UC로 변경 import undetected_chromedriver as uc driver = uc.Chrome(options=options) # 끝. 나머지 코드는 그대로 사용 가능
캡차 완전 해결
UC로 전환 후 며칠간 수백 회 테스트를 돌렸는데 캡차가 한 번도 안 떴다. 기존 stealth 스크립트나 영구 프로필 설정은 그대로 유지해도 무방하고, 제거해도 된다.
5네이버가 보는 건 $cdc_ 변수였다
그러면 왜 1년간 잘 되다가 갑자기 문제가 생겼을까? 가장 유력한 원인은 두 가지다.
| 봇 감지 기법 | 설명 | 중요도 |
|---|---|---|
| $cdc_ 변수 | chromedriver가 DOM에 주입하는 고유 마커 변수. 이 변수가 존재하면 자동화 도구로 판정 | 핵심 |
| navigator.webdriver | 셀레니움이 true로 설정하는 플래그. CDP로 숨길 수 있지만 불완전 | 보조 |
| UA/TLS 불일치 | User-Agent 버전과 실제 Chrome 버전이 다르면 불일치 탐지 | 보조 |
| 행동 패턴 | 입력 속도, 마우스 이동 패턴 등 사람과 다른 행동 | 부가 |
UC가 하는 일은 단순하다. chromedriver 바이너리를 다운로드한 뒤$cdc_ 문자열을 다른 값으로 치환해서 패치한다. 이렇게 패치된 chromedriver로 Chrome을 실행하면 DOM에 봇 마커가 주입되지 않는다.
1년간 잘 되다가 갑자기 안 된 이유
네이버가 봇 감지 로직을 강화했거나, Chrome 업데이트로 인한 UA 버전 불일치가 쌓이면서 임계치를 넘겼을 가능성이 높다. 단일 원인이 아니라 복합적인 요인이 겹쳤을 것이다.
6최종 코드
현재 잘 돌아가고 있는 최종 코드다. UA 하드코딩을 제거하고 UC로 전환한 버전이다.
naver_login.py — 최종 버전
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import random
def create_driver():
options = uc.ChromeOptions()
# 영구 프로필 (로그인 상태 유지)
options.add_argument("--user-data-dir=/home/user/chrome-profile")
options.add_argument("--profile-directory=Default")
# UA는 하드코딩하지 않는다 → Chrome이 자동으로 보내는 실제 UA 사용
driver = uc.Chrome(options=options)
return driver
def naver_login(driver, username, password):
driver.get("https://nid.naver.com/nidlogin.login")
wait = WebDriverWait(driver, 10)
# 아이디 입력
id_field = wait.until(EC.element_to_be_clickable((By.ID, "id")))
time.sleep(random.uniform(0.5, 1.0))
id_field.click()
for ch in username:
id_field.send_keys(ch)
time.sleep(random.uniform(0.05, 0.15))
# 비밀번호 입력
pw_field = wait.until(EC.element_to_be_clickable((By.ID, "pw")))
time.sleep(random.uniform(0.3, 0.8))
pw_field.click()
for ch in password:
pw_field.send_keys(ch)
time.sleep(random.uniform(0.05, 0.15))
# 로그인 버튼 클릭
time.sleep(random.uniform(0.5, 1.0))
login_btn = driver.find_element(By.ID, "log.login")
login_btn.click()
time.sleep(2)
return "nid.naver.com/nidlogin" not in driver.current_url요약 체크리스트
셀레니움 네이버 캡차 해결 핵심
- ✓네이버 봇 감지의 핵심은 $cdc_ 변수 (chromedriver가 DOM에 주입)
- ✓undetected-chromedriver가 chromedriver 바이너리를 패치해서 $cdc_ 제거
- ✓기존 selenium 코드에서 import만 바꾸면 드롭인 대체 가능
- ✓User-Agent는 하드코딩하지 말고 Chrome이 자동으로 보내는 값 사용
- ✓영구 프로필로 로그인 쿠키 유지하면 보조적으로 도움
- ✓navigator.webdriver 제거, 랜덤 딜레이는 보조적 효과에 불과
본 글은 2026년 3월 기준 실제 경험을 바탕으로 작성되었습니다. 네이버의 봇 감지 정책은 수시로 업데이트될 수 있으므로 이 방법이 영구적으로 작동한다는 보장은 없습니다. 자동화 스크립트는 해당 서비스의 이용약관과 robots.txt를 반드시 확인하고 사용하세요. 본 콘텐츠의 비상업적 공유는 자유이나, 상업적 이용 시 문의 페이지를 통해 연락 바랍니다.
댓글
(3개)로그인하면 댓글을 작성할 수 있습니다.
저도 똑같은 상황 겪었는데 undetected-chromedriver 쓰니까 바로 해결됐어요. $cdc_ 변수가 원인이라는 건 이 글 보고 처음 알았네요.
7단계 삽질 과정이 저랑 완전히 똑같아서 소름돋았습니다. 저는 결국 CDP 방식까지 갔다가 포기했는데 UC가 답이었군요.
UC 쓸 때 Chrome 버전 자동 매칭이 안 되면 직접 버전 지정해야 하더라고요. version_main 파라미터로 해결했습니다.
관련 글
© 2026 TreeRU. All rights reserved.
본 콘텐츠의 저작권은 TreeRU에 있으며, 출처를 밝히지 않은 무단 전재 및 재배포를 금합니다. 인용 시 출처(treeru.com)를 반드시 명시해 주세요.