차단당하지 않고 Puppeteer에 로테이팅 프록시 설정하기

최종 업데이트: August 10, 2026
Hand-drawn diagram of a Puppeteer-controlled browser rotating requests through multiple proxy nodes
AI 요약
- Puppeteer에서 프록시를 브라우저 재실행형, 게이트웨이 관리형, 브라우저 풀 분리형, 샤딩형으로 나누어 실전 구조를 비교하세요. - 프록시 자격 증명이 어디에 들어가야 하는지, HTTPS CONNECT가 요청 경로를 어떻게 바꾸는지, 페이지 단위 인증만으로는 왜 브라우저 실행이나 터널 실패를 못 푸는지 이해하세요. - 무작정 오류가 날 때마다 회전시키는 대신, 제한 재시도, 쿨다운, 사유 코드가 붙은 실패 처리, 세션 일관성을 바탕으로 상태 인지형 선택 로직을 구성하세요. - 403, 407, 429, 네비게이션 타임아웃, DNS, TLS 오류는 경로를 바꾸기 전에 어느 계층에서 발생했는지 먼저 파악해 진단하세요. - 관측성, 비밀값 관리, 동시성 제한, 정상 종료, 정책을 고려한 실패 차단 전략을 포함한 프로덕션 체크리스트를 사용하세요.

Puppeteer에서 흔히 겪는 실패 패턴은 초반 요청은 잘 되다가, 뒤로 갈수록 403이나 429가 뜨고, 타임아웃이 나거나, 챌린지 페이지로 넘어가는 경우입니다. 그런데도 많은 가이드는 로테이팅 프록시를 마치 두 줄짜리 설정만 바꾸면 끝나는 일처럼 설명하곤 합니다.

현실은 전혀 다릅니다. --proxy-server 플래그 하나 붙이면 끝난다는 예제와, 실제로 유지보수 가능한 시스템을 만드는 일 사이에는 엄청난 차이가 있습니다. 이 가이드에서는 브라우저 전체 단위 로테이션, 인증 게이트웨이, 브라우저 샤딩 또는 외부 릴레이, 일관된 브라우저 프로필, 프로덕션 수준의 오류 처리, 그리고 프록시를 아예 직접 관리하지 않는 편이 더 나은 경우까지 솔직하게 다룹니다.

로테이팅 프록시란 무엇이고, Puppeteer에 왜 필요한가?

프록시는 Puppeteer 인스턴스와 대상 사이트 사이에 끼어 있는 중간 경로입니다. 대상 사이트는 내 기기 IP가 아니라 프록시의 종료 IP를 보게 됩니다. 로테이팅 프록시는 이런 종료 IP들을 풀 단위로 바꿔 가며 쓰는 방식입니다. 요청마다 바꾸기도 하고, 세션마다 바꾸기도 해서, 트래픽이 한 클라이언트가 서버를 계속 두드리는 것처럼 보이지 않게 합니다.

Puppeteer에서 특히 중요한 이유는, 헤드리스 Chrome이 하나의 IP에서 수백 건의 연속 요청을 보내는 패턴이 바로 봇 탐지 시스템이 잡아내도록 설계된 대표적 형태이기 때문입니다. Cloudflare의 공식 문서도 휴리스틱, JavaScript 지문 검사, 머신러닝 모델, 행동 이상 탐지 등 여러 탐지 계층이 동시에 작동한다고 설명합니다. IP를 바꾸는 건 그중 딱 한 계층만 건드리는 일입니다. 그것도 하나뿐입니다.

알아두면 좋은 프록시 유형은 세 가지입니다. 서로 대체 가능한 개념은 아닙니다.

  • 데이터센터 프록시 — 저렴하고 빠르며 호스팅 업체에서 제공됩니다. ASN(네트워크 대역)만 봐도 데이터센터라는 사실이 너무 뚜렷해서, 차단 대상이 알아채기 쉽습니다.
  • 주거용 프록시 — 실제 소비자 ISP를 통해 라우팅되므로 일반 가정용 인터넷처럼 보입니다. 더 느리고 비싸지만 훨씬 자연스럽습니다.
  • 모바일 프록시 — 이동통신망 IP로, 보통 가장 비싼 옵션이며 모바일 네트워크 정체성이 꼭 필요한 상황에서 유용합니다.

주거용 종료 IP는 데이터센터 종료 IP보다 ASN 분류만으로는 덜 눈에 띌 수 있지만, 두 종류 모두 차단에서 자유롭지 않습니다. 탐지율에 절대적인 정답은 없습니다. 대상 사이트, 종료 IP 평판, 위치, 세션 이력, 브라우저 프로필, 요청 패턴에 따라 결과는 달라집니다.

헷갈리기 쉬운 또 하나의 차이는, 내가 직접 관리하는 정적 목록(풀을 직접 관리하고, 다음 IP를 고르고, 실패도 처리함)과 백커넥트/게이트웨이 프록시(하나의 엔드포인트에만 접속하면 공급자가 뒤에서 종료 IP를 바꿔 줌)입니다. 둘 다 유효하지만, 복잡성이 어디에 생기느냐가 다를 뿐입니다.

왜 Puppeteer에서 로테이팅 프록시를 설정할까? 흔한 사용 사례

솔직히 말하면, 정말 필요해지기 전까지는 로테이션이 없어도 됩니다. 하지만 한 번 필요해지면, 아주 절실해집니다.

사용 사례로테이션이 중요한 이유
상품 카탈로그 가격 모니터링같은 IP에서 반복 조회하면 속도 제한과 평판 신호가 누적될 수 있음
리드 보강 / 연락처 데이터 추출같은 IP에서 프로필을 계속 보면 일반 브라우징이 아니라 스크래핑처럼 보여 봇 엔진에 걸리기 쉬움
SERP 스크래핑검색 엔진은 IP 기반 제한과 CAPTCHA 차단을 가장 적극적으로 적용하는 편
경쟁사 인텔리전스며칠 동안 같은 도메인을 반복 수집하면 IP와 쿠키 이력에 묶인 지문이 쌓임
콘텐츠 집계페이지당 가치는 낮은데 페이지 수는 많은 형태로, 봇 탐지가 특히 잘 잡아내는 트래픽 패턴

"Amazon은 51번째 요청에서 막는다" 같은 식의 고정된 근거 있는 숫자는 없습니다. 사이트들은 보편적인 임계값을 공개하지 않으며, 제어 방식도 엔드포인트, 계정 상태, ASN 평판, 트래픽 모양에 따라 바뀔 수 있습니다. 가장 낮은 허용 요청률부터 시작하고, 상태 코드뿐 아니라 콘텐츠도 검증하고, 측정된 동작과 대상 사이트의 정책이 필요성을 뒷받침할 때만 로테이션을 추가하세요.

Puppeteer에서 쓰는 3가지 프록시 로테이션 전략: 어떤 방식이 맞을까?

Three Puppeteer proxy strategies compared: browser relaunch, rotating gateway, and browser sharding

대부분의 튜토리얼이 이 부분을 통째로 건너뛰거나, 있어도 가장 거친 방식만 보여줍니다. 사실은 세 가지 세분화 수준이 있고, 잘못 고르면 시간을 낭비하거나 단순한 작업을 불필요하게 복잡하게 만듭니다.

로테이션 전략세분화 수준브라우저 재시작 필요?복잡도적합한 경우
브라우저별(--proxy-server)브라우저 인스턴스당 프록시 1개낮음단순하고 소규모인 스크래핑
게이트웨이 관리형(proxy-chain + 백커넥트 엔드포인트)공급자/세션 정책 기준아니요중간인증된 로테이팅 게이트웨이
브라우저 샤딩 또는 외부 릴레이브라우저 샤드 또는 릴레이 규칙당 프록시 1개단일 프로세스 내 교체 아님높음제어된 동시성과 세밀한 라우팅

선택하기 전에 짚고 갈 점이 하나 있습니다. Puppeteer의 네트워크 인터셉션 문서setRequestInterception이 요청마다 프록시를 깔끔하게 바꾸는 스위치가 아니라고 명확히 설명합니다. 인터셉트된 모든 요청은 사용자가 명시적으로 계속 진행, 응답, 또는 중단시키기 전까지 멈춥니다. 진짜 요청 단위 프록시 라우팅은 보통 interception 핸들러 안에서 프록시를 직접 바꾸는 방식이 아니라, proxy-chain 같은 로컬 프로그래머블 게이트웨이를 경유시키는 방식으로 구현합니다. 아래 Method 3를 선택하기 전에 이 점을 꼭 기억하세요.

Puppeteer에서 로테이팅 프록시 설정하기: 단계별 가이드

난이도: 중급
소요 시간: 세 가지 방법 전체 기준 약 30~45분
준비물: Node.js 18+, npm, 프록시 목록 또는 공급자 계정(형식: protocol://user:pass@host:port), 그리고 puppeteer, proxy-chain, puppeteer-extra 패키지

시작 전 준비사항

핵심 패키지를 설치합니다:

npm install puppeteer proxy-chain puppeteer-extra puppeteer-extra-plugin-stealth

공급자로부터 프록시 목록을 받거나(캐주얼 테스트를 넘어서면 주거용 프록시가 권장됨), 최소한 몇 개의 테스트 프록시를 준비해 실제 요청량을 쓰기 전에 코드가 제대로 동작하는지 검증하세요. 자격 증명은 환경 변수에 저장하고, 절대 하드코딩하지 마세요. 그리고 로그 파일에 남을 수 있는 URL에도 넣지 마세요.

방법 1: --proxy-server를 이용한 브라우저별 프록시 로테이션

이 방식은 대부분이 처음 시작하는 기본 방법이고, 그만한 이유가 있습니다. 예측 가능하기 때문입니다. Puppeteer의 LaunchOptions는 Chrome 명령행 플래그를 넘길 때 args를 사용하도록 문서화하고 있고, --proxy-server는 Chromium의 네이티브 플래그입니다.

import puppeteer from 'puppeteer';

const proxyPool = [
  'http://proxy1.example:8080',
  'http://proxy2.example:8080',
  'http://proxy3.example:8080',
];

let proxyIndex = 0;

async function scrapeWithRotation(url) {
  const proxy = proxyPool[proxyIndex % proxyPool.length];
  proxyIndex++;

  const browser = await puppeteer.launch({
    headless: true,
    args: [`--proxy-server=${proxy}`],
  });

  const page = await browser.newPage();

  // 프록시에 인증이 필요하면, 어떤 탐색보다 먼저 실행해야 합니다
  await page.authenticate({
    username: process.env.PROXY_USER,
    password: process.env.PROXY_PASS,
  });

  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
  const content = await page.content();

  await browser.close(); // 프록시를 바꾸기 전에 닫아야 함
  return content;
}

page.authenticate()는 Puppeteer 문서에 따르면 내부적으로 요청 인터셉션을 조용히 활성화합니다. 약간의 성능 비용이 있지만, 왜 예상보다 느리게 느껴지는지 디버깅할 때 알아두면 좋습니다.

예상 결과: 호출할 때마다 다른 프록시에 연결된 새 브라우저가 실행됩니다. 로테이션하려면 닫고 다시 띄워야 하므로 시작 오버헤드를 피할 수 없습니다. 50페이지를 수집한다면, 브라우저 부팅 시간만으로도 다른 두 방법보다 체감상 확실히 느릴 수 있습니다.

언제 쓰면 좋나: 동시성이 낮은 스크립트, 일회성 수집, 속도보다 디버깅 단순성이 더 중요한 경우.

방법 2: proxy-chain을 이용한 인증형 로테이팅 게이트웨이

Chrome은 프록시 URL에 user:pass@host 형식의 자격 증명을 직접 넣는 방식을 받지 않습니다. proxy-chain 패키지(Apify 유지 관리)는 이 인증 문제를 해결하기 위해 로컬 익명 프록시를 띄워 인증된 상위 프록시로 전달합니다. 상위 프록시가 공급자의 로테이팅 또는 백커넥트 게이트웨이라면, 공급자가 세션 정책에 따라 그 하나의 엔드포인트 뒤에서 종료 IP를 바꿉니다. proxy-chain 자체가 기존 Puppeteer 페이지마다 다른 프록시를 할당해 주는 것은 아닙니다.

import puppeteer from 'puppeteer';
import { anonymizeProxy, closeAnonymizedProxy } from 'proxy-chain';

async function scrapeThroughGateway(upstreamProxyUrl, targetUrl) {
  const localProxy = await anonymizeProxy(upstreamProxyUrl);
  let browser;

  try {
    browser = await puppeteer.launch({
      headless: true,
      args: [`--proxy-server=${localProxy}`],
    });
    const page = await browser.newPage();
    await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
    return await page.content();
  } finally {
    if (browser) await browser.close();
    await closeAnonymizedProxy(localProxy, true); // 항상 정리해야 함
  }
}

finally 블록은 장식이 아닙니다. 로컬 프록시 서버를 닫지 않으면 포트가 누수되고, 실제로 아무도 익명 프록시를 닫지 않아서 밤새 파일 디스크립터를 소진한 스크래퍼를 본 적도 있습니다. proxy-chain은 DNS 문제에 593, 연결 거부에 594, 인증 실패에 597 같은 구체적인 오류 코드도 제공하는데, 실패를 분류하는 데 정말 유용합니다. 이 부분은 뒤에서 더 다룹니다.

언제 쓰면 좋나: 공급자의 엔드포인트나 세션 파라미터가 로테이션을 제어하는 인증형 주거용/데이터센터 게이트웨이. 여러 개의 고정 프록시 정체성을 동시에 써야 한다면, 별도의 브라우저 프로세스(브라우저 샤딩)나 목적 특화 외부 릴레이를 쓰세요. 네이티브 Puppeteer에는 페이지별 프록시를 지정하는 지원 API가 없습니다.

방법 3: 요청 단위 라우팅에는 외부 릴레이가 필요하다

이것은 가장 세밀한 옵션입니다. 이론적으로는 페이지의 이미지, 스크립트, API 호출 하나하나가 서로 다른 종료 IP를 통해 나갈 수 있습니다. 하지만 실제로는 가장 취약하고 문서화도 가장 부족한 방식입니다. Puppeteer의 요청 인터셉션은 네트워크 전송 경로를 요청마다 바꾸려고 만든 것이 아니라, 요청을 필터링하거나 수정하기 위해 설계됐기 때문입니다.

import puppeteer from 'puppeteer';

async function inspectRequests(url) {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.setRequestInterception(true);

  page.on('request', async (request) => {
    // 실제로 요청마다 프록시를 바꾸려면
    // 브라우저 전송 경로를 중간에 바꾸는 것이 아니라
    // 로컬 릴레이(proxy-chain)를 경유해야 합니다 — Chrome은 이를 직접 지원하지 않습니다.
    // 실제 운영 환경에서는 보통 이 핸들러로 리소스 타입을
    // 필터링하거나 중단하고, 브라우저 샤딩이나 게이트웨이와 함께 씁니다.
    if (['image', 'font', 'stylesheet'].includes(request.resourceType())) {
      request.abort();
    } else {
      request.continue();
    }
  });

  await page.goto(url, { waitUntil: 'networkidle2' });
  await browser.close();
}

솔직한 결론: Puppeteer 안에서 요청마다 IP를 바꾸는 진짜 기능은 setRequestInterception()으로 제공되지 않습니다. 정말 그 정도의 세밀함이 필요하다면, Chrome을 프로그래머블 외부 릴레이를 통해 라우팅하거나 프록시 세션 중심으로 설계된 스크래핑 프레임워크를 쓰세요. 대부분의 프로젝트에서는 브라우저 샤드마다 하나의 프록시를 쓰거나, 공급자 관리형 로테이팅 게이트웨이를 사용하는 편이 운영과 감사 측면에서 더 쉽습니다.

전체 안티-탐지 스택: 프록시만으로는 차단을 막을 수 없다

자주 나오는 불만은 이겁니다. "프록시를 쓰고 있는데도 여전히 차단됩니다." 하지만 IP 주소는 현대 봇 시스템이 평가하는 여러 신호 중 하나일 뿐입니다. IP만 바꾸고 나머지가 일관되지 않으면 오히려 더 강한 이상 신호가 될 수 있습니다. 예를 들어, Windows Chrome이라고 주장하면서 client hints, 시간대, 로케일이 전혀 다르면 금방 눈에 띕니다.

레이어 1: 로테이팅 주거용 프록시

앞서 설명했듯이 주거용 종료 IP는 데이터센터 종료 IP보다 더 자연스러워 보이는 경우가 많지만, 최소 풀 크기에 대한 보편적인 기준은 없습니다. 임의의 IP 개수를 정하지 말고, 측정된 요청량, 세션 길이, 쿨다운, 공급자의 재사용 방식에 맞춰 풀 크기를 정하세요.

레이어 2: 헤드리스 Chrome 신호를 감추는 스텔스 플러그인

puppeteer-extra-plugin-stealthnavigator.webdriver, WebGL 벤더 문자열, 누락된 Chrome 런타임 객체, 그리고 몇 가지 다른 CDP 누출 신호를 패치합니다. 분명히 유용한 호환성 레이어입니다. 하지만 프로젝트의 README도 이것이 고양이와 쥐의 게임이며 완전한 차단 방지는 아마 불가능하다고 아주 솔직하게 말합니다. 보증이 아니라 기본값으로 생각하세요.

레이어 3: 일관된 브라우저 프로필과 책임 있는 속도 조절

User-Agent 문자열은 브라우저가 보고하는 다른 정보와 내부적으로 맞아야 합니다. Chrome의 User-Agent Client Hints는 구조화된 플랫폼 데이터를 노출하므로, 사람이 직접 쓴 user-agent 문자열이 실제 플랫폼과 충돌할 수 있습니다. 번들된 Chrome 빌드가 제공하는 user agent를 우선 사용하고, 세션 내에서는 viewport/locale/timezone을 안정적으로 유지하며, 매 페이지마다 새로운 지문을 만들어내기보다 보수적으로 요청 속도를 조절하세요.

세 레이어를 하나의 실행 설정으로 묶으면 다음과 같습니다:

import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
import { anonymizeProxy, closeAnonymizedProxy } from 'proxy-chain';

puppeteer.use(StealthPlugin());

function boundedDelay(minMs = 800, maxMs = 1800) {
  return new Promise((r) => setTimeout(r, minMs + Math.random() * (maxMs - minMs)));
}

async function stableProfileScrape(targetUrl, upstreamProxy) {
  const localProxy = await anonymizeProxy(upstreamProxy);
  let browser;

  try {
    browser = await puppeteer.launch({
      headless: true,
      args: [`--proxy-server=${localProxy}`, '--lang=en-US'],
    });
    const page = await browser.newPage();
    await page.setViewport({ width: 1366, height: 768 });
    await boundedDelay();
    await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
    return await page.content();
  } finally {
    if (browser) await browser.close();
    await closeAnonymizedProxy(localProxy, true);
  }
}

이 블록은 경쟁 가이드들이 거의 보여주지 않는 부분입니다. 프록시, 스텔스, 지문 랜덤화를 한곳에 모아 바로 복사해 수정할 수 있게 만든 예시죠.

프로덕션급 오류 처리와 프록시 상태 점검

Proxy pool health state machine for 403, 407, 429, timeout, cooldown, and quarantine handling

대부분의 튜토리얼은 성공 경로가 한 번 돌아가면 바로 끝납니다. 하지만 실제 스크래핑은 계속 실패합니다. 프록시가 죽고, 자격 증명이 만료되고, 대상 사이트가 중간에 속도 제한을 걸고, 그런 일은 "잘 되겠지"라고 기대한다고 해결되지 않습니다.

지수 백오프와 지터를 넣은 재시도 로직

function backoffMs(attempt, base = 1000, cap = 30_000) {
  const exponential = Math.min(cap, base * 2 ** attempt);
  return Math.floor(exponential * (0.5 + Math.random() * 0.5)); // 지터는 동시 재시도 폭주를 막음
}

async function withRetry(fn, maxRetries = 4) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries) throw err;
      const delay = backoffMs(attempt);
      console.warn(`시도 ${attempt + 1} 실패: ${err.message}. ${delay}ms 후 재시도합니다.`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

실패한 프록시 자동 블랙리스트 처리

const proxyStats = new Map(); // proxyUrl -> { success, failure }

function recordResult(proxyUrl, success) {
  const stats = proxyStats.get(proxyUrl) || { success: 0, failure: 0 };
  success ? stats.success++ : stats.failure++;
  proxyStats.set(proxyUrl, stats);
}

function isHealthy(proxyUrl) {
  const stats = proxyStats.get(proxyUrl);
  if (!stats) return true;
  const total = stats.success + stats.failure;
  if (total < 5) return true; // 아직 데이터가 충분하지 않음
  return stats.failure / total < 0.5; // 실패율이 50%를 넘으면 블랙리스트
}

function getHealthyProxy(pool) {
  const healthy = pool.filter(isHealthy);
  if (healthy.length === 0) throw new Error('풀에 남아 있는 정상 프록시가 없습니다');
  return healthy[Math.floor(Math.random() * healthy.length)];
}

성공/실패만 보지 말고 오류 유형도 추적하세요. 407(잘못된 자격 증명)과 429(속도 제한)는 완전히 다른 대응이 필요합니다. 인증 실패하는 프록시에 빠르게 재시도만 반복하면 시간만 버립니다. 해결책은 더 빨리 로테이션하는 것이 아니라 자격 증명을 확인하는 것입니다.

Puppeteer에서 흔히 보는 로테이팅 프록시 오류 해결하기

오류가능한 원인해결 방법
ERR_PROXY_CONNECTION_FAILED프록시 다운 또는 접근 불가풀에서 제거하고 다음 프록시로 재시도
407 Proxy Authentication Required자격 증명 오류 또는 지원되지 않는 인증 방식page.authenticate() 자격 증명 확인; URL에 넣는 인증에는 proxy-chain 사용
TimeoutError느린 프록시 또는 대상 차단타임아웃 증가; 주거용 프록시로 전환
403 ForbiddenIP 또는 지문이 플래그됨프록시 로테이션 + 스텔스 활성화 + UA 랜덤화
ERR_TUNNEL_CONNECTION_FAILEDHTTPS 터널 문제CONNECT 메서드 지원 확인; proxy-chain 로컬 터널 시도

표에 깔끔하게 들어가지 않는 점도 몇 가지 있습니다. 200 상태 코드가 성공을 의미하는 것은 아닙니다. 소프트 블록은 로그인 벽이나 챌린지 화면 같은 완전한 HTML 페이지를 정상 상태 코드로 반환하는 경우가 많으므로, 응답 상태만 보지 말고 실제 콘텐츠를 검증해야 합니다. 막혔을 때는 Puppeteer 디버깅 가이드에서 권장하듯 headless: false로 실행하고, slowMo를 추가하고, NODE_DEBUG="puppeteer:*"를 설정해 상세한 프로토콜 로그를 보세요. 다만 이 로그에는 민감한 요청 데이터가 포함될 수 있으니, 운영 자격 증명으로 실행한 상태에서 계속 켜 두지는 마세요.

직접 관리하는 프록시 로테이션 vs 프록시 게이트웨이 vs AI 추출 API

기준직접 관리하는 목록 로테이션백커넥트 게이트웨이 (Bright Data, Oxylabs, Decodo)AI 추출 API (Thunderbit)
비용(소량 사용)낮음~중간GB당 중간~높음낮음(무료 티어 후 사용량 과금)
안정성상태 점검에 따라 다름높음(공급자 관리)높음(관리형 인프라)
안티탐지직접 구축부분적(IP 로테이션만)내장됨
구조화 출력아님(원시 HTML)아님(원시 HTML)예(JSON 스키마 기반)
설정 시간몇 시간몇 분몇 분
제어 수준완전공급자 API 범위 내스키마 모델 범위 내

2026-08-07 기준으로 확인한 주요 벤더 가격은 게이트웨이 옵션의 비용 곡선을 보여 줍니다. Bright Data의 주거용 가격은 프로모션이 바뀔 수 있는 종량제와 볼륨 플랜을 제공하고, Oxylabs는 5GB에서 GB당 6달러, 1TB에서 GB당 2.50달러를 보여 줍니다. Decodo (구 Smartproxy)는 3GB에서 GB당 3.75달러, 100GB에서 GB당 2.75달러, 종량제는 GB당 4달러를 제시합니다. Decodo는 또한 1억 1,500만+ IP 풀과 99.92% 성공률을 광고하지만, 이는 공급자 주장일 뿐 독립 재현 벤치마크는 아닙니다.

제가 실제로 쓰는 의사결정 기준은 이렇습니다. 페이지와 상호작용해야 하나요? 클릭, 스크롤, 폼 작성, 로그인 세션 유지가 필요하나요? 그렇다면 Puppeteer와 프록시로 만드세요. 이미 페이지에 있는 데이터만 필요하나요? 그러면 평생 유지관리할 프록시 인프라를 만들기 전에 추출 API를 먼저 검토하세요.

Puppeteer + 프록시가 과한 경우: API로 구조화 데이터만 추출하기

어느 프로젝트에서 상품 가격만 스프레드시트에 넣으면 됐는데 세 번째로 프록시 상태 점검 시스템을 다시 만들고 나서야 깨달았습니다. 이런 인프라 대부분은 사실 개발자가 진짜 원하는 것, 즉 페이지의 원시 HTML을 가져오는 문제를 풀기 위해 존재합니다. 그러나 진짜 목표는 구조화된 데이터입니다. HTML은 그저 번거로운 중간 형식일 뿐입니다.

Thunderbit의 Open API는 브라우저 자동화의 부작용으로 추출을 처리하는 대신, 추출 자체를 주요 기능으로 다룹니다. POST /extract는 URL과 JSON Schema를 받아, 일치하는 구조화 데이터를 반환합니다. JS 렌더링, 봇 방어, CAPTCHA 처리는 백엔드에서 처리하므로, 사용자가 직접 스텔스 플러그인과 프록시 풀을 엮을 필요가 없습니다.

curl -X POST https://openapi.thunderbit.com/openapi/v1/extract \
  -H "Authorization: Bearer $THUNDERBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/product",
    "schema": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "price": {"type": "number"}
      },
      "required": ["name", "price"]
    }
  }'

정확한 스키마가 아니라 깔끔한 Markdown만 필요할 때 쓰는 POST /distill 엔드포인트도 있고, 여러 URL에 같은 스키마를 한 번에 적용하는 배치 추출도 지원합니다. Thunderbit의 현재 API 가격을 보면 Distill은 페이지당 1유닛, Extract는 페이지당 20유닛이며, 무료 티어에는 일회성 600유닛이 포함되어 있어 실제 도입 전에 워크플로를 시험해 볼 수 있습니다.

Claude, Cursor, 또는 다른 MCP 호환 클라이언트 안에서 작업하는 개발자라면, Thunderbit는 thunderbit_extractthunderbit_distill을 MCP 도구로도 제공합니다. 에이전트가 작업 중간에 페이지에서 데이터를 가져와야 할지 판단할 수 있게 해 주므로, 별도의 스크래핑 단계를 요구하지 않습니다. 다만 MCP 설정을 붙이기 전에 최신 API 레퍼런스를 확인하는 것이 좋습니다. 문서 버전마다 도구 이름과 파라미터가 바뀔 수 있기 때문입니다.

항목Puppeteer + 로테이팅 프록시Thunderbit API
설정 복잡도높음 — 프록시 풀, 로테이션 로직, 스텔스, 재시도 필요낮음 — JSON Schema를 넣는 단일 API 호출
봇 방어 처리수동내장
출력원시 HTML(파싱 필요)스키마에 맞는 구조화 JSON
유지보수높음 — 셀렉터가 깨지고 프록시가 소모됨낮음
적합한 경우커스텀 자동화, 로그인 흐름, 특수 상호작용대규모 데이터 추출

DIY 방식도 공정하게 말하자면, 계정 로그인, 다단계 플로우 클릭, 세션 상태 유지를 요구하는 작업이라면 추출 API가 보통 이를 대체하지는 못합니다. Thunderbit의 FAQ도 인터랙티브 로그인 흐름은 현재 API로 지원되지 않는다고 명확히 밝힙니다. 이런 경우에는 Puppeteer + 프록시가 여전히 우세합니다. 하지만 작업이 "여러 공개 페이지에서 내가 정의한 스키마로 데이터를 가져오는 것"이라면, 직접 프록시 로테이션 스택을 구축하는 건 실제 문제보다 더 어려운 문제를 푸는 셈입니다. 코드를 아예 건너뛰고 싶은 팀이라면 Thunderbit Chrome Extension도 같은 AI 기반 추출을 클릭형 인터페이스로 제공합니다. 노코드 웹 스크래핑과 풀 개발자 세팅 사이에서 고민 중이라면 한 번 볼 만합니다.

결론과 핵심 요약

Puppeteer에서 로테이팅 프록시는 하나의 기술이 아닙니다. 브라우저별 로테이션은 단순하고 격리성이 좋습니다. 인증형 백커넥트 게이트웨이는 브라우저 전체 엔드포인트 뒤에서 종료 IP를 바꿔 줄 수 있습니다. 세밀한 요청 단위나 동시 정체성은 브라우저 샤딩이나 외부 릴레이가 필요하며, 요청 인터셉션만으로는 Chrome의 네트워크 경로를 바꿀 수 없습니다.

하지만 프록시만으로는 충분하지 않습니다. 프록시는 IP 평판 문제를 해결하고, 스텔스 플러그인과 지문 일관성은 브라우저 신호 문제를 해결하며, 지터와 속도 조절은 행동 패턴 문제를 해결합니다. 어느 한 층이라도 빠지면 여전히 차단될 수 있고, 단지 다른 이유일 뿐입니다.

직접 구현한다면 proxy-chain 저장소와 위의 코드 블록부터 시작하세요. 유료 강의보다 훨씬 멀리 갈 수 있습니다. 반대로 프록시 관리를 아예 건너뛰고 구조화된 데이터만 받고 싶다면, Thunderbit의 API 문서를 보기 전에 주말 내내 유지할 상태 점검 인프라를 만들지 않아도 될지 먼저 10분만 투자해 보세요. 어떤 경로도 정당합니다. 다만 정말 해결해야 할 문제가 무엇인지, 그리고 튜토리얼이 당신의 문제라고 가정하는 것이 무엇인지 구분하는 게 중요합니다. 이 분야에서 AI가 어떤 변화를 만들고 있는지 더 넓게 보고 싶다면, AI 웹 스크래핑 심층 분석과 전통적 방식과의 비교도 확인해 보세요.

자주 묻는 질문

Puppeteer에서 프록시는 얼마나 자주 바꿔야 하나요?

대상 사이트의 속도 제한이 얼마나 공격적인지에 따라 다릅니다. 봇 탐지가 강한 사이트라면 페이지마다 또는 세션마다 바꾸세요. 느슨한 사이트라면 세션 단위나, 전체 실행 동안 하나의 고정 IP를 유지해도 괜찮습니다. 보편적인 숫자는 없습니다. 403, 429, 타임아웃이 나오면 더 적극적으로 바꾸라는 신호로 보세요. 고정된 요청 횟수 규칙이 아닙니다.

Puppeteer 스크래핑에 무료 프록시를 써도 되나요?

기술적으로는 가능하지만, 빠른 테스트를 넘는 용도에는 권하지 않습니다. 무료 프록시 목록은 보통 느리고, 불안정하고, 이미 대상 사이트에서 블랙리스트된 경우가 많습니다. 프로덕션에 쓰려면 유료 공급자의 주거용 프록시나 관리형 게이트웨이를 쓰는 편이 비용 대비 가치가 있습니다.

puppeteer-extra-plugin-stealth는 모든 봇 방어 시스템에 통하나요?

아니요. 플러그인 문서도 그렇게 말합니다. 일부 흔한 헤드리스 Chrome 신호는 줄여 주지만, 대상 사이트는 여전히 네트워크 평판, TLS 특성, 쿠키, client hints, 행동 패턴을 평가할 수 있습니다. 이 플러그인은 보증이 아니라 하나의 호환성 레이어로 보세요.

Puppeteer에서 proxy-chain--proxy-server의 차이는 뭔가요?

--proxy-server는 Chromium의 네이티브 실행 플래그로 브라우저 인스턴스 전체에 하나의 프록시 엔드포인트를 지정합니다. 그리고 Chrome은 여기서 프록시 자격 증명을 직접 받지 않습니다. proxy-chain은 인증된 상위 프록시로 연결되는 로컬 익명 터널을 만듭니다. 이후 로테이션은 다른 상위 프록시로 다시 실행하거나, 공급자 관리형 백커넥트 게이트웨이를 쓰거나, 별도의 외부 릴레이를 직접 설계하는 방식으로 이뤄집니다. proxy-chain이 페이지별로 프록시를 나눠주는 것은 아닙니다.

로테이팅 프록시만 있으면 완전히 차단을 피할 수 있나요?

아니요. 그리고 이건 가장 흔한 오해입니다. Cloudflare의 봇 관리 같은 현대 봇 방어는 IP 평판을 브라우저 지문, 행동 패턴, 세션 이력과 함께 상관 분석합니다. 프록시는 IP 평판 문제만 풀어 줍니다. 다른 신호에서 걸리지 않으려면 스텔스 설정, 일관된 지문, 현실적인 타이밍이 여전히 필요합니다.

더 알아보기

Ke
Ke
Thunderbit CTO | 시니어 데이터 사이언티스트 & ML 전문가 머신러닝과 데이터 과학 분야에서 약 10년에 가까운 경험을 쌓아온 Ke Shen은 컬럼비아 대학교 출신이며, 전 Walmart Labs의 시니어 데이터 사이언티스트였습니다. Python, R, Java, 통계 분야에서 동료들에게도 인정받는 깊은 전문성을 바탕으로, 복잡한 AI 알고리즘을 이론에서 실제 운영 수준의 아키텍처로 전환하는 데 필요한 실전 인사이트를 공유합니다.
Topics
Puppeteer 프록시 로테이션프록시 로테이션브라우저 자동화
목차
Thunderbit · AI 웹 데이터 에이전트

원클릭 내 모든 페이지에서 데이터 추출

25만 명 이상의 사용자에게 신뢰받는
무료 플랜 이용 가능
AI를 사용하여 데이터 추출
Google Sheets, Airtable 또는 Notion으로 데이터를 쉽게 전송하세요
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week