블로그로 돌아가기

프록시가 계속 차단되는 경우 해결 방법: 진단 및 문제 해결 완벽 가이드

프록시 차단 원인을 분석하고 실용적인 해결책을 제시합니다: 올바른 로테이션부터 브라우저 핑거프린팅 설정까지.

📅2025년 12월 15일

프록시가 계속 차단될 때: 진단 및 문제 해결을 위한 완벽한 가이드

프록시의 지속적인 차단은 웹 스크래핑, 자동화 및 다중 계정 작업 시 가장 흔한 문제 중 하나입니다. 이 글에서는 이런 일이 발생하는 이유를 분석하고 무한정 제공업체를 바꾸는 것이 아닌 체계적인 해결 방법을 제시합니다.

프록시가 실제로 차단되는 이유

해결책을 찾기 전에 차단 메커니즘을 이해해야 합니다. 현대의 부정행위 방지 시스템은 다층 보안을 사용하며, 프록시 차단은 결과일 뿐 원인이 아닙니다. 이러한 시스템의 작동 방식을 이해하면 효과적인 우회 전략을 수립할 수 있습니다.

IP 평판 및 블랙리스트

각 IP 주소는 사용 이력을 바탕으로 평판이 형성됩니다. 주소가 이전에 스팸, DDoS 공격 또는 대량 스크래핑에 사용된 경우 Spamhaus, SORBS 또는 특정 서비스의 독점 목록과 같은 데이터베이스에 등재됩니다. 이러한 IP를 통해 연결하면 시스템이 즉시 의심을 갖게 됩니다.

데이터센터 프록시는 특히 이 문제에 취약합니다. 전체 서브넷이 "호스팅"으로 표시될 수 있으며, 이러한 IP의 모든 트래픽은 자동으로 높은 수준의 검사를 받습니다. Amazon AWS, Google Cloud, DigitalOcean의 IP 범위는 잘 알려져 있으며 종종 사전에 차단됩니다.

IPQualityScore, Scamalytics 또는 AbuseIPDB와 같은 서비스를 통해 IP 평판을 확인할 수 있습니다. 프록시의 사기 점수가 75 이상이면 문제가 바로 여기에 있습니다. 제공업체를 변경하거나 프록시 유형을 바꾸세요.

요청 패턴

사람은 초당 100개의 요청을 하지 않습니다. 사람은 정확히 2초 간격으로 페이지를 탐색하지 않습니다. 사람은 이미지, CSS, JavaScript를 무시하고 HTML만 요청하지 않습니다. 부정행위 방지 시스템은 이러한 패턴을 분석하며, "인간적" 행동에서의 모든 편차는 차단 위험을 증가시킵니다.

요청 간 시간의 통계가 특히 중요합니다. 안정적인 간격은 자동화의 명확한 신호입니다. 임의의 지연(예: 1~5초)을 추가하면 탐지 가능성이 크게 감소합니다.

메타데이터 불일치

User-Agent가 Windows의 Chrome을 나타내지만 HTTP 헤더가 Python requests의 특성을 드러내면 이는 위험 신호입니다. IP 주소가 독일에 위치하지만 브라우저 언어 설정이 러시아어를 나타내면 또 다른 위험 신호입니다. JavaScript의 시간대가 IP의 지리적 위치와 일치하지 않으면 세 번째 신호입니다.

이러한 불일치의 축적은 시스템이 연결을 의심스러운 것으로 분류하고 보호 조치를 적용하도록 합니다: 캡차부터 완전한 IP 차단까지.

브라우저 지문

현대의 보안 시스템은 브라우저의 수십 가지 매개변수를 수집합니다: 화면 해상도, 설치된 글꼴, 플러그인, WebGL 렌더링, 오디오 컨텍스트 등. 이러한 매개변수의 조합은 IP 변경 후에도 일정하게 유지되는 고유한 "지문"을 만듭니다.

프록시를 변경하지만 지문이 동일하게 유지되면 시스템은 같은 사용자임을 이해합니다. 하나의 지문이 짧은 시간에 수백 개의 다른 IP에서 나타나면 이는 자동화의 명확한 신호입니다.

진단: 차단 원인 파악 방법

무작정 설정을 변경하기 전에 진단을 수행하세요. 이렇게 하면 수 시간의 실험을 절약하고 문제의 실제 원인을 찾을 수 있습니다. 체계적인 진단 접근은 효과적인 해결책의 열쇠입니다.

단계 1: 프록시 자체 확인

메인 스크립트와 독립적으로 프록시의 기본 작동을 확인하세요:

import requests

proxy = {
    "http": "http://user:pass@proxy-server:port",
    "https": "http://user:pass@proxy-server:port"
}

# 기본 작동 확인
try:
    response = requests.get("https://httpbin.org/ip", proxies=proxy, timeout=10)
    print(f"프록시를 통한 IP: {response.json()['origin']}")
except Exception as e:
    print(f"연결 오류: {e}")

# 실제 IP 누출 확인
response = requests.get("https://browserleaks.com/ip", proxies=proxy)
# 실제 IP와 비교

프록시가 간단한 요청에서도 작동하지 않으면 문제는 프록시 자체 또는 자격 증명에 있습니다. 연결 형식의 정확성, 제공업체의 잔액 및 제한을 확인하세요.

단계 2: IP 평판 확인

종합적인 평가를 위해 여러 서비스를 사용하세요:

# 프록시 IP 획득
proxy_ip = requests.get("https://api.ipify.org", proxies=proxy).text

# 다음 서비스에서 확인:
# https://www.ipqualityscore.com/free-ip-lookup-proxy-vpn-test
# https://scamalytics.com/ip/{proxy_ip}
# https://www.abuseipdb.com/check/{proxy_ip}
# https://whatismyipaddress.com/ip/{proxy_ip}

print(f"위 서비스에서 IP {proxy_ip} 확인")

다음 지표에 주의하세요: 사기 점수(50 이하여야 함), IP 유형(데이터센터보다 주거용이 좋음), 블랙리스트 포함 여부. IP가 VPN/프록시로 표시되면 많은 웹사이트가 처음부터 의심을 갖게 됩니다.

단계 3: 문제 격리

같은 프록시를 다양한 대상 웹사이트에서 시도하세요. 모든 곳에서 차단되면 프록시 또는 설정에 문제가 있습니다. 특정 웹사이트에서만 차단되면 그 웹사이트의 보안 또는 행동에 문제가 있습니다.

또한 한 웹사이트에서 다양한 프록시를 시도하세요. 모두 차단되면 프록시가 아니라 스크립트, 지문 또는 행동 패턴에 문제가 있습니다. 이는 많은 사람들이 놓치는 중요한 테스트입니다.

단계 4: 서버 응답 분석

다양한 유형의 차단이 다르게 나타납니다. 이를 구분하는 방법을 배우세요:

def analyze_response(response):
    status = response.status_code
    
    if status == 403:
        print("접근 거부 — IP가 블랙리스트에 있을 수 있음")
    elif status == 429:
        print("너무 많은 요청 — 요청 빈도 감소")
    elif status == 503:
        print("서비스 이용 불가 — DDoS 방어 가능성")
    elif status == 407:
        print("프록시 인증 필요 — 자격 증명 확인")
    elif "captcha" in response.text.lower():
        print("캡차 감지 — 봇 의심")
    elif "blocked" in response.text.lower():
        print("명시적 차단 — IP 변경 및 접근 방식 재검토")
    elif len(response.text) < 1000:
        print("의심스럽게 짧은 응답 — 가능한 스텁")
    else:
        print(f"상태 {status}, 응답 길이: {len(response.text)}")

올바른 로테이션: 빈도, 논리, 구현

프록시 로테이션은 단순히 "자주 IP를 변경"하는 것이 아닙니다. 잘못된 로테이션은 없는 것보다 더 해로울 수 있습니다. 다양한 전략과 각각의 적용 시기를 살펴보겠습니다.

전략 1: 요청 수 기반 로테이션

가장 간단한 접근 방식은 일정 수의 요청 후 IP를 변경하는 것입니다. 세션이 필요 없는 스크래핑에 적합합니다:

import random

class ProxyRotator:
    def __init__(self, proxy_list, requests_per_proxy=50):
        self.proxies = proxy_list
        self.requests_per_proxy = requests_per_proxy
        self.current_proxy = None
        self.request_count = 0
    
    def get_proxy(self):
        if self.current_proxy is None or self.request_count >= self.requests_per_proxy:
            # 요청 수에 무작위성 추가
            self.requests_per_proxy = random.randint(30, 70)
            self.current_proxy = random.choice(self.proxies)
            self.request_count = 0
        
        self.request_count += 1
        return self.current_proxy

# 사용
rotator = ProxyRotator(proxy_list)
for url in urls_to_scrape:
    proxy = rotator.get_proxy()
    response = requests.get(url, proxies={"http": proxy, "https": proxy})

요청 수의 무작위성에 주목하세요. 정확히 50개(예: 정확히 50개)는 감지될 수 있는 패턴입니다. 무작위 범위는 행동을 덜 예측 가능하게 만듭니다.

전략 2: 시간 기반 로테이션

세션이 중요한 작업(예: 계정 작업)의 경우 IP를 시간에 연결하는 것이 좋습니다:

import time
import random

class TimeBasedRotator:
    def __init__(self, proxy_list, min_minutes=10, max_minutes=30):
        self.proxies = proxy_list
        self.min_seconds = min_minutes * 60
        self.max_seconds = max_minutes * 60
        self.current_proxy = None
        self.rotation_time = 0
    
    def get_proxy(self):
        current_time = time.time()
        
        if self.current_proxy is None or current_time >= self.rotation_time:
            self.current_proxy = random.choice(self.proxies)
            # 다음 로테이션까지의 무작위 간격
            interval = random.randint(self.min_seconds, self.max_seconds)
            self.rotation_time = current_time + interval
            print(f"새 프록시, 다음 로테이션까지 {interval//60}분")
        
        return self.current_proxy

전략 3: 계정용 고정 세션

여러 계정으로 작업할 때 각 계정이 일정한 IP를 사용하는 것이 중요합니다. 로그인한 계정의 IP 변경은 차단으로 가는 확실한 길입니다:

class AccountProxyManager:
    def __init__(self, proxy_pool):
        self.proxy_pool = proxy_pool
        self.account_proxies = {}  # account_id -> proxy
        self.used_proxies = set()
    
    def get_proxy_for_account(self, account_id):
        # 계정에 이미 프록시가 할당된 경우 반환
        if account_id in self.account_proxies:
            return self.account_proxies[account_id]
        
        # 사용 가능한 프록시 찾기
        available = [p for p in self.proxy_pool if p not in self.used_proxies]
        
        if not available:
            raise Exception("새 계정용 사용 가능한 프록시 없음")
        
        proxy = random.choice(available)
        self.account_proxies[account_id] = proxy
        self.used_proxies.add(proxy)
        
        return proxy
    
    def release_account(self, account_id):
        """계정 삭제 시 프록시 해제"""
        if account_id in self.account_proxies:
            proxy = self.account_proxies.pop(account_id)
            self.used_proxies.discard(proxy)

# 사용
manager = AccountProxyManager(residential_proxy_list)

for account in accounts:
    proxy = manager.get_proxy_for_account(account.id)
    # 이 계정의 모든 작업은 하나의 IP를 통해 진행

전략 4: 적응형 로테이션

가장 고급 접근 방식은 대상 웹사이트의 신호에 따라 프록시를 변경하는 것입니다:

class AdaptiveRotator:
    def __init__(self, proxy_list):
        self.proxies = proxy_list
        self.current_proxy = random.choice(proxy_list)
        self.proxy_scores = {p: 100 for p in proxy_list}  # 초기 프록시 "건강도"
    
    def get_proxy(self):
        return self.current_proxy
    
    def report_result(self, success, response_code=200):
        """각 요청 후 호출"""
        if success and response_code == 200:
            # 성공한 요청 — 점수 약간 증가
            self.proxy_scores[self.current_proxy] = min(100, 
                self.proxy_scores[self.current_proxy] + 1)
        elif response_code == 429:
            # 속도 제한 — 점수 감소 및 로테이션
            self.proxy_scores[self.current_proxy] -= 30
            self._rotate()
        elif response_code == 403:
            # 차단 — 점수 0으로 설정 및 로테이션
            self.proxy_scores[self.current_proxy] = 0
            self._rotate()
        elif response_code == 503:
            # 가능한 보안 — 점수 감소 및 로테이션
            self.proxy_scores[self.current_proxy] -= 20
            self._rotate()
    
    def _rotate(self):
        # 가장 높은 점수의 프록시 선택
        available = [(p, s) for p, s in self.proxy_scores.items() if s > 20]
        if not available:
            # 모든 프록시가 "죽음" — 점수 초기화
            self.proxy_scores = {p: 50 for p in self.proxies}
            available = list(self.proxy_scores.items())
        
        # 점수에 따른 가중 선택
        self.current_proxy = max(available, key=lambda x: x[1])[0]
        print(f"점수 {self.proxy_scores[self.current_proxy]}인 프록시로 로테이션")

브라우저 지문과 차단의 역할

지문은 쿠키 없이도 사용자를 식별할 수 있는 브라우저 특성의 조합입니다. IP를 변경하지만 지문이 동일하게 유지되면 보안 시스템이 모든 세션을 쉽게 연결합니다.

지문의 구성

현대의 지문에는 수십 가지 매개변수가 포함됩니다. 주요 범주는 다음과 같습니다:

범주 매개변수 식별에서의 가중치
User-Agent 브라우저, 버전, OS 중간
화면 해상도, 색상 깊이, 픽셀 비율 중간
글꼴 설치된 글꼴 목록 높음
WebGL 렌더러, 공급업체, 렌더링 해시 매우 높음
Canvas 렌더링된 이미지 해시 매우 높음
Audio AudioContext 지문 높음
시간대 시간대, 오프셋 중간
언어 navigator.languages 중간
플러그인 navigator.plugins 낮음(현대 브라우저에서)

지문과 IP의 일관성

지문이 IP의 지리적 위치와 일치하는 것이 중요합니다. 프록시가 독일에 있으면 지문이 독일 사용자처럼 보여야 합니다:

// 불일치 예(나쁨):
// IP: 독일
// 시간대: America/New_York
// 언어: ["ru-RU", "ru"]
// 이는 의심을 유발합니다

// 일관된 지문(좋음):
// IP: 독일
// 시간대: Europe/Berlin
// 언어: ["de-DE", "de", "en-US", "en"]

지문 관리 도구

진지한 작업을 위해 전문 도구를 사용하세요:

Stealth가 포함된 Playwright:

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync

with sync_playwright() as p:
    browser = p.chromium.launch(
        proxy={"server": "http://proxy:port", "username": "user", "password": "pass"}
    )
    
    context = browser.new_context(
        viewport={"width": 1920, "height": 1080},
        locale="de-DE",
        timezone_id="Europe/Berlin",
        geolocation={"latitude": 52.52, "longitude": 13.405},
        permissions=["geolocation"]
    )
    
    page = context.new_page()
    stealth_sync(page)  # stealth 패치 적용
    
    page.goto("https://target-site.com")

puppeteer-extra가 포함된 Puppeteer:

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
    args: [`--proxy-server=http://proxy:port`]
});

const page = await browser.newPage();

// 시간대 재정의
await page.evaluateOnNewDocument(() => {
    Object.defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
        value: function() {
            return { timeZone: 'Europe/Berlin' };
        }
    });
});

안티디텍트 브라우저

계정 작업에는 종종 안티디텍트 브라우저(Multilogin, GoLogin, Dolphin Anty 등)를 사용합니다. 이들은 고유한 지문을 가진 격리된 프로필을 만들 수 있습니다. 각 프로필은 자체 매개변수 세트, 쿠키, localStorage를 가지고 있습니다 — 완전히 격리된 환경입니다.

안티디텍트의 장점은 지문 문제를 "즉시" 해결한다는 것입니다. 단점은 비용과 자동화의 복잡성입니다(많은 제품이 API를 제공하지만).

행동 패턴: 봇처럼 보이지 않는 방법

완벽한 지문과 깨끗한 IP를 가지고도 비인간적인 행동으로 인해 차단될 수 있습니다. 현대 시스템은 기술 매개변수뿐만 아니라 웹사이트와의 상호작용 패턴도 분석합니다.

시간 지연

사람은 일정한 간격으로 요청을 하지 않습니다. 정규 분포를 사용하여 무작위 지연을 추가하세요:

import random
import time
import numpy as np

def human_delay(min_sec=1, max_sec=5, mean=2.5):
    """
    인간적인 지연을 생성합니다.
    로그 정규 분포 사용 — 
    대부분의 지연은 짧지만 때로는 길기도 합니다.
    """
    delay = np.random.lognormal(mean=np.log(mean), sigma=0.5)
    delay = max(min_sec, min(max_sec, delay))
    return delay

def human_typing_delay():
    """텍스트 입력 시 키 누름 간 지연"""
    return random.uniform(0.05, 0.25)

# 사용
for url in urls:
    response = requests.get(url, proxies=proxy)
    process(response)
    time.sleep(human_delay())  # 요청 간 무작위 일시 중지

네비게이션 모방

사람은 직접 링크로 제품 페이지로 이동하지 않습니다. 홈페이지에 들어가 검색을 사용하고 카테고리를 탐색합니다. 이 경로를 모방하세요:

async def human_like_navigation(page, target_url):
    """인간적인 네비게이션을 목표 페이지로 모방"""
    
    # 1. 홈페이지로 이동
    await page.goto("https://example.com")
    await page.wait_for_timeout(random.randint(2000, 4000))
    
    # 2. 때때로 홈페이지 스크롤
    if random.random() > 0.5:
        await page.evaluate("window.scrollBy(0, 300)")
        await page.wait_for_timeout(random.randint(1000, 2000))
    
    # 3. 검색 또는 네비게이션 사용
    if random.random() > 0.3:
        search_box = await page.query_selector('input[type="search"]')
        if search_box:
            await search_box.type("search query", delay=100)
            await page.keyboard.press("Enter")
            await page.wait_for_timeout(random.randint(2000, 4000))
    
    # 4. 목표 페이지로 이동
    await page.goto(target_url)
    
    # 5. 인간처럼 페이지 스크롤
    await human_scroll(page)

async def human_scroll(page):
    """인간적인 스크롤을 모방"""
    scroll_height = await page.evaluate("document.body.scrollHeight")
    current_position = 0
    
    while current_position < scroll_height * 0.7:  # 끝까지 아님
        scroll_amount = random.randint(200, 500)
        await page.evaluate(f"window.scrollBy(0, {scroll_amount})")
        current_position += scroll_amount
        await page.wait_for_timeout(random.randint(500, 1500))

마우스 움직임

일부 시스템은 마우스 움직임을 추적합니다. A에서 B로의 직선 움직임은 봇의 신호입니다. 사람은 미세 조정이 있는 곡선으로 마우스를 움직입니다:

import bezier
import numpy as np

def generate_human_mouse_path(start, end, num_points=50):
    """
    베지어 곡선과 약간의 노이즈를 사용하여
    인간적인 마우스 경로를 생성합니다.
    """
    # 베지어 곡선의 제어점
    control1 = (
        start[0] + (end[0] - start[0]) * random.uniform(0.2, 0.4) + random.randint(-50, 50),
        start[1] + (end[1] - start[1]) * random.uniform(0.2, 0.4) + random.randint(-50, 50)
    )
    control2 = (
        start[0] + (end[0] - start[0]) * random.uniform(0.6, 0.8) + random.randint(-50, 50),
        start[1] + (end[1] - start[1]) * random.uniform(0.6, 0.8) + random.randint(-50, 50)
    )
    
    # 베지어 곡선 생성
    nodes = np.asfortranarray([
        [start[0], control1[0], control2[0], end[0]],
        [start[1], control1[1], control2[1], end[1]]
    ])
    curve = bezier.Curve(nodes, degree=3)
    
    # 곡선의 점 생성
    points = []
    for t in np.linspace(0, 1, num_points):
        point = curve.evaluate(t)
        # 미세 노이즈 추가
        x = point[0][0] + random.uniform(-2, 2)
        y = point[1][0] + random.uniform(-2, 2)
        points.append((x, y))
    
    return points

async def human_click(page, selector):
    """인간적인 마우스 움직임으로 요소 클릭"""
    element = await page.query_selector(selector)
    box = await element.bounding_box()
    
    # 대상 지점 — 중심이 아닌 요소 내 무작위 지점
    target_x = box['x'] + random.uniform(box['width'] * 0.2, box['width'] * 0.8)
    target_y = box['y'] + random.uniform(box['height'] * 0.2, box['height'] * 0.8)
    
    # 현재 마우스 위치(또는 무작위 시작점)
    start_x = random.randint(0, 1920)
    start_y = random.randint(0, 1080)
    
    # 경로 생성
    path = generate_human_mouse_path((start_x, start_y), (target_x, target_y))
    
    # 경로를 따라 마우스 이동
    for x, y in path:
        await page.mouse.move(x, y)
        await page.wait_for_timeout(random.randint(5, 20))
    
    # 클릭 전 약간의 일시 중지
    await page.wait_for_timeout(random.randint(50, 150))
    await page.mouse.click(target_x, target_y)

리소스 로드

실제 브라우저는 HTML뿐만 아니라 CSS, JavaScript, 이미지, 글꼴도 로드합니다. requests를 사용하고 HTML만 요청하면 의심스럽습니다. headless 브라우저를 사용할 때는 이 문제가 자동으로 해결되지만, HTTP 클라이언트를 사용할 때는 이를 고려해야 합니다.

작업에 맞는 프록시 유형 선택

다양한 프록시 유형은 다양한 특성을 가지고 있으며 다양한 작업에 적합합니다. 잘못된 선택은 차단의 빈번한 원인입니다.

데이터센터 프록시

데이터센터 프록시는 호스팅 제공업체에 속하는 IP 주소입니다. 대규모 데이터센터의 AS(자율 시스템)에 속함으로써 쉽게 식별됩니다.

장점:

  • 높은 속도 및 안정성
  • 낮은 비용
  • 대규모 IP 풀

단점:

  • 쉽게 감지됨
  • 종종 블랙리스트에 등재됨
  • 심각한 보안이 있는 사이트에는 부적합

적합한 용도: SEO 도구, 접근성 확인, 엄격한 보안이 없는 API 작업, 테스트.

주거용 프록시

주거용 프록시는 파트너 프로그램이나 앱의 SDK를 통해 제공되는 실제 사용자의 IP 주소입니다. 일반 인터넷 제공업체(ISP)에 속합니다.

장점:

  • 일반 사용자처럼 보임
  • 낮은 사기 점수
  • 광범위한 지리적 범위
  • 감지하기 어려움

단점:

  • 높은 비용(트래픽 기반 결제)
  • 속도는 최종 사용자에 따라 다름
  • IP가 "사라질" 수 있음(사용자가 기기를 끔)

적합한 용도: 보안이 있는 사이트 스크래핑, 소셜 미디어 작업, 전자상거래, 감지되지 않는 것이 중요한 모든 작업.

모바일 프록시

모바일 프록시는 모바일 운영자(MTS, Beeline, Megafon 등)의 IP 주소입니다. CGNAT 기술로 인해 특별한 지위를 가집니다.

장점:

  • 사이트의 최대 신뢰
  • 하나의 IP를 수천 명의 실제 사용자가 사용 — 차단하기 어려움
  • 계정 작업에 이상적
  • 요청 시 IP 변경(네트워크 재연결)

단점:

  • 가장 높은 비용
  • 제한된 속도
  • 지리적 선택 제한

적합한 용도: 다중 계정 작업, Instagram/Facebook/TikTok 작업, 계정 등록, 차단 위험이 높은 모든 작업.

비교 표

매개변수 데이터센터 주거용 모바일
감지 가능성 높음 낮음 매우 낮음
속도 높음 중간 낮음-중간
비용 $ $$ $$$
소셜 미디어용 부적합 적합 이상적
스크래핑용 간단한 사이트 모든 사이트 과도함

고급 보안 우회 기술

기본 방법이 작동하지 않으면 더 복잡한 기술을 사용해야 합니다. 몇 가지 고급 접근 방식을 살펴보겠습니다.

Cloudflare 및 유사 보안 작업

Cloudflare, Akamai, PerimeterX는 JavaScript 챌린지를 사용하여 브라우저를 확인합니다. 간단한 HTTP 요청은 통과하지 못합니다. 해결 옵션:

1. 실제 브라우저 사용:

from playwright.sync_api import sync_playwright

def bypass_cloudflare(url, proxy):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,  # 때때로 headless가 감지됨
            proxy={"server": proxy}
        )
        
        page = browser.new_page()
        page.goto(url)
        
        # 검사 통과 대기(보통 5-10초)
        page.wait_for_timeout(10000)
        
        # 통과했는지 확인
        if "challenge" not in page.url:
            # 후속 요청을 위해 쿠키 저장
            cookies = page.context.cookies()
            return cookies
        
        browser.close()
        return None

2. 기성 솔루션 사용:

# cloudscraper — Cloudflare 우회 라이브러리
import cloudscraper

scraper = cloudscraper.create_scraper(
    browser={
        'browser': 'chrome',
        'platform': 'windows',
        'desktop': True
    }
)

scraper.proxies = {"http": proxy, "https": proxy}
response = scraper.get("https://protected-site.com")

캡차 해결

사이트에서 캡차를 표시하면 몇 가지 접근 방식이 있습니다:

인식 서비스: 2Captcha, Anti-Captcha, CapMonster. 캡차를 해결해줍니다