Java에서 웹 크롤링을 구현할 때 가장 많이 쓰이는 라이브러리가 Jsoup입니다. HTTP 요청부터 HTML 파싱, CSS 셀렉터 기반 데이터 추출까지 한 라이브러리로 처리할 수 있습니다. 유로 환율 알림 프로젝트에서 실제로 사용한 내용을 정리합니다.
의존성 추가
// build.gradle
implementation 'org.jsoup:jsoup:1.17.2'
기본 사용법
Jsoup.connect()로 URL에 요청을 보내고 Document 객체를 받아옵니다. 이후 CSS 셀렉터로 원하는 요소를 추출합니다.
Document doc = Jsoup.connect("https://example.com")
.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
.timeout(5000)
.get();
// CSS 셀렉터로 요소 추출
String title = doc.select("h1.title").text();
Elements items = doc.select("ul.list > li");
for (Element item : items) {
System.out.println(item.text());
}
환율 크롤링 실제 예시
네이버에서 유로 환율을 크롤링하는 코드입니다. 개발자 도구로 환율 숫자가 담긴 HTML 요소의 클래스명을 확인하고 셀렉터를 작성했습니다.
@Service
public class ExchangeRateCrawler {
public BigDecimal fetchEuroRate() {
try {
Document doc = Jsoup.connect("https://finance.naver.com/marketindex/")
.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/120.0.0.0 Safari/537.36")
.timeout(5000)
.get();
// 환율 요소 선택 후 텍스트 추출
String rateText = doc.select("div.환율셀렉터 span.value")
.first()
.text()
.replace(",", "");
return new BigDecimal(rateText);
} catch (IOException e) {
log.error("환율 크롤링 실패", e);
throw new RuntimeException("환율 조회 실패", e);
}
}
}
자주 쓰는 셀렉터 패턴
// 클래스로 선택
doc.select(".className")
// ID로 선택
doc.select("#elementId")
// 태그 + 클래스 조합
doc.select("span.price")
// 자식 요소
doc.select("ul > li")
// 속성값으로 선택
doc.select("a[href*=finance]") // href에 finance 포함
// 첫 번째 요소만
doc.select("div.list").first()
// 텍스트 추출
element.text() // 자식 포함 전체 텍스트
element.ownText() // 직접 텍스트만
// 속성값 추출
element.attr("href")
element.attr("src")
User-Agent 설정이 중요한 이유
User-Agent 없이 요청을 보내면 많은 사이트가 403 오류를 반환하거나 빈 응답을 줍니다. Jsoup의 기본 User-Agent는 크롤러로 식별되기 때문에 실제 브라우저 User-Agent를 사용하는 것이 중요합니다.
Jsoup.connect(url)
.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/120.0.0.0 Safari/537.36")
.header("Accept-Language", "ko-KR,ko;q=0.9")
.timeout(5000)
.get();
스케줄러와 조합하기
환율 알림 프로젝트에서는 1분마다 크롤링하고, 이전 값과 비교해서 10원 이상 차이가 나면 카카오톡을 전송했습니다.
@Scheduled(fixedDelay = 60000) // 1분마다
public void checkExchangeRate() {
BigDecimal current = crawler.fetchEuroRate();
String prevStr = redis.get("euro:rate");
if (prevStr != null) {
BigDecimal prev = new BigDecimal(prevStr);
BigDecimal diff = current.subtract(prev).abs();
if (diff.compareTo(new BigDecimal("10")) >= 0) {
kakaoService.sendMessage(
"유로 환율 변동: " + prev + " → " + current
);
}
}
redis.set("euro:rate", current.toPlainString());
}
주의사항
- 사이트 정책 확인: robots.txt와 이용약관을 먼저 확인하세요. 크롤링을 명시적으로 금지한 사이트는 접근하면 안 됩니다.
- 요청 간격: 과도한 요청은 서버에 부하를 주고 IP가 차단될 수 있습니다. 적절한 딜레이를 두세요.
- HTML 구조 변경: 사이트의 HTML이 바뀌면 셀렉터가 동작하지 않습니다. 크롤링 실패 시 알림을 받을 수 있도록 에러 핸들링을 꼼꼼하게 해두는 것이 좋습니다.
- SPA 대응: JavaScript로 동적으로 렌더링하는 사이트는 Jsoup으로 데이터를 가져올 수 없습니다. 이 경우 Selenium이나 Playwright 같은 도구가 필요합니다.