مراجعة Browserless: Chrome كخدمة يمكنك ضبط استهلاكها

آخر تحديث في August 14, 2026
مراجعة Browserless: Chrome كخدمة يمكنك ضبط استهلاكها
ملخص AI
Browserless هو Chrome بنمط headless مُقدَّم كخدمة تستضيفها بنفسك. حاوية Docker الخاصة به تبقى قيد التشغيل، وتقبل العمل عبر HTTP أو WebSocket، وتفرض...

Browserless은 그래픽 UI 없이 돌아가는 Chrome을 직접 운영하는 서비스 형태로 제공하는 방식이다. Docker 컨테이너 안에서 계속 실행되며, HTTP나 WebSocket으로 작업을 받아 처리하고, 각 앱 안에 부담을 넣는 대신 공통 요청 한도를 서비스 차원에서 관리한다. 여기서 진행한 REST 테스트에서는 대기 상태일 때 Chrome 프로세스가 0개였고, 요청이 들어오는 동안에만 Chrome 프로세스가 생성됐다가 작업이 끝나면 다시 0으로 돌아갔다. 실제로 묶여 있는 것은 “미리 워밍업된 브라우저 풀”이 아니라 서비스 용량과 큐였다.

v2.55.0을 로컬에서 정해진 환경으로 테스트했다. 시작 과정을 단계별로 분해했고, 세 가지 설정으로 수용 한계를 확인했으며, 알려진 결과와 출력이 맞는지 검증했고, 30세션 부하 테스트와 타임아웃 동작도 점검했다. 결과는 속도 향상보다는 운영 측면에서 더 의미가 있었다. 수용 한도는 클라이언트가 보는 응답과 정확히 맞아떨어졌고, 경계가 거친 지점들은 대부분 배포 방식과 설정 방식에서 비롯됐다.

가장 유용한 결론은 특정 응답 시간 숫자 하나가 아니었다. Browserless는 Chrome을 더 빨리 시작하게 만드는 도구가 아니라, Chrome 앞에 진입 정책을 둔다. 내부에는 고정된 세션 수가 있고, 그 뒤에는 대기열이 있으며, 한도를 넘는 요청에는 HTTP 429를 돌려준다. 이 상한은 환경 변수 두 개를 바꾸자 4에서 8, 다시 10으로 이동했다. 그리고 컨테이너 내부 계산값도 모든 요청에서 클라이언트 쪽 상태 코드와 일치했다.

ما هو Browserless فعليًا

많이 헷갈리는 지점이 바로 여기다. Browserless는 가져다 쓰는 라이브러리가 아니다. 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 별13,525개, 2026년 7월 27일 기준

별 개수는 시간이 지나며 바뀐다. 이 값은 테스트 시점의 스냅샷으로 보면 된다.

라이선스 관문: SSPL-1.0 또는 상용 라이선스

Browserless 저장소는 SSPL-1.0 또는 Browserless 상용 라이선스로 배포된다. 실제로는 현재 LICENSE 파일과 공식 오픈소스 배포 가이드를 먼저 꼼꼼히 읽어야 한다. 이 글은 상용 제품, 닫힌 소스 앱, CI 시스템, 호스팅 서비스, 내부 배포에 대한 법적 판단을 제공하지 않으므로, 그런 사례들을 라이선스 관점에서 분류하지 않는다. 배포와 유통 모델은 반드시 법률 자문이나 소프트웨어 라이선스 담당자가 판단해야 한다.

Browserless는 호스팅 플랜도 판매한다. 가격과 사용량 단위 정의는 수시로 바뀌고, 이번 자체 호스팅 테스트에는 포함되지 않았으니, 여기 적힌 오래된 표를 구매 가이드처럼 보지 말고 공식 사이트에서 다시 확인해야 한다.

내부의 세션 모델은 어떻게 동작하나

이번에 측정한 REST 경로는 요청마다 브라우저 작업처럼 동작했지만, 이 프레임워크만으로 Browserless 내부를 아주 깊게 추적한 것은 아니어서, 새 브라우저 프로세스를 만드는지 아니면 어떤 식으로든 컨텍스트를 재사용하는지까지는 구분하지 못했다. 확인된 사실은 단순하다. 대기 상태일 때 Chrome 프로세스는 0개였고, 활성 요청 중에는 Chrome 계열 프로세스가 11개였으며, 순차 실행이 끝난 뒤에는 다시 0개였다. 이 설정에서는 사전 워밍업된 브라우저 저장소 같은 흔적은 보이지 않았다.

오래 살아 있는 Node 서비스가 브라우저 작업을 일정 수만큼 허용하고, 또 다른 수는 대기열에 넣고, 나머지는 거절하는 구조다. 아래에서 측정한 건 이 아키텍처 계약으로 봐야 한다. “pool”이라는 말만 보고 프로세스나 컨텍스트 재사용을 단정하면 안 된다. 측정 프레임만으로는 그것을 증명할 수 없다.

수용 제어는 두 개의 손잡이로 움직인다.

  • CONCURRENT — 동시에 실행 가능한 세션 수
  • QUEUED — 자리가 날 때까지 기다릴 수 있는 추가 요청 수

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

인증은 선택 사항이 아니다. Browserless v2는 영구 토큰을 요구한다. TOKEN을 따로 지정하지 않으면 시작할 때 무작위 토큰을 만들고 stdout에 출력한다. 모든 REST 요청에는 ?token=이 붙어야 한다.

모니터링용으로는 /pressure가 있다. 여기서 현재 실행 중, 대기 중, CPU와 메모리 사용량, 최근 거절된 요청 수를 볼 수 있다. /sessions/config도 있다. JSON 형태의 /metrics도 가능하지만 METRICS_JSON_PATH 설정이 필요해서 이번에는 쓰지 않았다. 이미지에는 dumb-init이 PID 1으로 실행되는데, 이건 수년간 컨테이너 안의 Chrome과 함께 따라다니던 좀비 프로세스 문제에 대한 공식적인 해법이다.

설정 현실: 한 줄 명령과, 그 안에서 아무도 말하지 않는 네 가지

사람들이 늘 말하는 설치 방식은 정말로 docker run 한 줄이다. 하지만 그 주변에서 일어나는 일까지 같이 봐야 한다.

이미지 크기는 4.34GB다. 이게 시작 시간보다 먼저 기대치를 잡아야 하는 숫자다. 매니페스트에는 linux/arm64linux/amd64가 모두 들어 있고, 내가 쓴 arm64 호스트에서는 Docker가 arm64 이미지를 받았다. 다만 컨테이너 안 Chrome 사용자는 여전히 X11; Linux x86_64처럼 보이는데, 이건 Linux에서 Chrome이 쓰는 형식상의 UA 문자열일 뿐 에뮬레이션이 아니다. uname -maarch64를 보여준다. 사람들이 이걸 버그로 제보하곤 한다.

모든 측정에서 --shm-size=2g를 사용했다. 기본 Docker /dev/shm으로도 제대로 되는지 확인하는 대조 실험은 넣지 않았기 때문에, 2GB가 전 세계적으로 반드시 필요하다고 말할 수는 없다. 브라우저 수와 작업량에 맞게 조정해야 한다.

토큰은 형식이 아니라 배포 보안 요소다. 없으면 3000번 포트에 접근할 수 있는 누구나 네트워크 안에서 브라우저를 쓸 수 있다.

컨테이너 네트워크는 네가 책임져야 한다. 테스트 환경은 호스트에 있었기 때문에 host.docker.internal로 컨테이너에서 접근했다. colima에서는 --add-host host.docker.internal:host-gateway를 사용했다. 측정 전에 실제로 컨테이너가 테스트 환경에 닿는지 curl로 먼저 확인했다.

내 환경은 macOS 26.5.2 arm64 위의 colima 0.10.3(6 CPU / 11.6 GiB)와 Docker 29.2.1이었다. 프레임워크는 표준 라이브러리만 쓴 Python 3였다. PNG와 PDF는 디코딩 정확성이나 크기, 페이지 수, 완전성, 시각적 일치가 아니라 바이너리 시그니처만 확인했다.

같은 설정을 간단히 돌리면, 설치된 이미지를 쓰고, 명시적 토큰을 넣고, 여기서 사용한 shm 크기를 적용한다.

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

/pressure?token=...가 응답하면, 문서화된 POST /content?token=...에 목표 URL을 담은 JSON 바디를 보내 REST 경로를 시험할 수 있다. 실무에서는 429에 대해 지터를 섞은 제한적 재시도를 써야 한다. 바로 다시 보내면 결국 꽉 찬 같은 대기열에 다시 부딪칠 뿐이다.

시작 비용: 단계별로 쪼개 보면

Measured results chart: Observed Browserless startup stages

세 번의 새 docker run 실행을 측정했고, 중앙값과 최소~최대 범위를 정리했다.

단계중앙값범위실제 의미
docker run/pressure 200 응답0.78초0.70–0.87초HTTP 엔드포인트가 응답 가능해진 시점; 이 테스트만으로 브라우저 시작을 검증한 것은 아님
준비 완료 → 첫 /content 렌더0.32초0.28–0.41초첫 관측 요청: 브라우저 작업 + 이동 + HTML 반환
이후 /content 호출0.15초0.147–0.154초같은 컨테이너 안에서 관측된 후속 요청 시간

가운데 숫자를 잘못 읽기 쉽다. 이 값은 브라우저 자체 시작 시간만 따로 재는 것이 아니고, Browserless가 인프로세스 라이브러리보다 Chrome을 더 빨리 띄운다는 뜻도 아니다. HTTP 왕복, 브라우저 작업, 페이지 이동, 응답 전달이 모두 합쳐진 값이다. 첫 요청과 이후 요청 사이에 약 0.17초 차이가 있었지만, 이건 파일시스템, OS, Chrome, Node, 컨테이너 캐시 효과가 섞였을 수도 있다. 대기 상태에서 Chrome 프로세스가 0개였고, 이 요청들에 대해 CDP 추적이나 프로세스 타임라인을 별도로 잡지 않았기 때문에, 이 차이를 브라우저 재사용이나 분산된 시작 비용이라고 말할 수는 없다.

또 하나, 이 수치는 macOS 위 colima VM에서 나온 것이다. 실제 Linux 머신에서는 다르게 나올 수 있다. 0.78초를 SRE 팀에 그대로 가져가 재사용 가능한 값처럼 보지 마라.

실전에서: 양쪽에서 한계 찾기

CONCURRENT + QUEUED → 429라는 패턴은 흔하지만, 실제로 눈으로 확인되는 경우는 많지 않다.

설정은 이렇다. 서버에서 5초 동안 잠드는 테스트 경로를 만들어, 각 요청이 알려진 시간 동안 세션 하나를 점유하게 한다. 그다음 CONCURRENT + QUEUED + 4개의 요청을 동시에 던지고, 어떤 응답이 오는지 보면서, 별도 스레드가 /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으로 바뀌는 점이 이 결과를 단순한 참고가 아니라 실제로 유용한 정보로 만든다.

그리고 두 신호는 독립적이다. 클라이언트 쪽 상태 코드는 실제 HTTP 응답에서 왔고, /pressure는 컨테이너 내부 계산값을 다른 스레드가 읽은 것이다. 이 세 번의 짧은 실행에서는 둘이 정확히 맞아떨어졌다. 그래서 /pressure는 운영에서 후보 지표가 될 수 있지만, 자동 스케일링의 완성형은 아니다. 수집 주기, 리셋 의미, 여러 인스턴스 집계, 더 길고 혼합된 부하에서의 동작을 아직 확인해야 한다.

성공/실패 숫자만 보면 놓치는 부분이 있다. 대기열에 들어간 요청은 실패하지 않고 기다리며, 꽤 오래 기다릴 수도 있다. (2, 2)에서 5초 작업일 때 성공 응답은 5.7초~11.0초 사이였고, 중앙값은 8.3초였다. 그래서 총 응답 시간은 세션 시간의 거의 두 배가 됐다. 이 프레임은 수락과 실행 시간을 따로 재지 않았기 때문에, 전체 지연을 큐 대기로만 단정할 수는 없다.

실제 작업에선 어떻게 보이나

예를 들어 밤새 4,000개 제품 페이지를 PDF로 만들고, 각 페이지가 약 5초 걸린다고 해보자. CONCURRENT=5, QUEUED=5로 맞춘다. 처리량 상한은 5초마다 5페이지, 즉 초당 1페이지다. 그러면 파이프라인이 꽉 차 있는 상태라면 전체 작업은 약 67분 걸린다. 이건 벤치마크가 아니라 측정한 동작에 기반한 계산이지만, 배포 전에 반드시 해야 하는 계산이다.

한 번에 실행 슬롯과 대기 슬롯이 다 찼다면, 그 뒤에 오는 요청은 즉시 429를 받을 수 있다. 동시 요청에서 논리적으로 먼저 보낸 요청이 반드시 이긴다는 보장은 없다. 실행 프로그램은 이 응답을 역압으로 받아야 하고, 지터를 넣은 제한적 재시도를 써야 한다. 그렇지 않으면 상위 작업은 성공한 것처럼 보이는데 페이지 일부를 놓치는 일이 생길 수 있다. 이번 프레임이 그 실패를 직접 입증한 것은 아니지만, 운영상 충분히 위험한 상황이다.

실제로 엔드포인트가 무엇을 돌려주나

렌더링 정확도를 제대로 확인하려면, 테스트 페이지는 실제 브라우저를 쓰지 않으면 보이지 않는 마커를 숨긴다. 보이는 문자열 Runtime Injected Marker 88는 로딩 시 JavaScript가 조립하기 때문에, 서버가 보내는 바이트 안에는 그 문자열이 문자 그대로 존재하지 않는다. 일반적인 정적 가져오기는 그 페이지에서 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바이트를 돌려주고 주입된 마커를 포함했다는 건, Chromium이 HTML을 돌려주기 전에 실제로 페이지를 렌더링했다는 뜻이다. /scrape는 JavaScript 실행 후에야 생기는 노드에서 값을 뽑아냈다. 이 모든 것이 클라이언트 측 자동화 코드 없이, 인증된 POST 한 번으로 이뤄졌다.

이게 핵심이다. 같은 테스트에서 정적 크롤러는 이런 콘텐츠를 완전히 놓쳤고, 인프로세스 브라우저 라이브러리(chromedp, rod, Selenium)는 명시적인 대기를 추가한 뒤에야 잡아냈다. Browserless는 curl 같은 요청으로 이걸 가져왔다. 자동화 코드를 서비스 배포 비용으로 바꾸는 셈이다.

다만 여기엔 한계가 있다. 이 증거는 내 테스트 페이지의 콘텐츠 유형만 다루며, 현대 웹 전체를 포괄적으로 스캔한 것은 아니다. 또 우회 탐지용 엔드포인트인 /unblock은 의도적으로 테스트하지 않았다. 그러므로 이 결과를 봇 방어 기능에 대한 주장으로 읽으면 안 된다. /function, /download, /performance도 테스트하지 않았다.

실제로: 잔여 프로세스 점검

컨테이너 안 브라우저는 좀비를 남긴다는 평이 있어서, CONCURRENT=3으로 30개 세션을 순차 실행하며 컨테이너 내부 프로세스를 세어봤다.

그 전에 계측기를 맞췄다. 활성 세션이 돌아가는 동안 /proc에서 Chrome 계열 프로세스가 11개로 잡혔다. 브라우저, zygote, GPU, renderer, utility 프로세스가 모두 포함된 숫자다. 이 점이 중요하다. 도구가 Chrome을 제대로 세고 있다는 뜻이기 때문이다. 그래서 실행 후 0이 나오는 결과가 실제 측정이지, 단순히 못 본 게 아니다. “0개”만 보고 측정했다고 주장하는 테스트는 의미가 없다.

30세션 후 결과는 Chrome 프로세스 0개, 좀비 프로세스 0개였다. 남은 것은 dumb-init, node, Xvfb, start.sh, sh뿐이었다. /sessions도 유휴 상태에서 0을 반환했다.

docker stats에서 본 컨테이너 메모리(한 프로세스 RSS가 아니라 운영자가 보는 수치)는 이렇다.

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

30세션 동안의 순증은 약 9.5MiB였고, 표본 곡선은 10세션 이후 안정됐다. 이 범위에서는 세션마다 선형으로 메모리가 새는 패턴과는 맞지 않는다. Node 워밍업이 한 가지 설명일 수는 있지만, 이 정도 카운트와 메모리 측정만으로는 증명할 수 없다.

범위는 범위일 뿐이다. 여기서의 30세션은 작은 샘플이고, 장시간 실행이나 고동시성은 아니다. 이슈 트래커에 오래된 EventEmitter 리스너 경고가 보이는데, 그건 수천 세션과 수시간 뒤에야 나타날 수도 있는 종류다. 나는 그 수준까지 돌리지 않았다. 이 테스트로 말할 수 있는 건 v2.55.0에서 이 짧은 구간 동안 Chrome 프로세스나 좀비가 쌓이지 않았다는 점뿐이다.

타임아웃 한계

TIMEOUT은 조정 가능한 설정으로 설명돼 있다. 실제로 작동하는지 보고 싶었다.

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

TIMEOUT=5000에서 15초 동안 페이지를 붙잡으려는 요청은 5.007초에 HTTP 408로 돌아왔다. 그냥 계속 멈춰 있지 않았다는 뜻이다. 이 한 번의 관측만으로도 설정값 근처에서 타임아웃이 걸린다는 점은 확인된다. 하지만 타이머 내부 구현이나 슬롯 정리까지 증명하는 건 아니다. 더 강한 테스트라면 재시도를 걸고, /sessions/pressure가 다시 유휴 상태로 돌아오는지 확인한 뒤, 다음 요청이 방금 풀린 슬롯을 받는지 검증해야 한다.

이행의 함정: PREBOOT는 조용히 무시된다

내가 측정한 것 중에서, 업그레이드 전에 누가 미리 알려줬으면 가장 좋았을 결과다.

Browserless 2.0.0은 PREBOOTKEEP_ALIVE를 제거했다. 변경 로그에는 둘 다 혼란스럽고, 유용성은 낮고, 오류를 유발해서 제거했다고 적혀 있다. 그 판단 자체는 이해할 만하다. 문제는 v1 설정을 v2에 그대로 복사하는 흔한 업그레이드 방식에서 생긴다.

-e PREBOOT=true로 컨테이너를 띄우고, 기본 설정과 비교해 봤다.

신호PREBOOT=true기본값, 플래그 없음
준비 시간0.716초0.776초
차가운 렌더0.314초0.318초
따뜻한 렌더0.163초0.150초
유휴 상태의 Chrome 프로세스00

모든 시간은 기본 분기에서의 min–max 범위 안에 있다. 즉, 그냥 잡음이다. 사전 워밍업도 없었다. PREBOOT=true 컨테이너는 유휴 상태에서 브라우저를 하나도 유지하지 않았고, 플래그가 없는 컨테이너와 똑같았다. 여기에 숫자 말고 다른 신호도 두 가지 있다.

  • /config에는 **preboot**라는 키가 아예 없다. 실제 키는 concurrent, queued, timeout, token, maxCPU, maxMemory, retries 등이다.
  • 에러도 없고, 경고도 없고, 컨테이너 로그에도 아무것도 없다.

즉, v1의 PREBOOT를 v2에 넣으면 정상 부팅과 로그 체크 모두에서 조용히 무시된다. /config에 그 키가 없고 동작도 안 바뀌는 것이 감지 가능한 신호다. Browserless는 명시적인 거부나 경고를 보내지 않는다. 마이그레이션 검사는 “부팅이 초록색이다”를 믿는 대신, 실제 적용된 설정을 검증해야 한다.

KEEP_ALIVE는 반대 케이스고, 둘을 섞으면 안 된다. 같은 릴리스에서 제거됐지만, 이건 조용하지 않다. 간단히 보면 컨테이너가 stdout에 Environment variable of "KEEP_ALIVE" is deprecated and ignored.라는 경고를 남긴다. 운영자에게는 적절한 경고다. 이 값을 PREBOOT와 같은 측정 프레임에서 따로 통과시키지는 않았기 때문에, 여기서는 측정이 아니라 점검으로 언급한다. 하지만 방향성은 충분히 분명하다. 조용히 망가지는 건 PREBOOT뿐이다. Browserless는 KEEP_ALIVE에 대해서는 일반적인 “v2는 v1 플래그를 다 무시한다”는 표현보다 훨씬 더 분명하다.

장점과 단점

장점

  • 수용 제어가 문서대로 정확히 동작하고 설정값에 따라 이동한다. 세 가지 서로 다른 상한에서, 클라이언트 상태 코드와 서버 자체 계산값이 동시에 일치했다.
  • /pressure는 세 번의 짧은 실행에서 실행 중, 대기 중, 거절된 수를 클라이언트와 같은 값으로 보여줬다. 자동 확장과 경보 입력으로 볼 수 있다.
  • 클라이언트 측 자동화 코드 없이 실제 Chromium 렌더링이 가능했다. 인증된 POST 한 번으로 JS로 주입된 DOM이 정적 가져오기로는 보이지 않게 드러났다.
  • 30세션 순차 실행에서 Chrome 프로세스나 좀비가 쌓이지 않았다. 종료 후 결과는 Chrome 0개, 좀비 0개였다.
  • TIMEOUT 한 번의 테스트에서 5.007초에 408이 돌아와 5.000초 근처에서 제한이 걸리는 것을 확인했다. 다만 슬롯 해제는 따로 검증하지 않았다.
  • 인증은 기본 활성화다. 토큰 없이는 네 개의 REST 엔드포인트가 모두 401을 반환한다.
  • docker run 한 줄로 약 0.78초 안에 HTTP가 준비됐고, 첫 렌더는 그 뒤 0.32초였다.

단점

  • 이미지 크기 4.34GB. 이게 실제 비용이며, 레지스트리, CI 캐시, 콜드 배포 시간에 그대로 드러난다.
  • SSPL-1.0 또는 Browserless 상용 라이선스. 배포 모델에 맞는 현재 조건을 직접 확인해야 한다.
  • v1의 PREBOOT는 v2에서 조용히 무시된다. 에러도 경고도 없고, /config에도 키가 없다.
  • 이건 의존성을 추가하는 게 아니라 서비스를 운영하는 일이다. 컨테이너, 토큰, 네트워크 경로, 수용 상한, 업그레이드 책임까지 따라온다.
  • 대기 요청은 (2, 2) 실행에서 응답 시간을 세션 시간의 거의 두 배로 늘렸다. 큐 대기는 별도로 재지 않았다.
  • 측정한 REST 시간은 HTTP 왕복을 포함하므로, 브라우저 시작 비용이나 재사용 여부를 분리해 주지 않는다.

누가 써야 하고, 누가 피해야 하나

Browserless는 여러 곳에서 브라우저가 필요할 때 가치가 있다. 여러 앱이 함께 쓰는 렌더링 서비스, 각 서비스에 Chrome 의존성을 넣는 대신 HTTP 뒤에서 스크린샷이나 PDF를 뽑고 싶은 팀, 또는 실제로 측정 가능한 백프레셔와 함께 용량 한계를 두고 싶은 작업 파이프라인이라면 잘 맞는다. 이미 Docker를 쓰고 있고 배포 운영 담당이 있다면, 운영 그림도 꽤 명확하다. 예측 가능한 수용, 보이는 역압, 그리고 30세션 순차 점검에서 쌓이지 않은 Chrome/좀비 프로세스까지 확인됐다.

기술 스택 안의 각 서비스가 자기만의 Chromium을 따로 깔아야 하는 상황이라면 이것도 좋은 선택이다. 그걸 하나의 컨테이너에 묶고 토큰과 한도로 관리하는 건 충분히 합리적인 아키텍처적 타협이다.

반대로, 그냥 단일 스크립트 하나만 돌리려는 거라면 과하다. Python 파일 하나가 렌더된 페이지를 가져오게 하려고 4.3GB 이미지를 당겨서 컨테이너를 운영하는 건 너무 많은 의식이다. 이런 일은 인프로세스 브라우저 라이브러리로도 충분히 된다. 상용 제품의 SSPL 조건이 맞지 않는다면 피해야 한다. 그리고 진짜 원하는 게 “미리 준비된 브라우저”라면 PREBOOT는 v2에서 그걸 주지 않는다. 또 문제의 본질이 봇 방어라면, 내가 일부러 시험하지 않은 엔드포인트에 기대게 된다.

대안, 그리고 Thunderbit의 위치

비교해야 할 대상은 Browserless와 다른 컨테이너가 아니다. 브라우저가 어디에서 돌고, 누가 계속 살아 있게 할 책임을 지는가가 핵심이다.

관련 리뷰: Browsertrix Crawler 리뷰.

관련 리뷰: chromedp 리뷰.

브라우저 라이브러리 (chromedp, rod, Selenium, Playwright)자체 호스팅 BrowserlessThunderbit 관리형 추출
브라우저가 실행되는 곳네 프로세스 내부네 컨테이너 내부타사 인프라
설정 비용패키지 설치4.3GB 이미지 + 컨테이너 + 토큰API 키
여기서 측정한 시간이 글에서 측정하지 않음준비 후 첫 렌더 0.32초, 후속 요청 중앙값 0.15초이 글에서 측정하지 않음
네가 쓰는 것명시적 대기 로직이 들어간 자동화 코드문서화된 POST 한 번HTTP 호출 한 번
돌아오는 것네가 직접 정의한 값HTML, PNG/PDF 시그니처가 맞는 응답, 추출된 노드제품에 따라 구조화된 JSON 또는 Markdown
용량 한도네 장비CONCURRENT + QUEUED 이후 429서비스 플랜
장애 대응 책임네가 맡음네가 맡음그들이 맡음

브라우저를 네 프로세스 안에 두고, 직접 기다림 로직을 쓰는 게 괜찮다면 라이브러리가 더 가볍고 서비스도 없다. 이 쪽은 Playwright vs Puppeteer 비교오픈소스 추출 프로젝트 모음에서 더 자세히 다뤘다.

아예 브라우저를 직접 운영하고 싶지 않다면, Thunderbit 같은 관리형 विकल्प이 있다. Browserless는 렌더된 결과를 네 코드가 해석하는 방식이고, Thunderbit는 렌더링 인프라를 서비스 쪽이 맡으면서 Markdown이나 스키마화된 데이터를 돌려줄 수 있다. 이번 글은 Thunderbit의 속도, 용량, 실패 동작, 추출 품질, 비용을 측정하지 않았으므로, 이 표는 성능 비교가 아니라 책임 경계를 설명하는 것이다.

같은 테스트 계열에서 읽을 만한 글로는, 브라우저 기반 Markdown 라인을 직접 운영하는 Crawl4AI 리뷰와 더 넓은 범위를 정리한 웹 스크래핑 도구 개요가 있다.

웹 데이터 추출에 Thunderbit를 사용해 보세요

최종 판단

Browserless를 써야 할까? 여러 곳에서 브라우저 작업이 필요하고, 컨테이너를 운영할 사람이 있으며, 배포 모델이 라이선스 검토를 통과한다면 그렇다. 세 번의 합성 수용 테스트에서 실제 허용 개수는 CONCURRENT + QUEUED와 같았고, 초과분은 429를 받았다. /pressure도 클라이언트가 보는 수치와 맞았다. 별도의 30세션 순차 점검에서는 Chrome이나 좀비 프로세스가 쌓이지 않았다. 타임아웃 테스트 한 번도 설정값 근처에서 408을 반환했다. 이런 점들은 분명 유용하지만, 일반적인 보증은 아니다.

다만 운영 부담은 분명히 봐야 한다. 이건 4.34GB 이미지와 직접 운영해야 하는 서비스이지, 그냥 의존성 하나 추가하는 게 아니다. 그리고 이번 테스트는 이것이 인프로세스 브라우저 라이브러리보다 더 빠르다는 걸 증명하지 않았다. 네가 사는 것은 브라우저 그 자체가 아니라 조절 가능한 브라우저다. 즉, 상한이 있고 역압을 측정할 수 있다. 30세션 순차 테스트에서 Chrome이나 좀비 프로세스는 쌓이지 않았다. 대신 배포 비용과 반드시 읽어야 하는 라이선스가 있다. 단일 스크립트로 몇 페이지만 가져오려는 경우엔 그 대가가 너무 크다. 하지만 여러 서비스가 의존하는 렌더링 계층이 필요하다면 충분히 가치가 있다. 단, 마이그레이션할 때 v1 환경 변수를 그대로 복사하지 말아야 한다. PREBOOT는 멀쩡히 작동하는 것처럼 보이지만 실제로는 아무것도 하지 않는다.

웹 데이터 추출에 Thunderbit를 사용해 보세요 Get Started Free

자주 묻는 질문

Browserless는 Chrome 없는 상태를 더 빠르게 만들어 주나?
이번 테스트로는 그걸 답할 수 없다. docker run 후 0.78초에 HTTP 엔드포인트가 응답 가능해졌고, 첫 /content는 0.32초, 이후 같은 컨테이너 요청은 약 0.15초였다. 하지만 이 수치는 HTTP 왕복, 브라우저 작업, 이동, 응답 전달이 모두 합쳐진 값이다. 프레임은 시작 시간만 따로 떼지 못했고, 프로세스 재사용도 추적하지 못했으며, 인프로세스 동등 벤치마크도 내지 않았다. Browserless는 공용 서비스 상한과 수용 제어용으로 쓰고, 지연 경로는 직접 측정하는 게 맞다.

Browserless에서 동시성 한도를 넘기면 어떻게 되나?
HTTP 429를 바로 받는다. 상한은 정확히 CONCURRENT + QUEUED와 같았고, 세 가지 설정에서 이를 확인했다. (2,2)에서는 4개 성공, 4개 거절. (3,5)에서는 8개 성공, 4개 거절. (5,5)에서는 10개 성공, 4개 거절. 서버의 /pressure도 각 경우 실행 중, 대기 중, 최근 거절 수가 동일했다. 기억할 점은 대기열에 들어간 요청은 실패하지 않고 기다린다는 것이다. (2, 2)와 5초 작업에서는 성공 응답이 5.7초~11.0초 사이였다. 클라이언트는 429를 역압으로 보고, 재시도와 지수적 백오프를 넣어야 한다.

Browserless v2에서 PREBOOT는 아직 작동하나?
아니다. PREBOOT는 2.0.0에서 제거됐고, v2는 -e PREBOOT=true를 넣어도 에러나 경고 없이 그냥 무시한다. 세 가지 방식으로 확인했다. 시간은 기본값과 차이가 없었고, PREBOOT=true 컨테이너의 유휴 Chrome 프로세스 수는 0이었으며, /config에도 preboot 키가 없다. v1 설정을 옮기는 경우, 그 복사는 사전 워밍업이 아니다. 같은 릴리스에서 제거된 KEEP_ALIVE는 stdout에 "deprecated and ignored" 경고를 남기므로, 조용히 실패하는 문제는 PREBOOT에만 해당한다.

Browserless는 상업적으로 무료인가?
저장소는 SSPL-1.0 또는 Browserless 상용 라이선스를 제시하지만, 이 글은 구체적인 상용 또는 닫힌 소스 사례를 어느 쪽에 해당하는지 연결하지 않는다. 현재 LICENSE공식 배포 문서를 확인한 뒤, 소프트웨어 라이선스 담당자가 배포 및 유통 모델을 판단해야 한다.

Browserless가 좀비 Chrome 프로세스를 남기나?
이번 짧은 관측 창에서는 쌓이지 않았다. 30세션 순차 실행 후 컨테이너에는 Chrome 프로세스 0개, 좀비 0개만 남았고, dumb-init, node, Xvfb, start.sh, sh만 있었다. 활성 세션 중에는 Chrome 계열 프로세스 11개를 셌기 때문에, 측정기가 제대로 작동한다는 것도 확인했다. 컨테이너 메모리는 294MiB에서 303MiB로 올라간 뒤 안정됐다. 이건 수시간 지속 테스트도 아니고, 높은 동시성 테스트도 아니고, 수천 세션 테스트도 아니다.

Ke
Ke
المدير التقني في Thunderbit | عالم بيانات أول وخبير في تعلّم الآلة بخبرة تقارب عقدًا من الزمن في تعلّم الآلة وعلم البيانات، كيه شين خريج جامعة كولومبيا وكان سابقًا عالم بيانات أول في Walmart Labs. وبفضل خبرته العميقة المعترف بها من قبل الأقران في Python وR وJava والإحصاء، يشارك رؤى مجرّبة حول نقل خوارزميات الذكاء الاصطناعي المعقدة من النظرية إلى بنية جاهزة للإنتاج.
فهرس المحتويات
Thunderbit · وكيل بيانات الويب بالذكاء الاصطناعي

استخرج البيانات من أي صفحة في بنقرة واحدة

موثوق لدى أكثر من 250,000 مستخدم
تتوفر خطة مجانية
من صفحة ويب إلى جدول بيانات
صف ما تحتاجه — ويقوم وكيل Thunderbit بالذكاء الاصطناعي باستخراجه وتصديره إلى Excel أو Google Sheets أو Airtable أو Notion. ابدأ مجانًا.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week