Jsoup 라이브러리를 통한 정적 페이지 크롤링
Spring

Jsoup 라이브러리를 통한 정적 페이지 크롤링

- 발생한 문제

1. 카카오 Local API를 사용하여 키워드를 통한 장소 검색을 구현.

2. 이 때, 장소에 해당하는 대표 이미지를 함께 보내주어야 하는데, 카카오 Local API의 장소 Response에는 대표 이미지가 없음.

 

- 시도한 방법 1 (실패) : OpenGraph 

OpenGraph란, HTML 문서에 대해서 다양한 메타데이터를 통일성있게 제공할 수 있도록 Facebook에서 만든 프로토콜이다.

HTML 문서의 meta 태그 중 "og:" prefix로 시작하는 부분이 OpenGraph 프로토콜 관련 메타데이터이다.

이 OpenGraph의 경우, url에 해당하는 메타데이터로 title(제목), url, image(썸네일 이미지)를 포함하고 있다.

예를 들어, 네이버의 경우 HTML 소스 중 head 부분을 확인하면 다음과 같이 OpenGraph 관련 메타데이터를 포함한다.

이는 메신저로 네이버 url을 전송해보면 메타데이터가 명확하게 드러난다.

"네이버"라는 title과 url, 그리고 해당하는 썸네일 이미지가 사용자가 인지하기 편하도록 제공된다.

 

카카오 Local API에서 장소 이미지는 제공해주지 않지만 해당 장소에 대한 카카오 장소 상세 페이지 url을 제공해주므로, 해당 url의 og image url을 추출하려 했다.

 

하지만..

 

썸네일이 지도,,

카카오 Local API에서 제공해주는 장소 상세 페이지 url의 썸네일 이미지는 위와 같이 지도 상의 위치로 설정되어 있었고, OpenGraph를 다시 한 번 공부해보는 계기로만 그치게 되었다.

 

 

- 시도한 방법 2 (실패) : Jsoup 라이브러리를 사용해 장소 상세 페이지 내 배너 이미지 크롤링

OpenGraph를 사용해 구현할 수 있다는 미련은 과감하게 버리고, 크롤링을 이용하기로 했다.

장소 상세 페이지 내에는 아래와 같이 최상단에 가게에 대한 배너 이미지가 존재함을 확인했고, 이를 크롤링하여 이미지 url을 추출하고자 했다.

 

크롤링을 위해 Jsoup 라이브러리를 사용하고자 했는데, 이유는 다음과 같다.

현재 진행중인 프로젝트에서는 일정과 비용을 고려했을 때 당장에 Selenium 같은 기술을 사용하기 위한 추가적인 서버 구축은 힘들었고, 이러한 이유로 정적 페이지에 대해 HTML 구문을 추가적인 서버 구동 없이 분석하고 추출해주는 Jsoup 라이브러리를 사용하고자 했다.

 

하지만 결과적으로 장소 상세 페이지에 대한 크롤링은 실패로 돌아갔는데, 원인은 장소 상세 페이지의 배너 이미지 쪽 컴포넌트가 동적 페이지로 구성되어 있기 때문이었다.

 

위에서 언급했듯 Jsoup 라이브러리의 경우 정적 페이지에 대한 HTML 구문 분석 및 추출이 가능한데, 해당 장소 상세 페이지의 경우 스크롤 시 배너가 숨겨지는 등의 기능이 존재하는 동적 페이지로 구성되어 있었다.

 

일정 수치만큼 스크롤 시, 배너가 숨겨지는 동적 페이지로 구성

 

이로 인해 Jsoup 라이브러리로 해당 페이지의 HTML 소스를 추출했을 때, 배너 쪽 컴포넌트가 올바르게 추출되지 않는 문제가 발생하였다.

 

- 시도한 방법 3 (성공) : Jsoup 라이브러리를 사용해 장소 상세 페이지 내 정적 페이지를 찾아 이미지 크롤링

 

동적 기능이 포함되지 않은 이미지 url 컴포넌트를 찾기 위해 고군분투하던 중, 장소 정보 인쇄하기 버튼이 눈에 띄어 클릭해보았다.

 

새로운 페이지로 이동하더니 아래와 같은 페이지가 등장했고, 이거다 싶었다.

 

찾고있던 이미지가 있었고, 누가봐도 정적 페이지에 해당했다.

바로 HTML 소스를 확인해보니 역시나 동적 기능이 포함되지 않은 순수한 정적 페이지였고, HTML 소스 내 이미지 url도 포함되어 있었다.

 

 

본 인쇄하기 페이지의 경우 현재 프로젝트의 조건에 딱 맞는 아래 세 가지 특징을 가지고 있어, 해당 페이지를 크롤링하여 기능을 구현하기로 결정했다.

 

1. 정적 페이지이므로, 추가적인 서버 구동 없이 Jsoup 라이브러리를 이용해 크롤링하여 추출 가능

2. 장소 상세 페이지보다 훨씬 더 적은 양의 데이터를 가지는 페이지이므로, 크롤링 속도 향상

3. https://place.map.kakao.com/placePrint.daum?confirmid={장소 고유 ID} 형식으로 url이 고정 패턴을 가지므로 카카오 Local API에서 제공해주는 장소의 고유 ID를 통해 모든 장소에 대한 인쇄하기 페이지에 접근이 가능 (로직 일반화 가능)

 

2번 크롤링 속도 향상의 경우도 프로젝트 상황에서 중요한 요점이었다.

본 프로젝트에서 장소 검색 API의 경우 각 장소에 대해 페이징 기능을 사용한다 하더라도 페이지 당 15개 정도의 장소 목록이 응답되는데, 카카오 Local API 호출시간과 더해 각 장소에 대한 크롤링 시간까지 길어진다면 UX 측면에서 매우 긴 로딩 시간을 자랑하게 되므로 특히나 크롤링 속도의 향상이 필요했다.

 

바로 Jsoup 라이브러리를 통해 인쇄하기 페이지를 크롤링해 로직을 구현했다.

 

// https://place.map.kakao.com/placePrint.daum?confirmid={장소 고유 ID} 형식으로 로직 통일 가능
String url = "https://place.map.kakao.com/placePrint.daum?confirmid=" + placeId;

Document doc = Jsoup.connect(url).get();

// "https:" 가 빠진채로 image src가 저장되어 있으므로 "https:" prefix 추가
String imagePath = "https:" + doc.getElementsByClass("thumb_g").get(0).attr("src");

 

단 세줄만으로 페이지에서 이미지 url을 추출했다. Jsoup 최고..

위에서 말한 특징 3번과 같이 모든 장소에 대해 고유한 장소 ID만 바꿔준다면 모든 장소에 대해 인쇄하기 페이지에 접근하여 크롤링이 가능해, 공통 메서드로 추출이 가능했다.

 

HTML 내 이미지 url은 "https:" 가 없이 "//" 로 시작하는 형태를 가지기에, 추출한 이미지 url에 prefix로 "https:"를 붙여주는 전처리를 통해 무탈하게 장소의 이미지 추출을 구현할 수 있었다.

 

로컬 테스트 결과

 

하나의 장소에 대한 이미지 url 크롤링 작업은 약 0.05초 ~ 0.07초 사이로 측정되었다.

페이징을 통해 15개의 장소에 대해 크롤링 작업을 진행했을 때에 대략 0.8초 ~ 1초 정도의 짧은 시간으로 구현할 수 있다.

 

 

본 기능 구현을 위한 여러 시도와 기술적 도전을 통해 OpenGraph와 같은 지식을 추가적으로 익힐 수 있었고, 실패가 곧 해결방법에 점진적으로 다가갈 수 있는 단계로 작용하는 좋은 경험을 하게 되었다.