Bifrost Logo BifrostNetwork
모든 블로그 글로 돌아가기
BifrostNetwork 기술팀

Playwright Scraper Proxy 실전: 주거용 프록시 인증, 세션, 재시도와 데이터 검증

Python과 Playwright로 프록시를 연결하는 기술 가이드. HTTP 인증, 주거용 고정 세션, 429 Retry-After, 브라우저 트래픽 최적화와 데이터 검증을 코드 및 공식 자료로 설명합니다.

프록시를 연결해 출구 IP가 바뀌었는데도 가격은 왜 다른 지역 기준일까요? HTTP 200을 받았는데 저장할 유효 상품이 없거나, 이미지를 차단했는데 프록시 트래픽이 줄지 않을 수도 있습니다.

이 문제들은 네트워크 출구, 브라우저 상태, 페이지 준비 조건, 데이터 검증이라는 서로 다른 계층에서 발생합니다. 운영 환경의 scraper proxy 통합은 프록시 세션과 브라우저 컨텍스트를 함께 관리하고 업무 데이터로 성공을 판단해야 합니다. Python, Playwright, BifrostNetwork를 이용해 구체적으로 살펴보겠습니다.

자료 확인일: 2026년 9월 14일. 공식 문서와 프로토콜 표준에 근거합니다. 예제는 연결 및 검증 방법을 설명하며, 상용 프록시의 성공률·지연·절감률을 실측한 결과가 아닙니다.

1. 프록시가 담당할 계층부터 정하기

Scraper proxy는 크롤러가 대상 사이트에 접근할 때 사용하는 네트워크 출구입니다. Proxy scraper는 보통 프록시 주소를 수집하는 도구를 뜻하므로 다른 요구 사항입니다.

이 구조에서는 큐가 방문 시점을 결정하고, Playwright가 JavaScript를 실행하며, 게이트웨이가 출구를 선택합니다. 파서는 필드를 추출하고 검증기는 저장 여부를 결정합니다. 프록시는 선택자를 유지보수하거나 없는 접근 권한을 부여하지 않습니다.

작업 조건권장 시작점검증 항목
공식 API나 내보내기에 필요한 필드가 있음해당 인터페이스 우선할당량, 데이터 권한, 갱신 주기
HTML에 필요한 데이터가 모두 있음HTTP 클라이언트, 필요하면 프록시 추가파싱 정확도, 출구 요구 사항
JavaScript 실행이나 상호작용이 필요함작업별 프록시를 설정한 Playwright준비 조건, 컨텍스트 상태, 브라우저 자원
특정 시장의 주거용 네트워크에서 관측해야 함주거용 출구 테스트실제 국가, 페이지 시장, 세션 연속성

이는 설계 제안이며 주거용 프록시가 다른 방식보다 빠르다는 결론이 아닙니다. 비용 비교는 Scraper Proxy 선택 가이드를 참고하세요.

Playwright는 브라우저 시작 시 또는 BrowserContext별로 프록시를 설정할 수 있습니다. 독립 컨텍스트는 쿠키와 캐시를 공유하지 않아 작업 분리에 적합합니다. Playwright 네트워크, Browser.new_context.

2. 프로토콜과 인증부터 확인하기

Chromium의 SOCKS5 지원은 비밀번호 인증 지원과 다릅니다

Chromium 공식 문서는 Chrome의 SOCKSv5가 인증 방식을 지원하지 않는다고 명시합니다. requests에서 작동하는 인증 포함 SOCKS5 연결 문자열을 Playwright Chromium에 그대로 복사하면 실패할 수 있습니다. 여기서는 사용자 이름과 비밀번호를 사용하는 HTTP 프록시를 선택합니다. Chromium 프록시 구현.

proxy.usernameproxy.password는 프록시 인증용이고, http_credentials는 웹사이트 HTTP 인증용입니다. 프록시 비밀번호를 사이트 인증에 넣거나 Proxy-Authorization을 일반 페이지 요청 헤더로 보내지 마세요. Playwright 인증과 프록시 설정.

프록시 URL이 http://여도 HTTPS 대상에 접속할 수 있습니다. HTTP 프록시는 CONNECT 터널을 만들고 그 안에서 대상과 TLS 통신을 수행할 수 있습니다. 그렇다고 클라이언트와 프록시 사이의 바깥 연결까지 TLS로 암호화되는 것은 아닙니다. 해당 구간의 프록시 인증을 암호화하려면 공급자와 클라이언트가 명시적으로 지원하는 HTTPS 프록시 엔드포인트를 사용해야 합니다. 접두사만 바꾸면 안 됩니다. RFC 9110: CONNECT, Chromium HTTP/HTTPS 프록시.

대시보드의 인증 정보를 복사하고 사용자 이름에 라우팅 옵션 넣기

BifrostNetwork의 현재 문서는 gate.bifrostnetwork.cc:9521 게이트웨이와 HTTP/HTTPS CONNECT 지원을 안내합니다. 사용자 이름에 국가, 세션 ID, TTL을 추가할 수 있습니다. 기본 사용자 이름은 주문에서 복사하고, 예제를 보고 요금제 코드를 추측하지 마세요. 연결 문서.

기본사용자이름-country-us-session-고유작업ID-ttl-300

-country-us는 미국 출구, -session-…은 고정 세션(sticky session), -ttl-300은 300초를 요청합니다. TTL 생략 시 문서상 기본값은 600초입니다. 지원되지 않는 지역·ASN 조합에는 대체 경로가 적용될 수 있으므로 사용자 이름만으로 실제 국가를 확정할 수 없습니다. 세션 및 위치 옵션.

3. 업무 흐름 하나에 컨텍스트와 프록시 세션 하나 연결하기

상품 목록 → 시장 선택 → 상세 페이지 → 가격 조회는 상태를 공유하는 하나의 흐름입니다. 작업 ID에 다음을 연결하세요.

task_id
  ├─ country + proxy_session_id
  ├─ BrowserContext(쿠키, 사이트 저장소, locale)
  └─ 시작·종료 시각 + 콘텐츠 검증 결과

흐름이 끝나면 컨텍스트를 닫고 다음 독립 작업에 새 세션 ID를 만듭니다. 같은 페이지의 탐색, 스크립트, XHR 사이에서 프록시 설정을 바꾸거나 이전 시장의 쿠키를 유지한 채 출구만 바꾸지 마세요.

이는 작업 분리 전략이지, 새 세션마다 한 번도 사용하지 않은 IP를 받는다는 보장이 아닙니다. 연결 재사용, 가용 노드, 라우팅이 결과에 영향을 줍니다. 출구를 한 번 확인해도 이후 모든 하위 요청이 같은 노드를 지난다고 증명할 수는 없습니다. 긴 흐름은 주요 단계에서 출구 변화를 확인하고 예정된 세션 시간을 넘으면 작업을 다시 예약합니다.

IP 국가, 브라우저 locale, 배송 국가, 통화를 각각 검증해야 합니다. locale="en-US"는 브라우저 언어 관련 동작에 영향을 주지만 미국 IP를 만들거나 사이트의 시장 선택을 대체하지 않습니다. locale 설정.

4. Python 예제: 큐를 확장하기 전에 한 페이지 검증하기

별도 Python 환경에 Playwright와 해당 버전의 Chromium을 설치합니다. 운영에서는 Python, Playwright, 브라우저 버전을 기록하고 고정해 회귀 검증을 재현할 수 있게 하세요. 설치 문서.

python -m pip install playwright
python -m playwright install chromium

로컬 환경이나 비밀 관리 서비스로 다음 변수를 주입합니다. 비밀번호를 저장소나 공유 셸 기록에 남기지 마세요.

변수내용
BIFROST_BASE_USERNAME대시보드의 기본 사용자 이름. 아래에서 추가하는 라우팅 옵션은 제외
BIFROST_PASSWORD프록시 비밀번호
TARGET_URL수집이 허용됨을 확인한 HTTPS 페이지
READY_SELECTOR업무 요소 하나만 찾는 CSS 선택자
EXPECTED_TEXT해당 요소에 포함되어야 하는 비어 있지 않은 텍스트
BIFROST_PROXY_SERVER선택 사항. 기본값 http://gate.bifrostnetwork.cc:9521

scraper_proxy.py로 저장한 뒤 python scraper_proxy.py를 실행합니다. 페이지를 한 번 방문하며 인증, HTTP, 콘텐츠 오류는 상위 스케줄러로 전달합니다. 자동 IP 변경이나 암묵적 재시도는 없습니다.

import asyncio
import json
import os
import time
import uuid
from urllib.parse import urlsplit

from playwright.async_api import async_playwright, expect


async def main():
    target = os.environ["TARGET_URL"]
    selector = os.environ["READY_SELECTOR"]
    expected = os.environ["EXPECTED_TEXT"].strip()
    parsed = urlsplit(target)
    if parsed.scheme != "https" or not parsed.hostname or not expected:
        raise ValueError("유효한 HTTPS 주소와 비어 있지 않은 예상 텍스트가 필요합니다")

    task_id = uuid.uuid4().hex[:16]
    base = os.environ["BIFROST_BASE_USERNAME"]
    proxy = {
        "server": os.environ.get(
            "BIFROST_PROXY_SERVER", "http://gate.bifrostnetwork.cc:9521"
        ),
        "username": f"{base}-country-us-session-{task_id}-ttl-300",
        "password": os.environ["BIFROST_PASSWORD"],
    }

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            context = await browser.new_context(proxy=proxy, locale="en-US")
            try:
                page = await context.new_page()
                started = time.monotonic()
                response = await page.goto(
                    target, wait_until="domcontentloaded", timeout=30_000
                )
                if response is None:
                    raise RuntimeError("탐색에서 주 문서 응답을 반환하지 않았습니다")
                if not 200 <= response.status < 300:
                    # 상위 스케줄러에서 기록하고 해석합니다. 여기서 즉시 재시도하지 않습니다.
                    raise RuntimeError(json.dumps({
                        "task_id": task_id,
                        "status": response.status,
                        "retry_after": response.headers.get("retry-after"),
                    }))

                ready = page.locator(selector)
                await expect(ready).to_have_count(1, timeout=10_000)
                await expect(ready).to_be_visible(timeout=10_000)
                await expect(ready).to_contain_text(expected, timeout=10_000)
                print(json.dumps({
                    "task_id": task_id,
                    "status": response.status,
                    "elapsed_ms": round((time.monotonic() - started) * 1000),
                    "content_check": "passed",
                }))
            finally:
                await context.close()
        finally:
            await browser.close()


if __name__ == "__main__":
    asyncio.run(main())

선택자와 예상 텍스트는 대상 페이지에 맞게 정해야 합니다. 모든 쇼핑몰에 통하는 선택자는 없습니다. 운영 버전은 최종 URL, 상품 ID, 가격, 통화, 시장, 수집 시각을 확인하고 업무 키로 중복을 제거해야 합니다. 예제는 명확하게 실패할 수 있는 준비 조건만 보여 줍니다. 빈 선택자, 다중 일치, 선택자 타임아웃을 모두 프록시 장애로 분류하지 마세요. Locator assertions는 제한 시간 안에 조건을 반복 확인합니다. Locator assertions.

page.goto()는 404, 500 같은 유효한 HTTP 상태에서 자동으로 예외를 발생시키지 않으므로 응답을 검사해야 합니다. 반환값은 주 문서 응답이며 이후 상품 API의 성공까지 의미하지 않습니다. domcontentloaded는 DOM 이벤트만 나타내므로 데이터 준비 여부는 별도 단언으로 확인합니다. 공식 문서는 networkidle을 권장하지 않습니다. 네트워크가 조용하다고 데이터가 완전한 것은 아닙니다. Page.goto.

5. 오류별로 재시도하기: 429에서 즉시 IP를 바꾸지 않기

증상먼저 확인할 항목스케줄러 대응
407 또는 프록시 인증 예외사용자 이름, 비밀번호, 프로토콜, 요금제 상태해당 설정을 중단하고 인증 정보 수정
CONNECT 실패나 연결 타임아웃게이트웨이 접근성, 프로토콜, 출구와 대상 연결계층별 진단 후 일시 장애만 제한적으로 재시도
401 / 403대상 인증, 접근 정책, 응답 내용접근 조건 확인, 무한 재시도 방지
429대상의 제한과 적용 범위Retry-After 해석 후 해당 작업 부하 감소
502 / 503 / 504게이트웨이 또는 대상 중 발생 위치, 일시 여부출처 단서를 기록하고 예산 내에서 대기
200이지만 필드 누락 또는 지역 오류페이지 상태, API, 파서, 시장 설정콘텐츠 실패로 분류하고 수정 후 재실행

401, 403, 407은 각각 대상 인증, 처리 거부, 프록시 인증과 관련됩니다. 브라우저 터널 생성 실패는 페이지 응답 대신 예외로 나타날 수 있습니다. RFC 9110 상태 코드.

RFC 6585는 IP만으로 요청을 집계하도록 요구하지 않습니다. 계정, 쿠키, 리소스를 기준으로 제한할 수도 있습니다. 429 이후 IP를 바꿔도 제한이 해제되지 않을 수 있으며 대상에 대한 전체 시스템 부하를 제어하지도 못합니다. RFC 6585 제4절.

Retry-After는 음이 아닌 정수 초 또는 HTTP 날짜입니다. 두 형식을 모두 지원하고, 날짜는 현재 UTC와의 차이로 대기 시간을 계산하며 시계 오차를 고려합니다. RFC 9110: Retry-After.

대상 규칙과 작업 예산에 맞춰 재시도를 제한하세요.

해석 가능한 Retry-After 없음: 무작위 지터를 더한 지수 백오프
유효한 Retry-After 있음: 지정 시간 이상 기다린 뒤 작은 지터 추가
대기가 남은 작업 예산을 초과함: 연기 또는 종료. 대기 시간을 줄이지 않음
최대 시도 횟수 도달: 오류 분류를 보존하고 실패 큐로 이동

최소한 대상 도메인별로 제한을 합산하고, 계정 할당량이 있으면 도메인 사이에서도 계정 예산을 공유합니다. worker마다 동시성이 낮아도 합산 요청은 제한을 넘을 수 있으므로 대기 상태를 공유해야 합니다. 한 번의 탐색은 스크립트, 이미지, API 요청을 만들기에 페이지 작업 수와 HTTP 요청 수는 다릅니다.

Scrapy의 현재 RetryMiddleware 기본 재시도 목록에는 429가 포함됩니다. 하지만 재시도 대상이라는 사실만으로 서버 지정 대기 정책이 구현되지는 않습니다. AutoThrottle은 지연을 참고하며 빠른 비 200 응답 때문에 대기 시간이 감소하지 않게 합니다. 실제 설정에서 대기와 재시도 동작을 검증하세요. RetryMiddleware, AutoThrottle.

6. 리소스 차단 전 정상 로딩 기준값 측정하기

필드와 무관한 이미지·미디어가 많더라도 HTML 외 요청을 전부 차단하면 페이지가 깨질 수 있습니다. 정상 로딩을 먼저 측정하고 불필요함을 확인한 자원만 실험적으로 막아 필드 완전성, 작업 시간, 청구 트래픽을 비교합니다.

context.route()를 활성화하면 HTTP 캐시가 비활성화됩니다. Service Worker가 처리하는 요청은 해당 가로채기를 우회할 수 있습니다. 공식 문서는 service_workers="block"을 고려하도록 권장하지만 Service Worker에 의존하는 페이지 동작이 달라집니다. 여러 페이지에서 캐시를 재사용하던 작업이라면 적용 후 총비용을 다시 측정해야 합니다. BrowserContext.route.

요청 완료 후 request.sizes()로 큰 자원을 조사할 수 있습니다. responseBodySize는 인코딩된 응답 본문의 바이트 수입니다. 프록시 청구량과 같지는 않으므로 프로토콜 오버헤드, 실패 요청, 공급자의 계량 범위를 별도로 확인합니다. Request.sizes.

len(page.content())는 렌더링된 HTML 문자열 길이입니다. 전체 네트워크 바이트나 모든 페이지 자원의 크기가 아닙니다.

7. 고정 표본으로 BifrostNetwork 평가하기

주요 페이지 템플릿과 시장을 포함하는 URL 표본을 고릅니다. 브라우저 버전, 시간 범위, 작업 수, 재시도 예산, 필드 규칙을 고정하세요. 표본 크기는 페이지 다양성과 변동성에 따라 결정합니다. 다음은 평가 방법이며 서비스 성능 보장이 아닙니다.

지표기록 방법확인할 수 있는 것
출구와 시장 일치실제 IP, 위치 판정 출처, 페이지 국가·통화목표 시장의 데이터인지
콘텐츠 통과율합격 작업 ÷ 전체 작업HTTP 성공 외에 실제 사용 가능한 비율
세션 변화주요 단계의 출구와 사이트 상태긴 흐름의 조건 일관성
지연 분포성공 작업 P50/P95, 실패·타임아웃 별도수집 마감 충족 여부
재시도 증폭총 시도 횟수 ÷ 작업 수불안정성으로 늘어난 작업량
유효 1,000건당 비용총비용 ÷ 합격·중복 제거 기록 수 × 1,000확장 시 경제성

총비용에는 프록시 청구액, 브라우저 컴퓨팅, 귀속 가능한 유지보수를 포함합니다. 유효 기록이 0이면 단위 비용은 계산할 수 없으며 해당 시험은 실패입니다. 요금제는 같은 기준으로 비교하고 가장 좋은 한 번의 결과를 평균으로 제시하지 마세요.

기존 Playwright 파이프라인에는 proxy 설정으로 BifrostNetwork를 연결합니다. 대시보드에서 인증 정보를 얻고 개발자 문서의 국가·세션 옵션으로 작업을 구성한 뒤 현재 가격과 시험 청구량으로 예산을 계산하세요. 이 글은 측정하지 않은 처리량을 제시하지 않으며 동적 주거용 고정 세션을 전용 정적 IP SLA로 간주하지 않습니다.

운영 전 대상 API, 접근 규칙, 허용 빈도를 확인합니다. robots.txt는 크롤러 규칙이며 RFC 9309는 접근 권한 자체가 아니라고 명시합니다. 예제는 robots 규칙을 자동 조회하거나 적용하지 않으므로 작업 접수 단계에서 처리해야 합니다. RFC 9309.

자주 묻는 질문

미국 프록시인데 다른 통화가 나오는 이유는 무엇인가요?

실제 출구, 배송 국가, 쿠키, 계정 시장, 반환 데이터를 확인하세요. IP 위치는 여러 입력 중 하나이며 BifrostNetwork가 문서화한 대체 라우팅도 검증에 포함합니다.

요청마다 IP를 바꾸면 더 안정적인가요?

연결된 탐색과 XHR 흐름에서 출구를 자주 바꾸면 상태 불일치를 추적하기 어려워집니다. 업무 작업을 세션 경계로 삼고 다음 독립 작업에서 교체가 필요할 때 새 컨텍스트와 세션을 만드세요.

HTTP 200이 왜 수집 성공을 뜻하지 않나요?

주 문서가 빈 틀이거나, 상품 API가 실패하거나, 로그인·오류 화면일 수 있습니다. 필드, 시장, 시각, 중복 제거 규칙을 모두 통과한 데이터만 집계합니다.

예제를 바로 수백 개 동시 작업으로 늘려도 되나요?

먼저 작업별 브라우저 자원과 대상 부하를 측정하고 제한된 큐, 공유 대기, 실패 예산을 추가하세요. 동시성은 시스템 용량 제약이며 worker 증가는 대상의 허용 요청 빈도를 대체하지 않습니다.