1️⃣ 문제 상황
Spring Boot 기반 크롤러 프로젝트에서 Playwright(Java) 를 이용해 Danawa 상품 리뷰를 병렬로 크롤링하던 중, 다음과 같은 에러가 반복적으로 발생했습니다:
TargetClosedError: Target page, context or browser has been closed
PlaywrightException: Cannot find object to call __adopt__: browser-context@xxxxxxx
- 병렬 처리: Virtual Thread 기반
- 프록시:
socks5://127.0.0.1:1080(로컬 프록시 서버) - 각
Catalog마다browser.newContext()호출
2️⃣ 원인 분석
Playwright는 Java ↔ JS 런타임 간 통신을 통해 객체를 관리합니다.
특히 browser.newContext()는 내부적으로 context 객체를 등록(adopt) 하며 통신을 진행합니다.
하지만 병렬 환경에서 생성 직후 close()를 호출하거나
JS 통신이 완료되기 전에 GC 되면 아래와 같은 문제가 발생합니다:
- TargetClosedError: 페이지나 컨텍스트가 조기에 닫힘
- __adopt 에러: context 객체가 아직 등록되기 전에 닫힘
특히 프록시를 설정한 상태에서는 네트워크 레이턴시로 인해 이 타이밍 문제가 더욱 자주 발생했습니다.
3️⃣ 시도했던 해결 방법들
| 시도 | 결과 |
| Catalog 마다 browser.newContext() 후 즉시 page.navigate() | ❌ context가 adopt 되기 전에 GC 되는 경우 발생 |
| context.close() 를 try-finally 에 감싸기 | ❌ 일부는 해결되지만 여전히 오류 발생 |
| Headless 모드 OFF | 🔍 디버깅에 도움은 됐지만 본질적인 해결은 아님 |
4️⃣ 해결 방법 ✅
변경된 구조
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch(...);
for (Catalog catalog : catalogs) {
BrowserContext context = browser.newContext(...);
// 컨텍스트 adopt 시점까지 대기
Thread.sleep(2000); // 또는 page.waitForTimeout(200);
Page page = context.newPage();
page.navigate(...);
context.close();
}
🔑 핵심은 context가 완전히 등록될 시간을 보장해주는 것!
즉, 생성 직후 바로 폐기하지 않도록 지연을 두어야 race condition을 피할 수 있습니다.
5️⃣ 결과
- 병렬 스레드 2개 × 총 2560개의 상품
- 재시도 로직 없이도 100% 정상 크롤링 완료
- 3시간 이상 장기 실행 테스트도 성공
- 리뷰 저장 중복 제거 및 Proxy 정상 작동 확인
6️⃣ 마무리
Playwright는 매우 강력하지만, Java 병렬 환경에서는 JS 런타임과의 타이밍 이슈를 주의해야 합니다.
이번 경험을 통해 다음 교훈을 얻었습니다:
- 병렬 처리 시 리소스 생성/해제 시점은 명확히!
- Playwright context는 adopt 되기 전에 close 하면 안 됨
- 네트워크 환경(프록시 포함)에 따른 속도 차이 고려
비슷한 문제를 겪고 있다면 context 생성 후 약간의 대기시간을 주고, close는 확실하게 try-finally 또는 스코프 제어로 감싸보세요!
'SPRING' 카테고리의 다른 글
| [SPRING] 스프링 로그 모니터링 전 로그 설계 (2) | 2025.06.19 |
|---|---|
| [250602 트러블 슈팅] 크롤링 도중에 IP밴 (3) | 2025.06.02 |
| [SPRING] Lettuce 분산 락 트러블슈팅 (1) | 2025.05.28 |
| [SPRING] Lettuce 분산 락 구현 (1) | 2025.05.27 |
| [SPRING] 동시성 제어 분산 락 (2) | 2025.05.26 |