왜 당신의 scraper가 5‑6개의 동시 Chrome 인스턴스에서 정체되는가 (그리고 아무도 이름 붙이지 않는 shared‑cookie 함정)

발행: (2026년 4월 23일 PM 12:35 GMT+9)
8 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content you want converted to Korean (excluding the source link you’ve already provided)? Once I have the article text, I’ll keep the source line unchanged and translate the rest while preserving all formatting, markdown, and technical terms.

Why tabs break Turnstile (and other CF challenges)

Cloudflare의 Turnstile 위젯은 하나의 Chrome 프로세스에서 다중 탭 스크래핑에 적대적인 두 가지 동작을 합니다:

  • document.visibilityState를 확인합니다. 백그라운드 탭은 hidden을 보고하고, 위젯의 챌린지 스크립트는 visible 전환을 기다리며 중단되거나 포기합니다. 이것이 OP가 “Turnstile이 포커스되지 않은 페이지에서 제대로 로드되지 않는다”고 관찰한 현상입니다.
  • 쿠키는 전체 브라우저 프로필에 걸쳐 공유됩니다. 두 탭이 같은 오리진에서 CF 챌린지를 동시에 시작하면, 동일한 cf_clearance 쿠키 슬롯을 두고 경쟁하게 됩니다. 먼저 포커스된 탭이 토큰을 기록하고, 다른 탭의 챌린지는 상태 불일치를 감지해 차단됩니다.

스레드의 답변 중 하나가 이를 간단히 요약했습니다: “쿠키는 브라우저 내 모든 탭 사이에 공유되며, 여러 챌린지가 서로 차단될 수 있다.” 이것이 전체 이야기입니다. 포커스는 단지 증상일 뿐이며, 실제 충돌은 쿠키 경쟁에서 발생합니다.

“5‑6 browsers max”가 잘못된 이유

그 5‑6개의 Chrome 프로세스가 무엇을 하고 있는지 프로파일링하면 다음을 볼 수 있습니다:

  • 각각 ~200 MB RSS, 대부분 V8과 렌더러에서 할당된 힙
  • 페이지 로드/페인팅을 기다리며 돌아가는 두세 개의 렌더 스레드
  • 네트워킹 및 암호화를 위한 워커 스레드 풀

16 GB / 8‑코어 머신에서는 메모리보다 CPU가 한계이며, 페이지 로드마다 전체 Chromium 렌더링과 챌린지 스크립트용 JS 실행이 트리거되기 때문입니다 — 이는 의도적으로 비용이 많이 들게 설계되었습니다 (즉, proof‑of‑work의 “작업” 부분).

따라서 실제 한계는 “RAM에 들어가는 Chrome 바이너리 수”가 아니라 “CPU가 동시에 해결할 수 있는 CF 챌린지 수”입니다. OP가 5‑6개의 브라우저로 분당 30개의 풀이를 기록한 경우, 브라우저당 약 5 풀이/분, 즉 10‑12초에 하나 정도가 됩니다. 이는 차가운 프로파일에서 챌린지가 소요되는 시간과 일치합니다.

프로필 격리 수정

이 회피는 더 많은 스레드나 탭을 만드는 것이 아니라 캐시된 클리어런스를 이용한 프로필 격리입니다.

아이디어

  1. Chrome 프로필 풀을 미리 준비합니다(예: 20개). 각 프로필이 CF 챌린지를 한 번씩 풀고 얻은 cf_clearance 쿠키를 저장합니다.
  2. 스크래핑 요청이 들어올 때마다, 아직 클리어런스가 만료되지 않은 프로필을 선택합니다(CF 클리어런스는 보통 ~30분 지속됩니다).
  3. 해당 프로필로 스크랩을 실행합니다. 클리어런스가 이미 존재하므로 챌린지가 실행되지 않아 10초짜리 작업 증명이 생략됩니다.
  4. 프로필의 클리어런스가 만료되면, 백그라운드에서 조용히 다시 워밍업합니다.

이 구조에서는 병목이 “브라우저당 CF 챌린지 연산”에서 “페이지당 네트워크 지연”으로 이동하고, 수십 개의 동시 요청을 손쉽게 확장할 수 있습니다.

실제 운영 결과

  • 20개의 프로필을 미리 워밍업
  • CF 보호 대상 페이지에서 평균 로드 시간 약 1.5 초(워밍된 클리어런스 사용)
  • “페이지당 새 브라우저를 사용하고 매번 챌린지를 푸는 경우” 대비 약 8배 높은 처리량

browser-act 로 수행하기

browser-act는 프로필 풀을 관리해 주는 CLI입니다 — 각 browser-id는 자체 쿠키와 스토리지를 가진 격리된 프로필입니다.

# Install:
npx skills add browser-act/skills --skill browser-act

# Create 20 profiles:
for i in $(seq 1 20); do
  browser-act browser create --profile-name "scrape-$i"
done

# Warm each profile by opening the target site once (runs the CF challenge):
browser-act --session warmup browser open https://target.site

# Later, from your scraper, pick a warm profile and run:
browser-act --session scrape browser open https://target.site/page-N
browser-act --session scrape get markdown > page-N.md

쿠키는 프로필당 디스크에 지속되므로, 스크레이퍼를 재시작해도 쿠키가 사라지지 않습니다.

논쟁할 만한 점들

  • 대상 사이트가 요청마다 도전 정책을 회전한다면(일부 은행/도박 사이트가 그렇습니다) 이것은 도움이 되지 않습니다. 이는 다른 상황이며 — JS 솔버 루프가 필요합니다.
  • ~20개의 프로필이 단일 머신에서 수익 감소가 시작되는 지점입니다. 그 이상이면 별도의 인스턴스와 별도 IP에 배치하세요 — cf_clearance 쿠키는 IP에 묶여 있습니다.
  • 만약 분당 ~30페이지만 필요하다면 OP의 5‑6 브라우저 설정으로 충분합니다. 프로필 격리 방식은 분당 ~100페이지를 초과할 때 중요해집니다.

Full r/webscraping thread: optimised chrome? for multi threading. 같은 한계에 부딪히고 있다면 지금까지 시도한 것을 내려놓고 — 비교해 보겠습니다.

0 조회
Back to Blog

관련 글

더 보기 »