Browserless 리뷰: 필요할 때만 나눠 쓰는 Chrome 서비스

최종 업데이트: August 14, 2026
Browserless 리뷰: 필요할 때만 나눠 쓰는 Chrome 서비스
AI 요약
Browserless는 headless Chrome을 직접 운영하는 서비스 형태로 묶어둔 도구다. Docker 컨테이너가 계속 떠 있으면서 HTTP나 WebSocket으로 작업을 받아 처리하고, ...

Browserless는 헤드리스 Chrome을 서비스 형태로 묶어, 직접 호스팅해서 쓰는 방식입니다. Docker 컨테이너는 계속 켜 둔 상태로 HTTP 또는 WebSocket 요청을 받아 처리하고, 각 호출자에게 따로 브라우저를 붙여 주는 게 아니라 공유된 수용 한도를 적용합니다. 이 문서의 REST 테스트에서는 서비스가 유휴 상태일 때 Chrome 프로세스가 0개였고, 요청이 진행 중일 때는 Chrome 프로세스가 생겼다가, 작업이 끝나면 다시 0개로 돌아갔습니다. 다시 말해, 풀링되는 건 미리 워밍된 브라우저 프로세스 묶음이 아니라 서비스 용량과 대기열입니다.

저는 v2.55.0을 통제된 로컬 fixture에서 검증했습니다. 시작 과정을 단계별로 쪼개 보고, 세 가지 설정에서 admission control을 확인했으며, 알려진 정답과의 endpoint 일치성, 30회 세션 soak 테스트, 그리고 timeout 경계까지 살펴봤습니다. 확인된 유용한 동작은 속도 향상이라기보다 운영 특성이었습니다. admission limit은 클라이언트가 보는 응답과 딱 맞아떨어졌고, 문제 지점들은 대부분 배포 방식에서 비롯된 것이었습니다.

가장 의미 있었던 결과는 지연 시간 숫자가 아니었습니다. Browserless가 Chrome을 더 빨리 띄워 주는 건 아니고, Chrome에 출입 통제 규칙을 붙여 주는 쪽에 가깝습니다. 즉, 안에는 정해진 수의 세션만 들여보내고, 그다음에는 큐를 두며, 나머지는 HTTP 429를 돌려줍니다. 환경 변수 두 개를 바꾸자 이 한도는 4에서 8, 10으로 바뀌었고, 컨테이너 자체의 집계값은 제가 보낸 모든 요청에서 클라이언트의 status code와 정확히 일치했습니다.

Browserless가 실제로 무엇인가

이 카테고리에서 사람들이 자주 헷갈립니다. Browserless는 import해서 호출하는 라이브러리가 아닙니다. ghcr.io/browserless/chromium 이미지로 제공되는 Docker 이미지이며, 장기 실행 서비스로 띄워서 사용합니다. 브라우저 작업을 중개하고, 두 가지 방식으로 노출합니다. REST 엔드포인트(/content, /scrape, /screenshot, /pdf, 그리고 /function, /unblock)와, Puppeteer 및 Playwright가 connect()할 수 있는 CDP/WebSocket 인터페이스입니다.

저는 REST 인터페이스를 테스트했습니다. WebSocket 경로도 실제로 널리 쓰이지만, 이번에는 측정하지 않았습니다.

테스트한 버전은 v2.55.0이며, 확인 시점은 2026년 7월 27일이었습니다.

항목
이미지 버전v2.55.0, 2026년 7월 14일 공개
Chrome149.0.7827.0
Node24.18.0
베이스 이미지Ubuntu 24.04
GitHub star 수2026년 7월 27일 기준 약 13,525개

star 수는 계속 바뀌므로, 특정 시점의 스냅샷으로 봐야 합니다.

라이선스 게이트: SSPL-1.0 또는 상용 라이선스

이 저장소는 Browserless를 SSPL-1.0 또는 Browserless 상용 라이선스로 제공합니다. 경로를 정하기 전에 현재의 저장소 LICENSE와 Browserless의 공식 오픈소스 배포 가이드를 정확히 확인하세요. 이 글은 상용 제품, 폐쇄형 애플리케이션, CI 시스템, 호스팅 서비스, 내부 배포에 대한 법률 검토를 하지 않았으므로, 그런 시나리오에 라이선스를 단정해서 적용하지 않습니다. 배포 방식에 맞는 판단은 법무 담당자나 소프트웨어 라이선스를 책임지는 사람이 내려야 합니다.

Browserless는 호스팅 플랜도 판매합니다. 가격과 사용 단위 정의는 자주 바뀌므로, 이 글의 오래된 표를 조달 증빙처럼 보지 말고 공식 사이트에서 다시 확인해야 합니다.

세션 모델이 내부에서 동작하는 방식

측정한 REST 경로는 요청 단위 브라우저 작업처럼 동작했지만, 이 테스트 하네스는 Browserless 내부를 충분히 깊게 추적하지 않았기 때문에 새로운 브라우저 프로세스와 모든 가능한 컨텍스트 재사용 전략을 구분할 수는 없었습니다. 대신 더 단순한 사실은 확인했습니다. 유휴 상태일 때 Chrome 프로세스는 0개였고, 활성 요청 중에는 chrome 계열 프로세스가 11개였으며, 순차 실행 후에는 다시 0개로 돌아갔습니다. 이 구성에서 미리 워밍된 브라우저 풀의 흔적은 보이지 않았습니다.

장기 실행되는 Node 서비스는 제한된 수의 브라우저 작업을 허용하고, 그다음에도 또 제한된 수를 큐에 올리며, 나머지는 거절합니다. 아래에서 측정한 아키텍처 계약으로 이 admission 모델을 이해하면 됩니다. “pool”이라는 단어만 보고 프로세스나 컨텍스트 재사용을 추측하면 안 됩니다. 타이밍 하네스로는 그 사실을 증명할 수 없기 때문입니다.

admission control에는 두 가지 설정값이 있습니다.

  • CONCURRENT — 동시에 실행할 세션 수
  • QUEUED — 슬롯이 생길 때까지 대기할 수 있는 추가 요청 수

컨테이너의 /config 엔드포인트는 기본값으로 CONCURRENT=10, QUEUED=10, TIMEOUT=30000을 보고했습니다. CONCURRENT + QUEUED를 넘는 요청은 즉시 거절됩니다.

인증은 선택사항이 아닙니다. Browserless v2는 항상 토큰을 요구합니다. TOKEN을 지정하지 않으면 시작할 때 랜덤 토큰을 만들어 stdout에 출력합니다. 모든 REST 호출에는 ?token=이 포함되어야 합니다.

가시성을 위해서는 /pressure(running, queued, CPU, memory, 최근 거절된 요청 수), /sessions, /config를 제공합니다. /metrics JSON export도 있지만 METRICS_JSON_PATH 설정이 필요해서 사용하지 않았습니다. 또 이미지에는 dumb-init이 PID 1로 실행되는데, 이는 컨테이너화된 Chrome 주변에서 오래전부터 따라다니던 좀비 프로세스 문제에 대한 문서화된 해법입니다.

셋업의 현실: 명령어는 한 줄이지만, 그 한 줄에 안 들어가는 게 네 가지 있다

누구나 인용하는 설치 명령은 정말로 docker run 한 줄이면 됩니다. 다만 그 주변을 어떻게 준비하느냐가 중요합니다.

이미지 크기는 4.34 GB입니다. 기대치를 잡아야 하는 숫자는 시작 지연 시간이 아니라 이 용량입니다. 매니페스트에는 linux/arm64linux/amd64가 모두 들어 있으며, 제 arm64 호스트에서는 Docker가 네이티브 arm64 변형을 받았습니다. (컨테이너 안 Chrome의 user-agent는 여전히 X11; Linux x86_64로 보이는데, 이건 에뮬레이션이 아니라 Linux에서 Chrome이 쓰는 외형상의 UA입니다. uname -maarch64를 보여줍니다. 사람들이 이걸 버그로 제보하곤 합니다.)

모든 측정에서 --shm-size=2g를 사용했습니다. 하네스는 Docker 기본 /dev/shm에서의 비교 측정을 포함하지 않았으므로, 이 글은 2 GiB가 보편적으로 반드시 필요하다고 말할 수도 없고, 실패 지점을 수치화할 수도 없습니다. 브라우저 수와 워크로드에 맞춰 크기를 잡아야 합니다.

토큰은 형식이 아니라 배포 이슈입니다. 이게 없으면, 포트 3000으로 접근할 수 있는 사람이라면 누구나 당신 네트워크 안의 브라우저를 사실상 자기 것으로 만들 수 있습니다.

컨테이너 네트워킹은 당신 책임입니다. 제 fixture는 호스트에서 실행됐기 때문에 컨테이너는 host.docker.internal을 통해 접근했습니다(colima는 --add-host host.docker.internal:host-gateway로 매핑). 측정값을 믿기 전에, 컨테이너가 실제로 fixture에 도달할 수 있는지 원시 curl로 먼저 확인했습니다.

제 환경은 macOS 26.5.2 arm64에서 colima 0.10.3(6 CPU / 11.6 GiB)와 Docker 29.2.1이었습니다. 하네스는 Python 3 표준 라이브러리만 사용했습니다. PNG와 PDF는 디코딩의 정확성, 해상도, 페이지 수, 완전성, 시각적 품질은 보지 않고 파일 시그니처만 확인했습니다.

동일한 최소 실행 예시는 고정된 이미지와 명시적 토큰, 그리고 여기서 사용한 shared-memory 할당을 씁니다.

docker run --rm -p 3000:3000 --shm-size=2g \
  -e TOKEN=replace-with-a-secret \
  ghcr.io/browserless/chromium:v2.55.0

/pressure?token=...가 응답한 뒤에는, 대상 URL을 담은 JSON 본문으로 인증된 POST /content?token=...를 보내 REST 경로를 테스트할 수 있습니다. 운영 환경의 호출자는 429 응답에 대해 지터를 포함한 bounded retry도 넣어야 합니다. 즉시 재시도하면 이미 가득 찬 같은 큐와 또 경쟁할 뿐입니다.

시작 비용을 단계별로 분해해 보면

Measured results chart: Observed Browserless startup stages

세 번의 새 docker run 부팅에서, 중앙값과 최소–최대는 다음과 같았습니다.

단계중앙값범위실제 의미
docker run/pressure 200 응답0.78 s0.70–0.87 sHTTP 엔드포인트 응답 가능; 이 체크만으로 브라우저 실행은 검증되지 않음
ready → 첫 /content 렌더0.32 s0.28–0.41 s첫 요청에서 관찰된 값: 브라우저 작업 + 이동 + HTML 반환
이후 /content 호출0.15 s0.147–0.154 s같은 컨테이너에서 후속 요청의 관찰 지연 시간

가운데 행은 과해석하기 쉽습니다. 브라우저 시작만 따로 측정한 것도 아니고, Browserless가 인프로세스 라이브러리보다 Chrome을 더 빨리 띄운다는 뜻도 아닙니다. 이는 컨테이너로 향하는 HTTP 왕복에 브라우저 작업, 네비게이션, 응답 전송이 합쳐진 값입니다. 첫 요청과 이후 요청 사이 약 0.17초 차이는 파일시스템, OS, Chrome, Node, 또는 컨테이너 캐시 효과를 포함할 수 있습니다. 유휴 상태에서 Chrome 프로세스는 0개였고, 이 호출들에 대해 CDP trace나 프로세스 타임라인을 캡처하지 않았으므로, 이 차이를 브라우저 재사용이나 “amortized”된 시작 비용으로 돌릴 수는 없습니다.

또한 이는 macOS의 colima VM에서 나온 수치입니다. 베어메탈 Linux에서는 다를 수 있습니다. 0.78초를 SRE에게 가져가면서 이식 가능한 값인 것처럼 말하면 안 됩니다.

실전: 한계치 찾기, 양쪽에서 보기

CONCURRENT + QUEUED → 429 계약은 여기저기서 반복되지만, 실제로 증명되는 경우는 드뭅니다.

설정은 간단합니다. fixture 라우트가 서버 측에서 5초 동안 sleep하도록 해서, 각 요청이 정해진 시간 동안 한 세션을 점유하게 만듭니다. 그다음 CONCURRENT + QUEUED + 4개의 요청을 동시에 보내 어떤 응답이 오는지 확인합니다. 동시에 별도의 sampler thread가 /pressure를 폴링해서 컨테이너 자체의 집계를 읽습니다.

설정 (CONCURRENT, QUEUED)보낸 수HTTP 200HTTP 429서버 /pressure 최고치 (running / queued / recentlyRejected)
(2, 2)8442 / 2 / 4
(3, 5)12843 / 5 / 4
(5, 5)141045 / 5 / 4

여기서 세 가지를 확인할 수 있었습니다.

한도는 매번 정확히 CONCURRENT + QUEUED입니다. 성공 응답 수는 각각 4, 8, 10이었고, 이는 각 설정에서의 합과 일치했습니다. 거절 수는 초과분과 같았고, 세 번 모두 4였습니다.

그리고 이 한도는 움직입니다. 이미지에 박힌 고정값이 아니라, 설정한 값이 그대로 한도가 됩니다. 환경 변수를 바꿔 4 → 8 → 10으로 이동하는 점이 이 기능을 단순한 잡담이 아니라 실용적으로 만드는 부분입니다.

또한 두 신호는 서로 독립적입니다. 클라이언트의 status code는 실제 HTTP 응답에서 나왔고, /pressure는 다른 thread가 폴링한 컨테이너 내부 집계였습니다. 이 짧은 세 번의 실행에서 둘은 일치했습니다. 이는 /pressure가 프로덕션 신호 후보라는 뜻이지, 완전한 autoscaling 계약을 의미하진 않습니다. 스크레이핑 주기, reset semantics, 다중 replica 집계, 더 긴 혼합 워크로드에서의 동작은 아직 더 검증이 필요합니다.

참고로 pass/fail 개수만 보면 놓치기 쉬운 nuance가 하나 있습니다. 대기열에 들어간 요청은 실패하지 않습니다. 기다릴 뿐이고, 꽤 오래 기다릴 수도 있습니다. (2, 2) 설정에서 5초 작업을 돌렸을 때 성공 응답은 5.7초에서 11.0초 사이에 도착했고, 중앙값은 8.3초였습니다. 따라서 end-to-end 지연은 대략 세션 두 번 분량까지 늘어났습니다. 하네스는 admission 시각과 execution 시각을 따로 기록하지 않았으므로, 전체 지연을 전부 queue wait로 단정할 수는 없습니다.

실제 작업에서는 어떤 모습인가

예를 들어 매일 밤 4,000개의 상품 페이지를 PDF로 렌더링하고, 각 페이지가 약 5초 걸린다고 합시다. CONCURRENT=5, QUEUED=5로 설정하면 처리량 한도는 5초당 5페이지, 즉 초당 1페이지입니다. 파이프를 정확히 꽉 채운 상태를 유지한다면 전체 작업은 대략 67분이 걸립니다. 이건 벤치마크가 아니라 측정된 동작을 바탕으로 한 산술이지만, 실제 배포 전에 꼭 해봐야 할 계산입니다.

모든 running 및 queued 슬롯이 찬 뒤에 도착한 요청은 즉시 429를 받을 수 있으며, 동시에 여러 요청을 보내더라도 어떤 순번의 요청이 밀릴지는 보장되지 않습니다. 작업 러너는 이 응답을 backpressure로 다뤄야 하고, bounded retry에 지터를 넣어야 합니다. 그렇지 않으면 상위 레벨의 작업 집계는 계속 진행되는데 페이지 일부가 조용히 빠지는 운영 리스크가 생깁니다. 이 하네스가 직접 증명한 실패 시나리오는 아니지만, 충분히 현실적인 위험입니다.

실전: 엔드포인트가 실제로 보는 것

렌더 fidelity를 솔직하게 테스트하려고, fixture 페이지는 실제 브라우저에서 실행 중이 아니면 마커 텍스트를 볼 수 없게 숨겨 두었습니다. 눈에 보이는 문자열 Runtime Injected Marker 88은 로딩 시 JavaScript 조각들로 조립되므로, 서버가 보내는 어떤 바이트에도 연속된 리터럴로 존재하지 않습니다. 이 페이지를 단순한 정적 fetch로 가져오면 702바이트가 오지만 마커는 포함되지 않습니다.

엔드포인트결과바이트 수
/content런타임 삽입 마커 존재, 정적 마커 2개도 포함811
/scrape on #scrape-me (JS로 삽입된 노드)SCRAPE_TARGET_VALUE_CC 반환422
/screenshotPNG 시그니처 89 50 4E 47가 있는 응답18,621
/pdfPDF 시그니처 %PDF-가 있는 응답40,974
네 가지 모두, 토큰 없음HTTP 401 (403 아님)

/content가 811바이트와 삽입 마커를 반환했다는 건, HTML이 돌아오기 전에 실제 Chromium이 페이지를 렌더링했다는 뜻입니다. /scrape는 JavaScript가 실행되기 전에는 존재하지 않는 노드에서 값을 뽑아왔습니다. 둘 다 클라이언트 측 자동화 코드 없이 동작했습니다. 인증된 POST 한 번이면 충분했습니다.

이게 진짜 핵심입니다. 같은 테스트 라운드에서 정적 크롤러는 이런 종류의 콘텐츠를 완전히 놓쳤고, 인프로세스 브라우저 라이브러리(chromedp, rod, Selenium)는 명시적 wait를 직접 넣은 뒤에야 잡아냈습니다. Browserless는 curl 같은 요청 한 번으로 잡아냈습니다. 자동화 코드를 배포 무게로 바꾸는 셈입니다.

다만 이 주장에도 경계선이 있습니다. 증거는 제가 만든 fixture의 콘텐츠 유형에만 해당하며, 현대 웹 전체를 조사한 것이 아닙니다. 그리고 안티-디텍션 엔드포인트인 /unblock은 의도적으로 건드리지 않았습니다. 따라서 이 결과를 anti-bot 능력 주장으로 읽으면 안 됩니다. /function, /download, /performance도 테스트하지 않았습니다.

실전: 짧은 잔여물 점검

컨테이너화된 Chrome은 시체를 남긴다는 평판이 있어서, CONCURRENT=3으로 30회의 순차 세션을 실행하고 컨테이너 내부 프로세스를 세어봤습니다.

아무것도 믿기 전에 감지기를 보정했습니다. 세션이 진행 중일 때 /proc 열거기는 chrome 계열 프로세스 11개를 읽었습니다(browser, zygote, GPU, renderer, utility 등). 이건 중요합니다. 도구가 Chrome을 볼 수 있다는 뜻이므로, 실행 후 0개라는 값은 “못 봐서 0”이 아니라 실제 측정이라는 뜻입니다. 세어볼 수 있는지 증명하지 않은 채 “0 processes”라고 보고하는 누수 테스트는 무의미합니다.

30회 세션 후: chrome 프로세스 0개, zombie 0개. 살아남은 것은 dumb-init, node, Xvfb, start.sh, sh뿐이었습니다. /sessions도 유휴 상태에서 0을 읽었습니다.

컨테이너 메모리, docker stats 기준(운영자가 보는 수치이며, 단일 프로세스 RSS가 아닙니다):

N회 세션 후051015202530
컨테이너 메모리(MiB)294300301302302303303

30회 세션 전체에서의 순증가는 약 9.5MB였고, 샘플 곡선은 10회 세션 이후 평탄해졌습니다. 이 짧은 구간에서 세션당 선형 누수가 있었다는 패턴과는 맞지 않습니다. Node warmup이 하나의 가능한 설명이지만, 이 프로세스 수와 메모리 시리즈만으로는 증명할 수 없습니다.

범위: 30회의 순차 세션은 작은 soak 테스트일 뿐, endurance나 동시성 테스트는 아닙니다. 이슈 트래커에서 오래전부터 언급된 EventEmitter listener 경고는 수 시간과 수천 세션이 지나야 드러날 수 있는 종류의 문제인데, 저는 그 정도는 돌리지 않았습니다. 따라서 지원 가능한 결론은, v2.55.0에서 이 범위 안에서는 Chrome 프로세스나 zombie가 누적되는 현상이 관찰되지 않았다는 것뿐입니다.

timeout 경계

TIMEOUT은 문서화된 조절값입니다. 실제로 작동하는지 보고 싶었습니다.

사례페이지 유지 시간상태경과 시간
예산 내2,000 ms2002.406 s
예산 초과15,000 ms4085.007 s

TIMEOUT=5000 설정에서, 15초 동안 페이지를 붙잡으려 한 세션은 hanging 되지 않고 5.007초에 HTTP 408을 반환했습니다. 이 단일 관찰은 설정한 경계 근처에서 enforcement가 일어난다는 것을 확인합니다. 하지만 타이머 구현이나 슬롯 정리까지는 보여 주지 않습니다. 더 강한 테스트라면 반복해서 실행하고, /sessions/pressure가 다시 idle로 돌아가는지 확인한 다음, 이어지는 요청이 해제된 슬롯을 실제로 다시 얻는지 검증해야 합니다.

마이그레이션 함정: PREBOOT는 조용히 무시되고, 그것을 알려 주지도 않는다

제가 측정한 것 중에서, 업그레이드 전에 가장 먼저 알려줬으면 좋았을 결과입니다.

Browserless 2.0.0은 PREBOOTKEEP_ALIVE를 제거했습니다. changelog에는 혼란만 낳고 실익이 적으며 버그를 유발했기 때문이라고 적혀 있습니다. 충분히 납득할 만한 결정입니다. 문제는 v1 설정을 그대로 v2에 복붙하는 상황인데, 업그레이드할 때 사람들이 가장 흔히 하는 방식이 바로 그것입니다.

컨테이너를 -e PREBOOT=true로 실행하고 기본값과 비교했습니다.

신호PREBOOT=true기본값, 플래그 없음
Ready 시간0.716 s0.776 s
Cold render0.314 s0.318 s
Warm render0.163 s0.150 s
유휴 상태의 Chrome 프로세스 수00

모든 타이밍은 기본 arm의 min–max 범위 안에 들어갑니다. 즉, 효과가 아니라 잡음입니다. 그리고 아무것도 미리 워밍되지 않았습니다. PREBOOT=true 컨테이너는 유휴 상태에서 브라우저를 하나도 들고 있지 않았고, 플래그가 없는 경우와 완전히 같았습니다. 또 다른 신호 두 가지는 숫자가 아닙니다.

  • /config에는 preboot 키 자체가 없습니다. 키들은 concurrent, queued, timeout, token, maxCPU, maxMemory, retries 등입니다.
  • 에러도, 경고도 없었습니다. 컨테이너 로그에도 아무것도 없습니다.

즉, v1의 PREBOOT 설정을 v2에 넣으면 일반적인 시작 및 로그 확인에서는 조용히 아무 일도 하지 않는 no-op가 됩니다. /config에서 키가 사라져 있고 동작도 바뀌지 않았다는 점이 감지 가능한 신호이며, Browserless는 명시적으로 거부하거나 경고를 남기지 않습니다. 마이그레이션 점검은 startup가 초록색이라고 해서 모든 환경 변수가 적용됐다고 가정하지 말고, 실제 적용된 설정을 확인해야 합니다.

KEEP_ALIVE는 정반대 케이스이며, 둘을 같은 범주로 묶으면 안 됩니다. 같은 릴리스에서 제거되었지만, 이건 조용히 무시되지 않습니다. 컨테이너를 잠깐 확인해 보면 stdout에 Environment variable of "KEEP_ALIVE" is deprecated and ignored.라는 로그가 남습니다. 운영자에게 보이는 올바른 경고입니다. 저는 KEEP_ALIVEPREBOOT와 같은 측정 하네스에 넣지는 않았기 때문에, 측정치가 아니라 점검 결과로 보고합니다. 그래도 방향성은 충분히 중요합니다. 조용한 함정은 PREBOOT만 해당됩니다. Browserless는 KEEP_ALIVE에 대해서는 “v2가 v1 플래그를 다 무시한다”는 식의 뭉뚱그린 설명보다 훨씬 정직합니다.

장단점

장점

  • 문서화된 그대로 동작하는 admission control이며, 설정에 따라 정확히 한도가 움직임 — 세 가지 서로 다른 한계에서, 클라이언트 status code와 서버 자체 집계를 동시에 통해 검증됨
  • /pressure가 client-visible running, queued, rejected 카운트와 세 번의 짧은 실행에서 일치함; autoscaling과 alerting의 후보 입력으로 검토할 만함
  • 클라이언트 측 자동화 코드 없이 실제 Chromium 렌더링: 인증된 POST 한 번으로 정적 fetch가 볼 수 없는 JS 주입 DOM을 확인함
  • 30회 순차 실행에서 Chrome 프로세스 누적이 관찰되지 않음; 실행 후 Chrome 프로세스 0개, zombie 0개
  • TIMEOUT 한 번의 시험에서 5.000초 예산에 대해 5.007초에 408을 반환했으며, cleanup과 slot release는 별도로 검증하지 않음
  • 기본 인증이 켜져 있음: 네 개의 REST 엔드포인트 모두 토큰이 없으면 401을 반환
  • 준비된 서비스까지 docker run 한 번, 그리고 그 뒤 첫 렌더까지 약 0.78초

단점

  • 4.34 GB 이미지. 이건 솔직한 비용이며, registry, CI cache, cold deploy 시간에 바로 반영됩니다.
  • SSPL-1.0 또는 Browserless 상용 라이선스. 정확한 현재 조건을 배포 방식과 맞춰 검토해야 합니다.
  • v1의 PREBOOT는 v2에서 받아들여지지만 조용히 무시됨 — 에러도, 경고도, /config 키도 없음
  • 의존성을 추가하는 게 아니라 서비스를 운영하는 것임: 컨테이너, 토큰, 네트워크 경로, admission 한도, 업그레이드 책임이 따라옴
  • 대기열에 들어간 요청은 (2, 2) 실행에서 end-to-end 지연을 대략 세션 두 번 분량까지 늘렸음; 별도의 queue wait는 측정되지 않음
  • 측정된 REST 타이밍에는 HTTP 홉이 포함되며, 브라우저 시작 비용이나 브라우저 재사용을 분리해 보여 주지 않음

누가 써야 하고, 누가 쓰지 말아야 하나

Browserless는 한 가지 이상의 작업이 브라우저를 필요로 할 때 제값을 합니다. 여러 앱이 공유하는 렌더링 서비스, 모든 서비스에 Chrome 의존성을 넣는 대신 HTTP 엔드포인트 뒤에서 스크린샷과 PDF를 쓰고 싶은 팀, 실제로 측정 가능한 backpressure가 있는 용량 한도가 필요한 작업 파이프라인이라면 이 형태가 잘 맞습니다. 이미 Docker를 쓰고 있고 배포 책임자가 따로 있다면 운영 스토리도 분명합니다. 예측 가능한 admission, 관찰 가능한 backpressure, 그리고 30회 순차 점검에서 Chrome 프로세스나 zombie 누적이 관찰되지 않았다는 점이 그것입니다.

스택 안의 모든 서비스가 각자 Chromium을 설치해야 하는 상황을 피하고 싶을 때도 올바른 선택입니다. 그것을 하나의 컨테이너로 모아 토큰과 한도를 두는 것은 충분히 좋은 아키텍처적 거래입니다.

반대로, 단 한 개의 스크립트만 작성하는 상황이라면 굳이 쓸 필요 없습니다. 한 개의 Python 파일이 렌더된 페이지 하나를 가져오기 위해 4.3GB를 내려받고 컨테이너까지 띄우는 건 작은 작업치고 절차가 너무 많습니다. 프로세스 내부의 브라우저 라이브러리는 별도 서비스 배포 없이 그 일을 처리합니다. 상용 제품의 SSPL 조건이 맞지 않고 해결할 방법도 없다면 쓰지 마세요. 진짜 원하는 것이 cold cost 없이 바로 워밍된 브라우저라면, v2의 PREBOOT는 그걸 제공하지 않습니다. 그리고 진짜 문제가 anti-bot 대응이라면, 그 부분은 제가 일부러 테스트하지 않은 엔드포인트에 달려 있으므로 보증할 수 없습니다.

대안, 그리고 Thunderbit가 들어가는 위치

비교할 포인트는 Browserless와 다른 컨테이너가 아닙니다. 브라우저가 어디서 실행되고, 누가 그것을 계속 살아 있게 유지할 책임을 지는가입니다.

관련 리뷰: Browsertrix Crawler 리뷰.

관련 리뷰: chromedp 리뷰.

브라우저 라이브러리 (chromedp, rod, Selenium, Playwright)Browserless 자체 호스팅Thunderbit 관리형 추출
브라우저 실행 위치당신의 프로세스 안당신의 컨테이너 안다른 사람의 인프라
설정 비용패키지 설치4.3GB 이미지 + 컨테이너 + 토큰API 키
여기서 측정한 타이밍이 글에서 측정하지 않음HTTP 준비 후 첫 렌더 0.32초, 이후 호출 중앙값 0.15초이 글에서 측정하지 않음
작성해야 하는 것explicit wait가 포함된 자동화 코드인증된 POST 한 번HTTP 호출 한 번
반환되는 것스크립트한 그대로HTML, 시그니처 매칭 PNG/PDF 응답, 스크래핑된 노드제품별 구조화 JSON 또는 Markdown
용량 한도내 머신 한도CONCURRENT + QUEUED, 이후 429제공업체 플랜
당직 책임자당신당신그들

브라우저를 프로세스 안에 두고 wait를 직접 작성하는 데 거부감이 없으면, 라이브러리가 더 가볍고 별도 배포도 필요 없습니다. 이쪽은 Playwright vs Puppeteer 비교와 더 넓은 오픈소스 스크래핑 프로젝트 모음에서 따로 정리했습니다.

아예 브라우저 운영 자체를 원하지 않는다면, 저희 Thunderbit도 하나의 관리형 대안입니다. Browserless는 렌더링된 결과물을 돌려주고 그 해석은 당신 코드가 맡는 반면, Thunderbit는 렌더링 인프라를 제공업체가 책임지는 동안 Markdown이나 스키마 일치 데이터로 돌려줄 수 있습니다. 이 글은 Thunderbit의 지연 시간, 용량, 실패 동작, 추출 품질, 비용을 벤치마크하지 않았으므로, 아래 표는 성능 비교가 아니라 책임 경계를 설명합니다.

같은 테스트 라운드에서 이어서 읽을 만한 글로는, 직접 운영하는 브라우저 기반 Markdown 파이프라인을 다룬 Crawl4AI 리뷰와 더 넓은 범주의 웹 스크래핑 도구 개요가 있습니다.

웹 데이터 추출을 위해 Thunderbit 사용해 보기

결론

Browserless를 써야 할까요? 여러 호출자가 브라우저 작업을 필요로 하고, 컨테이너를 운영할 사람이 있으며, 라이선스 검토가 배포 모델을 승인한다면 그렇습니다. 세 번의 synthetic admission 테스트에서 수용된 요청 수는 CONCURRENT + QUEUED와 정확히 일치했고, 초과분은 429를 받았으며, /pressure는 클라이언트가 본 카운트와 맞았습니다. 별도의 30회 순차 점검에서는 Chrome 프로세스나 zombie가 누적되지 않았습니다. 한 번의 timeout 시험은 설정 경계 근처에서 408을 반환했습니다. 이건 유용한 bounded observation이지, 보편적인 보증은 아닙니다.

다만 투자 규모는 솔직하게 봐야 합니다. 이건 4.34GB짜리 이미지이자 직접 운영해야 하는 서비스이지, 그냥 추가하는 dependency가 아닙니다. 이 테스트는 인프로세스 브라우저 라이브러리와의 속도 비교를 입증하지 않았습니다. 이 제품이 주는 것은 브라우저를 배급할 수 있다는 점입니다. 즉, 알려진 한도와 측정 가능한 backpressure를 얻는 것입니다. 30회 순차 점검에서는 Chrome 프로세스나 zombie의 누적이 관찰되지 않았습니다. 비용은 배포 무게와, 읽어야 할 라이선스입니다. 한두 페이지를 한 스크립트로 렌더링하는 수준이라면 이 거래는 손해입니다. 여러 서비스가 의존하는 렌더링 계층을 운영한다면 이야기가 달라집니다. 다만 들어올 때는 v1 환경 변수를 꼭 다시 확인하세요. PREBOOT는 열심히 일하는 척만 하면서 실제로는 아무것도 하지 않을 테니까요.

웹 데이터 추출을 위해 Thunderbit 사용해 보기 Get Started Free

자주 묻는 질문

Browserless가 headless Chrome을 더 빠르게 만드나요? 이 테스트로는 답할 수 없습니다. HTTP 엔드포인트는 docker run 후 0.78초 만에 응답 가능해졌고, 첫 /content 호출은 0.32초가 걸렸으며 같은 컨테이너의 이후 호출은 약 0.15초였습니다. 이 수치들은 HTTP 왕복, 브라우저 작업, 네비게이션, 응답 전송이 모두 섞여 있습니다. 하네스는 launch time만 따로 분리하지 않았고, 프로세스 재사용도 추적하지 않았으며, 비교 가능한 인프로세스 벤치마크도 공개하지 않았습니다. Browserless는 공유 서비스 경계와 admission control 용도로 쓰고, 실제 지연 경로는 직접 벤치마크하세요.

Browserless의 concurrency 한도를 넘기면 어떻게 되나요? 즉시 HTTP 429를 받습니다. 한도는 정확히 CONCURRENT + QUEUED이며, 세 가지 설정에서 확인했습니다. (2,2)는 4개를 수용하고 4개를 거절했고, (3,5)는 8개를 수용하고 4개를 거절했으며, (5,5)는 10개를 수용하고 4개를 거절했습니다. 서버의 /pressure 엔드포인트는 매번 running, queued, recentlyRejected 수를 일치하게 보고했습니다. 알아둘 점은 queued 요청은 실패하는 게 아니라 기다린다는 것입니다. (2,2)에서 5초짜리 작업은 성공 응답이 5.7초에서 11.0초 사이에 왔습니다. 클라이언트는 429를 backpressure로 보고 retry와 backoff를 넣어야 합니다.

Browserless v2에서 PREBOOT는 아직 동작하나요? 아니요. PREBOOT는 2.0.0에서 제거되었고, v2는 -e PREBOOT=true를 아무 에러나 경고 없이 받아들이지만 실제로는 아무 일도 하지 않습니다. 저는 세 가지 방식으로 이 inert 상태를 확인했습니다. 지연 시간은 기본값과 구분되지 않았고, 유휴 상태의 PREBOOT=true 컨테이너는 대기 중인 chrome 프로세스가 0개였으며, /config에도 preboot 키가 전혀 없었습니다. v1 설정을 마이그레이션했다면 인스턴스는 미리 워밍되지 않은 상태입니다. 같은 릴리스에서 제거된 KEEP_ALIVE는 "deprecated and ignored" 경고를 로그에 남긴다는 점도 참고하세요. 즉, 조용히 실패하는 문제는 PREBOOT에만 해당됩니다.

Browserless는 상업적으로 무료인가요? 저장소는 SSPL-1.0 또는 Browserless 상용 라이선스를 제공하지만, 이 글은 특정 상용 또는 폐쇄형 시나리오를 어느 쪽에 매핑해야 하는지 단정하지 않습니다. 현재 LICENSE공식 배포 가이드를 검토한 뒤, 소프트웨어 라이선스를 책임지는 사람이 배포 모델을 판단해야 합니다.

Browserless는 zombie Chrome 프로세스를 남기나요? 이번에 테스트한 짧은 구간에서는 누적되지 않았습니다. 30회의 순차 세션 후 컨테이너에는 Chrome 프로세스 0개와 zombie 0개만 남았고, dumb-init, node, Xvfb, start.sh, sh만 살아 있었습니다. 세션이 살아 있는 동안 감지기는 chrome 계열 프로세스 11개를 세었기 때문에, 아무것도 못 보는 상태는 아니었습니다. 컨테이너 메모리는 294 MiB에서 303 MiB로 늘어난 뒤 샘플에서 평탄해졌습니다. 이것은 수 시간, 동시성, 수천 세션 규모의 endurance 결과는 아닙니다.

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

1회 클릭 안에 어떤 페이지든 데이터 추출

250,000명+ 사용자가 신뢰
무료 플랜 제공
웹페이지에서 스프레드시트까지
필요한 내용을 설명하세요 — Thunderbit의 AI Agent가 수집하고 Excel, Google Sheets, Airtable, Notion으로 내보냅니다. 시작은 무료입니다.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week