Selenium 프록시 인증, 왜 이렇게 어려운가
Selenium으로 대규모 스크래핑이나 QA 자동화를 구축한 경험이 있다면, 한 가지 사실을 이미 알 것입니다. 표준 Selenium은 프록시 인증을 지원하지 않습니다. ChromeOptions에 --proxy-server를 넘기면 HTTP CONNECT 프록시는 작동하지만, user:pass@host:port 형식의 인증 URL은 브라우저가 무시합니다. 그 결과 — 407 Proxy Authentication Required 팝업이 뜨거나, 아예 연결이 거부됩니다.
이 문제는 레지덴셜 프록시를 사용할 때 더 치명적입니다. 레지덴셜 IP는 기본적으로 사용자 인증을 요구하고, 국가·도시 타겟팅이나 세션 고정 같은 기능은 모두 사용자 이름 필드에 플래그를 인코딩하는 방식으로 동작하기 때문입니다. 즉, Selenium residential proxies를 제대로 쓰려면 프록시 인증을 우회하는 확장 메커니즘이 필수입니다.
이 글에서는 이 문제를 해결하는 네 가지 접근법을 코드와 함께 설명하고, 스텔스 기법, IP 회전 패턴, 컨테이너 기반 병렬 크롤링까지 실전 구현을 다룹니다.
Chrome + selenium-wire: 인증 프록시의 정석
selenium-wire는 Selenium WebDriver를 래핑하여 HTTP 요청/응답을 가로채고, 프록시 인증 헤더를 자동으로 주입하는 라이브러리입니다. Chrome DevTools Protocol(CDP)을 직접 다루지 않아도 되므로, 가장 간단한 Selenium proxy auth 해결책으로 널리 쓰입니다.
기본 구성
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
PROXY_USER = "user-country-US"
PROXY_PASS = "your_password"
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
# selenium-wire에 프록시 설정 전달
seleniumwire_options = {
"proxy": {
"https": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
"http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
},
"verify_ssl": False,
}
driver = webdriver.Chrome(
options=options,
seleniumwire_options=seleniumwire_options,
)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()selenium-wire는 백그라운드에서 로컬 미티게이션 프록시를 띄우고, 브라우저 → 로컬 프록시 → ProxyHat 게이트웨이 체인을 자동으로 구성합니다. 덕분에 브라우저는 인증 없는 로컬 프록시로 연결하고, 로컬 프록시가 ProxyHat에 Proxy-Authorization 헤더를 추가해 줍니다.
주의사항
- selenium-wire는 WebDriver를 서브클래싱하므로, 기존
webdriver.Chrome호출을seleniumwire.webdriver.Chrome으로 교체하기만 하면 됩니다. driver.requests속성으로 모든 네트워크 요청/응답에 접근할 수 있어 디버깅에 유용합니다.- 대역폭 오버헤드가 약간 있으므로, 초고속 스크래핑이 필요하면 CDP 방식을 고려하세요.
Firefox: 프로필 기반 프록시 설정
Firefox는 Chrome과 달리 프로필(pref.js)에 프록시 인증 정보를 직접 저장할 수 있습니다. 확장 프로그램 없이도 인증 프록시를 설정할 수 있어, 가벼운 솔루션을 원할 때 적합합니다.
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
import tempfile, os
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "user-country-DE-city-berlin"
PROXY_PASS = "your_password"
# 임시 Firefox 프로필 생성
profile_dir = tempfile.mkdtemp()
user_js = os.path.join(profile_dir, "user.js")
with open(user_js, "w") as f:
f.write(f"""
pref("network.proxy.type", 1);
pref("network.proxy.http", "{PROXY_HOST}");
pref("network.proxy.http_port", {PROXY_PORT});
pref("network.proxy.ssl", "{PROXY_HOST}");
pref("network.proxy.ssl_port", {PROXY_PORT});
pref("network.proxy.no_proxies_on", "localhost, 127.0.0.1");
pref("signon.autologin.network", true);
""")
# 인증 정보는 로그인 매니저에 저장
logins_json = os.path.join(profile_dir, "logins.json")
with open(logins_json, "w") as f:
f.write(f"""{{"nextId": 1, "logins": [{{
"hostname": "moz-proxy://{PROXY_HOST}:{PROXY_PORT}",
"username": "{PROXY_USER}",
"password": "{PROXY_PASS}",
"usernameField": "",
"passwordField": ""
}}]}}""")
options = Options()
options.add_argument("-headless")
options.profile = profile_dir
driver = webdriver.Firefox(options=options)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()Firefox 프로필 방식은 selenium-wire 같은 외부 의존성 없이 작동하지만, 프로필 디렉터리 관리와 정리를 직접 해야 합니다. 병렬 세션을 많이 띄울 때는 임시 프로필이 누적되지 않도록 shutil.rmtree(profile_dir) 호출을 반드시 보장해야 합니다.
Selenium Stealth: 자동화 지문 최소화
프록시 인증만 해결되는 것으로는 부족합니다. 많은 사이트가 WebDriver 탐지를 통해 Selenium 트래픽을 차단합니다. navigator.webdriver 플래그, cdc_ 변수, 자동화 특유의 스택 트레이스 등이 감지 대상입니다.
selenium-stealth 적용
selenium-stealth는 Chrome의 자동화 지문을 최소화하는 패치 세트입니다. 사용이 매우 간단합니다.
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
# 스텔스 패치 적용
stealth(
driver,
languages=["ko-KR", "en-US"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hh=True,
)
driver.get("https://bot.sannysoft.com")
print("Stealth test:", "detected" if "WebDriver" in driver.page_source else "ok")
driver.quit()selenium-driverless: 한 단계 더 나아가기
selenium-driverless는 CDP를 통해 Chrome을 직접 제어하여, chromedriver 바이너리 자체를 우회합니다. 이렇게 하면 cdc_ 변수 같은 chromedriver 지문이 원천 제거됩니다.
import asyncio
from selenium_driverless.webdriver import Chrome as DriverlessChrome
from selenium_driverless.webdriver.options import Options as DriverlessOptions
async def main():
options = DriverlessOptions()
options.add_argument("--headless=new")
# CDP를 통한 프록시 인증 — 확장 없이 가능
options.proxy = {
"server": f"http://gate.proxyhat.com:8080",
"username": "user-country-JP",
"password": "your_password",
}
driver = await DriverlessChrome(options=options)
await driver.get("https://httpbin.org/ip")
src = await driver.page_source
print(src)
await driver.quit()
asyncio.run(main())Selenium stealth 기법은 프록시와 결합할 때 진가를 발휘합니다. 레지덴셜 IP로 현지 사용자를 위장하고, 스텔스 패치로 봇 탐지를 우회하면 — 성공률이 극적으로 올라갑니다.
회전 프록시 풀: 세션마다 새 IP
대규모 스크래핑에서는 한 IP로 수천 페이지를 요청하면 차단이 불가피합니다. Selenium residential proxies의 핵심 이점은 IP 회전입니다. 각 WebDriver 세션에 새 IP를 할당하는 패턴을 구현해 봅시다.
프록시 풀 매니저
import itertools
import string
import random
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
from selenium_stealth import stealth
class RotatingProxyPool:
"""각 WebDriver 세션에 고유한 레지덴셜 IP를 할당하는 풀 매니저."""
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
def __init__(self, base_user: str, password: str):
self.base_user = base_user
self.password = password
def _session_id(self, length: int = 8) -> str:
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
def create_driver(self, country: str = "US", city: str = None):
"""새 IP로 WebDriver 세션을 생성합니다."""
sid = self._session_id()
# 사용자 이름에 국가·세션 플래그 인코딩
user_part = f"{self.base_user}-country-{country}-session-{sid}"
if city:
user_part = f"{self.base_user}-country-{country}-city-{city}-session-{sid}"
proxy_url = f"http://{user_part}:{self.password}@{self.PROXY_HOST}:{self.PROXY_PORT}"
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
sw_options = {"proxy": {"http": proxy_url, "https": proxy_url}}
driver = webdriver.Chrome(
options=options,
seleniumwire_options=sw_options,
)
stealth(driver, languages=["en-US"], platform="Win32")
return driver
# 사용 예시
pool = RotatingProxyPool(base_user="user", password="your_password")
# 각 세션이 다른 레지덴셜 IP를 사용
drivers = []
for country in ["US", "DE", "JP", "KR"]:
d = pool.create_driver(country=country)
d.get("https://httpbin.org/ip")
drivers.append(d)
print(f"{country}: {d.find_element('tag name','body').text[:80]}")
# 반드시 정리
for d in drivers:
d.quit()핵심 아이디어는 세션 ID를 사용자 이름에 인코딩하는 것입니다. ProxyHat 게이트웨이는 -session-{id} 플래그를 보고, 해당 세션에 고정된 레지덴셜 IP를 할당합니다. 세션마다 다른 ID를 사용하면, 각 WebDriver 인스턴스가 서로 다른 IP를 받습니다.
스티키 세션 vs 퍼-리퀘스트 회전
| 전략 | 사용자 이름 플래그 | 적합한 용도 | IP 지속 시간 |
|---|---|---|---|
| 퍼-리퀘스트 회전 | -session-rand (매 요청 변경) | SERP 스크래핑, 대규모 데이터 수집 | 1회 요청 |
| 스티키 세션 | -session-abc123 (고정) | 로그인 후 크롤링, 장바구니·체크아웃 | 최대 30분 |
| 도시 타겟팅 | -country-US-city-nyc | 현지 가격 비교, 지역 검증 | 세션 기간 |
Selenium Grid + Docker: 대규모 병렬 크롤링
단일 머신에서 WebDriver를 여러 개 띄우면 리소스 경합으로 성능이 급락합니다. Selenium Grid를 Docker로 구성하면, 컨테이너 단위로 브라우저 노드를 수평 확장할 수 있습니다.
Docker Compose 구성
# docker-compose.yml
version: '3.8'
services:
selenium-hub:
image: selenium/hub:4.18
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
environment:
- SE_SESSION_REQUEST_TIMEOUT=300
- SE_NODE_SESSION_TIMEOUT=300
chrome-node:
image: selenium/node-chrome:4.18
depends_on:
- selenium-hub
deploy:
replicas: 4
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=2
- SE_NODE_OVERRIDE_MAX_SESSIONS=true
shm_size: '2gb'Grid에 연결하는 클라이언트
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from seleniumwire import webdriver as sw_webdriver
from concurrent.futures import ThreadPoolExecutor, as_completed
GRID_URL = "http://localhost:4444/wd/hub"
PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "user-country-US"
PROXY_PASS = "your_password"
def scrape_page(url: str) -> str:
options = Options()
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
sw_opts = {
"proxy": {
"http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
"https": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
}
}
driver = sw_webdriver.Chrome(
options=options,
seleniumwire_options=sw_opts,
command_executor=GRID_URL,
)
try:
driver.get(url)
return driver.title
finally:
driver.quit()
urls = [
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
]
with ThreadPoolExecutor(max_workers=4) as pool:
futures = {pool.submit(scrape_page, u): u for u in urls}
for f in as_completed(futures):
print(f"{futures[f.result]} → {f.result}")컨테이너화 시 핵심 팁
- shm_size를 최소 2GB로 설정하세요. Chrome은 공유 메모리를 많이 사용하며, 기본 64MB에서는 크래시가 빈번합니다.
- SE_NODE_MAX_SESSIONS를 CPU 코어 수에 맞추세요. vCPU 2개면 세션 2개가 적당합니다.
- 노드를 수평 확장하려면
replicas를 늘리거나, Kubernetes에 Grid를 배포하세요. - selenium-wire는 로컬 미티게이션 프록시를 띄우므로, Grid 환경에서는 노드 컨테이너가 ProxyHat 게이트웨이에 아웃바운드 접근할 수 있어야 합니다.
대규모 병렬 크롤링에 대한 더 자세한 아키텍처는 웹 스크래핑 유스케이스를 참고하세요.
Playwright vs Selenium: 언제 전환해야 할까
Selenium은 20년 가까운 역사와 방대한 생태계를 자랑하지만, 최근 Playwright가 강력한 대안으로 떠오르고 있습니다. 특히 Selenium proxy auth와 Selenium stealth 문제에서 Playwright는 기본적으로 더 나은 해결책을 제공합니다.
| 비교 항목 | Selenium | Playwright |
|---|---|---|
| 프록시 인증 | selenium-wire 또는 CDP 확장 필요 | 네이티브 지원 (proxy: {username, password}) |
| 스텔스 | selenium-stealth / driverless 필요 | 기본적으로 더 적은 지문 (자동화 플래그 감소) |
| API 설계 | 동기 중심, 비공식 비동기 래퍼 존재 | 비동기 네이티브, auto-wait 내장 |
| 브라우저 지원 | Chrome, Firefox, Edge, Safari (드라이버 필요) | Chromium, Firefox, WebKit (내장 바이너리) |
| 생태계 | 압도적 (Sauce Labs, BrowserStack, 레거시 QA) | 빠르게 성장 중, 하지만 레거시 호환성은 낮음 |
| 학습 곡선 | 낮음 (풍부한 StackOverflow 답변) | 중간 (공식 문서 훌륭, 커뮤니티는 성장 중) |
Selenium을 유지해야 하는 경우
- 기존 QA 스위트가 Selenium 기반이고, 전환 비용이 너무 큰 경우
- Sauce Labs, BrowserStack 등 클라우드 테스트 플랫폼과의 통합이 필요한 경우
- Appium을 통한 모바일 자동화를 함께 운영하는 경우
Playwright로 전환해야 하는 경우
- 프록시 인증과 스텔스가 핵심 요구사항인 신규 스크래핑 프로젝트
- 빠른 병렬 실행과 자동 대기(auto-wait)가 필요한 경우
- 모던 비동기 Python/Node.js 스택과의 긴밀한 통합이 필요한 경우
Playwright에서 ProxyHat을 사용하는 방법은 Playwright 프록시 설정 가이드를 참고하세요.
실전 체크리스트: Selenium + ProxyHat 운영 가이드
- 프록시 인증: selenium-wire(Chrome) 또는 프로필 방식(Firefox) 중 하나를 선택하세요. CDP 직접 제어가 가능하다면 selenium-driverless도 좋은 선택입니다.
- 스텔스: 최소한 selenium-stealth를 적용하세요. 고도의 봇 탐지를 피해야 한다면 selenium-driverless를 고려하세요.
- IP 회전: 세션 ID를 사용자 이름에 인코딩하여, 각 WebDriver 인스턴스에 고유 IP를 할당하세요.
- 병렬화: Selenium Grid + Docker Compose로 브라우저 노드를 수평 확장하세요.
- 정리:
driver.quit()을 반드시 보장하세요. try/finally 또는 컨텍스트 매니저를 사용하세요. - 속도 제한: 초당 요청 수를 도메인당 1~3회로 유지하여, 레지덴셜 IP의 수명을 보호하세요.
- 윤리:
robots.txt를 존중하고, 개인 데이터 수집은 GDPR/CCPA를 준수하세요.
핵심 요약: Selenium에서 인증 프록시를 쓰는 가장 빠른 방법은 selenium-wire입니다. 스텔스가 필요하면 selenium-stealth를 추가하고, 대규모 병렬 처리는 Selenium Grid + Docker로 수평 확장하세요. 신규 프로젝트라면 Playwright의 네이티브 프록시 인증과 낮은 지문을 반드시 검토하세요.
ProxyHat의 레지덴셜·모바일·데이터센터 프록시는 Selenium, Playwright, 그리고 어떤 자동화 도구와도 완벽히 호환됩니다. 프록시 요금제를 확인하거나, 지원 국가 목록에서 타겟 지역을 확인해 보세요.






