Jsoup · Selenium · Playwright · BeautifulSoup · Puppeteer, 뭘 언제 쓸까
Jsoup
Selenium
Playwright
BeautifulSoup
Puppeteer
역할이 다 다르다.
예시 시나리오 외부 API를 주기적으로 호출해서 JSON으로 저장하던 예시 수집기가 있다. 이제 여기에 "API로 제공되지 않는 사이트의 데이터를 긁어오는 크롤링 기능"을 붙여야 한다.
크롤링의 두 가지 큰 갈래
먼저 도구들을 크게 두 부류로 나눠야 한다. 이 구분이 모든 것의 시작이다.
1. HTML 파서 계열
- HTTP 요청으로 HTML을 받아오고, 문자열을 파싱해서 원하는 값을 뽑는다.
- "브라우저를 흉내 내지 않는다."
- 대표: Jsoup (Java), BeautifulSoup (Python)
2. 브라우저 자동화 계열
- 진짜 브라우저(Chromium 등)를 띄워서 페이지를 열고 JS를 실행한 뒤 결과 DOM을 본다.
- "사람이 크롬 여는 것과 똑같이 동작한다."
- 대표: Selenium, Playwright, Puppeteer
이 구분이 왜 중요하냐면, 요즘 웹의 절반 이상은 JavaScript로 렌더링되기 때문이다.
<!-- 서버가 준 원본 HTML -->
<div id="root"></div>
<script src="app.js"></script>이런 페이지를 Jsoup으로 가져오면 <div id="root"></div> 만 나온다. 실제 데이터는 브라우저가 JS를 실행한 뒤에야 채워진다. HTML 파서로는 손도 못 댄다.
반대로 브라우저 자동화 도구는 무겁고 느리다. 서버 자원도 훨씬 많이 먹는다.
즉 모든 크롤링의 첫 질문은 "이 사이트가 JS 렌더링이 필요한가?" 다.
다섯 도구 한눈에 보기
| 도구 | 언어 | 계열 | JS 실행 | 특징 |
|---|---|---|---|---|
| Jsoup | Java/Kotlin | HTML 파서 | ❌ | JVM 진영의 표준. 가볍고 빠름 |
| BeautifulSoup | Python | HTML 파서 | ❌ | Python 진영의 표준. requests와 짝 |
| Selenium | 다국어 (Java, Python, JS 등) | 브라우저 자동화 | ✅ | 가장 오래된 표준. 테스트 자동화 뿌리 |
| Playwright | 다국어 (JS, Python, Java, .NET) | 브라우저 자동화 | ✅ | MS가 만든 차세대. Selenium 대안 |
| Puppeteer | JS/TS | 브라우저 자동화 | ✅ | Google 제작. Chrome 전용 |
HTML 파서 계열
Jsoup — JVM 진영의 표준
어떻게 동작하나?
- 내부 HTTP 클라이언트로 URL에 GET 요청을 날린다.
- 응답으로 받은 HTML 문자열을 파싱해서 DOM Tree를 만든다.
- CSS Selector나 DOM 탐색 API로 원하는 노드를 뽑는다.
Document doc = Jsoup.connect("https://example.com/list")
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
Elements titles = doc.select("div.article > h2.title");
titles.forEach(t -> log.info(t.text()));장점
- 가볍다. 라이브러리 하나 추가하면 끝. 브라우저 프로세스도 필요 없음.
- 빠르다. 페이지 하나 처리에 수십~수백 ms.
- Spring Boot에 그대로 녹인다. 별도 프로세스 관리 불필요.
- CSS Selector가 강력하다.
단점
- JS 실행 못 함. SPA는 못 긁는다.
- 로그인·쿠키·세션은 직접 관리해야 함 (일부는 지원하지만 브라우저만큼 자연스럽지 않음).
- 최신 웹의 안티봇(Cloudflare 등)에 상대적으로 취약.
언제 쓰나? 서버가 HTML을 완성해서 주는 사이트. 뉴스, 블로그, 위키, 오래된 관리자 페이지, 공공데이터 페이지 등.
BeautifulSoup — Python 진영의 사촌
Jsoup와 거의 같은 포지션이다. 다만 Python이라는 점이 다르다.
import requests
from bs4 import BeautifulSoup
html = requests.get("https://example.com/list").text
soup = BeautifulSoup(html, "html.parser")
for h2 in soup.select("div.article > h2.title"):
print(h2.get_text())Jsoup와의 차이
| 항목 | Jsoup | BeautifulSoup |
|---|---|---|
| HTTP 요청 | 내장 (Jsoup.connect(...)) | 별도 라이브러리 (requests, httpx) |
| 파싱 엔진 | 자체 파서 (관대함) | html.parser / lxml / html5lib 선택 가능 |
| Selector | CSS Selector 강력 | CSS Selector + find_all DSL |
| 언어 진영 | JVM 표준 | Python 표준 |
기능적으로는 대체로 대등하다. 팀의 언어 스택을 따라가는 게 답이다. Spring Boot 프로젝트에 갑자기 Python 서비스를 붙일 이유는 없다.
브라우저 자동화 계열
세 도구(Selenium, Playwright, Puppeteer)는 진짜 브라우저를 원격 제어한다는 점에서 같다. 차이는 "어떻게 제어하느냐"에 있다.
공통 원리: 브라우저 제어 프로토콜
[내 코드]
│ 명령 (예: goto, click, waitFor)
▼
[드라이버 / 프로토콜 계층]
│ WebDriver Protocol 또는 Chrome DevTools Protocol(CDP)
▼
[실제 브라우저 프로세스 (Chrome/Firefox 등)]
│ 페이지 로드, JS 실행, DOM 변경
▼
[내 코드] → DOM/스크린샷/네트워크 응답 회수
핵심은 브라우저를 프로세스로 띄운다는 점이다. 그래서 무겁고 느리다. 하지만 사람이 크롬 여는 것과 똑같이 JS까지 실행된 최종 화면을 볼 수 있다.
Selenium
- 2004년부터 있어온 가장 오래된 표준.
- WebDriver 프로토콜을 사용. W3C 표준.
- 다국어 지원. Java 진영에서 첫 선택지가 이거였다.
- 원래 뿌리는 E2E 테스트 자동화. 크롤링은 부차적.
WebDriver driver = new ChromeDriver();
driver.get("https://spa-example.com");
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".loaded")));
List<WebElement> items = driver.findElements(By.cssSelector(".item"));장점: 표준. 문서 풍부. 다양한 브라우저 지원. 단점: 상대적으로 느림. API가 오래되어 대기(wait) 처리가 번거로움. 최신 웹(Shadow DOM, iframe)에서 다루기 까다로움.
Playwright
- MS가 2020년경 내놓은 후발주자. Selenium을 겨냥한 "다음 세대".
- CDP(Chrome DevTools Protocol) 기반. Firefox·WebKit은 자체 패치 브라우저.
- 자동 대기(auto-waiting):
click()이 알아서 요소가 나타날 때까지 기다린다. - 네트워크 가로채기(intercept), 여러 컨텍스트 격리, 병렬 실행이 기본 지원.
- Java 바인딩 있음.
try (Playwright pw = Playwright.create()) {
Browser browser = pw.chromium().launch();
Page page = browser.newPage();
page.navigate("https://spa-example.com");
page.waitForSelector(".loaded");
List<ElementHandle> items = page.querySelectorAll(".item");
}장점: Selenium보다 빠르고 안정적. API가 현대적. 안티봇 회피 옵션도 상대적으로 강함. 단점: 상대적으로 후발이라 몇몇 legacy 환경 지원이 부족. 브라우저 바이너리를 자체 관리.
Puppeteer
- Google이 만든 Chrome 자동화 라이브러리. Node.js 전용(공식은).
- CDP 기반. Playwright의 뿌리가 여기.
- Chrome/Chromium에 특화. 파이어폭스도 실험적으로 지원.
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://spa-example.com');
await page.waitForSelector('.loaded');
const items = await page.$$('.item');장점: Chrome과 완벽 궁합. 성능·안정성 좋음. 단점: Node.js 전용이라 JVM 백엔드에선 별도 워커를 두거나 Playwright로 우회해야 함.
셋 중에 뭘 골라야 하나 (Java/Spring Boot 관점)
- 이미 Selenium을 쓰고 있으면 → 굳이 옮길 이유 없음
- 새로 시작한다면 → Playwright(Java) 가 가장 현대적이고 안정적
- Node.js 크롤러를 따로 붙일 수 있으면 → Puppeteer도 훌륭
실제 기업에서는 어떻게 선택하나
정리된 사례를 몇 가지 패턴으로 보면:
패턴 1: HTML 파서 우선 + 필요할 때만 브라우저
가장 흔한 실무 패턴이다.
- Jsoup으로 먼저 시도.
- 응답 HTML에 원하는 데이터가 없으면 (JS 렌더링 사이트) → Playwright/Selenium으로 fallback.
- 브라우저 자동화는 비싸므로 최후의 수단.
이유: 브라우저 자동화는 페이지당 처리 시간이 10~100배, 서버 자원(메모리, CPU)은 수십 배 든다. 만 개 페이지를 다 브라우저로 열면 서버가 죽는다.
패턴 2: 정적 사이트 대량 → HTML 파서만
뉴스 수집, 상품 카탈로그, 공공데이터 등. 사이트가 서버 렌더링이라면 Jsoup/BeautifulSoup만으로 하루 수백만 페이지도 처리 가능.
패턴 3: SPA 필수 → 브라우저 자동화만
React/Vue 기반의 최신 서비스, 로그인 후 세션 유지가 복잡한 사이트, 캡차·안티봇이 심한 사이트.
Playwright/Puppeteer로 처리하고, 인스턴스 풀·큐잉·크레딧 관리를 함께 설계한다. Kubernetes 위에서 Chromium Pod를 여러 개 띄우는 구조가 흔하다.
패턴 4: 하이브리드
- 리스트 페이지는 Jsoup으로 빠르게 URL만 수집
- 상세 페이지는 Playwright로 JS 렌더링 결과 파싱
- 두 파이프라인을 큐로 연결
수집기 규모가 커지면 자연스럽게 이 형태로 간다.
예시 수집기에 적용한다면
지금 예시 수집기의 상태를 정리하면:
- Scheduler(@Scheduled) → 10분마다 트리거
- Spring Batch → Chunk 단위 처리, JobRepository로 이력 관리, 재시작 지원
- 외부 API 호출 → JSON 저장 (완료)
여기에 크롤링을 붙일 때 자연스러운 구조는 이렇다.
[Batch Job: dataCollectionJob]
├── Step: apiCollectStep ── 기존 (API 호출)
├── Step: jsoupCrawlStep ── 신규 (정적 HTML 사이트)
└── Step: browserCrawlStep ── 신규 (JS 렌더링 사이트)
jsoupCrawlStep: Chunk 지향. Reader가 크롤 대상 URL 목록을 뱉고, Processor가 Jsoup으로 HTML 파싱, Writer가 JSON 저장.browserCrawlStep: 필요할 때만. Playwright(Java)로 페이지 열어서 렌더링된 DOM 수집. Chunk size는 작게(2~5) 잡아 리소스 폭발 방지.
중요한 판단 포인트
- 새 사이트를 추가할 때 무조건 Jsoup으로 먼저 시도한다. 되면 그걸로 끝.
- Jsoup으로 원하는 값이 안 나오면 → 브라우저 개발자 도구의 Network 탭을 열어보자. JS가 뒤에서 호출하는 내부 JSON API가 보인다면 그 API를 직접 때리는 게 최고다 (=크롤링이 아니라 다시 API 호출).
- 그마저도 안 되는 진짜 SPA만 Playwright로 처리.
이 순서를 지키면 수집기의 90%는 Jsoup으로, 10%만 브라우저로 돌아간다.
정리
- 크롤링 도구는 크게 HTML 파서 vs 브라우저 자동화로 갈린다.
- JS 렌더링이 필요한가가 첫 번째이자 가장 중요한 갈림길.
- Java/Spring Boot 백엔드라면: Jsoup을 기본, Playwright(Java) 를 fallback.
- BeautifulSoup는 Python판 Jsoup, Puppeteer는 Node.js판 Playwright에 가깝다.
- 브라우저 자동화는 비싼 도구다. 최후의 수단으로 미뤄라.
- 수집기 아키텍처는 Jsoup Step + Browser Step을 분리해서 리소스 계획을 따로 잡는 것이 좋다.
다음 글에서는 Jsoup 내부를 뜯어본다. HTTP 요청부터 DOM Tree 생성, CSS Selector 검색이 어떻게 이루어지는지 단계별로 본다.