Runjob(런잡 프로젝트)/SpringBoot + Kotlin

[SpringBoot + Kotlin] Jsoup 을 이용한 웹 크롤링

맏리믓 2023. 4. 23. 21:13

들어가며

- 전 시간에 API 호출을 이요하여 원하는 데이터를 수집 하여 보았다. 

- 하지만 모든 사이트에서 데이터를 호출하는 API 를 제공 하는 것은 아니다.

- 이런 경우에 사용할 수 있는 방법에 웹의 HTML 에 있는 데이터를 직접 가지고 오는 방법이다.


크롤링 하고자 하는 웹페이지의 개발자 모드 열기

- 우선 당연한 이야기지만 크롤링 하고자 하는 웹 페이지를 열어야 한다.

- 이번 시간에는 저번시간에 진행 했던 영화 정보를 크롤링을 통해 가져 오는것을 진행 해 보겠다.

- 우측의 연한 파란 부분이 좌측 웹 사이트의 영화 정보부분임을 알 수 있다.

- 또한 내부의 "inner_cont" 가 영화 정보를 답고 있음을 알 수 있다.


파일 생성

- 우선 크롤링코드를 적을 파일을 생성 해 준다.

 


jsoup의 간단한 사용법 (val html = Jsoup.connect(url).get() )일때

 

- select -

1. <div class="test"/> 처럼 class 이름이 test 일때

- val item = html.select("div.test")

2. <div id="test"/> 처럼 id 이름이 test 일때

- val item = html.select("div#test")

3. <div class="test"/> 내부의 a 태그만 가져 올때

- val item = html.select("div.test a")

 

- attr -

4. HTML 의 속성값을 가져 올때

ex) <a id="ID" href="test_href01" title="title01"> abcdefg </a>

- val item = html.select("a#ID")

- val attr = item.attr("href") // => test_href01

 

- text() -

5. 내부의 텍스트 값을 가져 오고 싶을 때(위의 예시에서 abcdefg 를 가져 올 때

- item.text() // => abcdefg


실제로 Jsoup 을 이용하여  크롤링 테스트

- 우선 보이는 대로 "inner_cont" class 를 그대로 크롤링 해 본다.

- 이젠 "Controller" 와 "Interface" 는 생략 한다.

//movieInfoServiceImpl.kt

package com.example.runjob_blog.Service.impl

import com.example.runjob_blog.Service.movieInfoService
import org.jsoup.Jsoup
import org.springframework.stereotype.Service

@Service
class movieInfoServiceImpl:movieInfoService {
    override fun saveTimeTable(movieID: String){
        val url = "https://movie.daum.net/moviedb/main?movieId=${movieID}"
        val conn = Jsoup.connect(url)
        val html = conn.get()
        val inner_cont = html.select("div.inner_cont")

        println(inner_cont)
    }
}

 

class 이름이 inner_cont 인 html 이 모두 나옴을 알 수 있다.

- 여기서 주의 할 점은 class 이름이 "inner_cont" 인 div 가 여려개라면 모두 나온다는 것이다.


실제로 원하는 데이터만 크롤링

- 여기서 원하는 데이터는 위 데이터 내부의 <dl/> 부분이다.

- 다음과 같이 코드를 수정하면 <dl/> 부분만 가져 올 수 있다.

- 또한 <dl/> 처럼 개수가 많으면 배열처럼 사용 할 수 있다.

- 다음 이미지를 보면 inner_cont[0] 을 하면 가장 처음 dl 이 출력 되는것을 알 수 있다.

package com.example.runjob_blog.Service.impl

import com.example.runjob_blog.Service.movieInfoService
import org.jsoup.Jsoup
import org.springframework.stereotype.Service

@Service
class movieInfoServiceImpl:movieInfoService {
    override fun saveTimeTable(movieID: String){
        val url = "https://movie.daum.net/moviedb/main?movieId=${movieID}"
        val conn = Jsoup.connect(url)
        val html = conn.get()
        val inner_cont = html.select("div.inner_cont dl")

        println(inner_cont[0])
    }
}

[0] 번째를 출력하니 첫번째 항목만 출력


데이터 를 Hashmap 으로 정제

- 위 그림처럼 text 를 쭉 받아서는 사용 할 수 없다.

- 따라서 우리가 사용하기 편하게 "개봉 = 2023.03.08" 처럼 되어 있는것이 좋다.

- 먼저 데이터를 분석 해 보면 <dt> 태그 안에는 데이터의 이름이, <dd> 태그 안에는 데이터의 값이 들어 있다.

- 이를 토대로 inner_cont 를 반복돌려 hashmap 에 저장 한다.

- 필요에 따라 같은 방식으로 이름도 데이터에 추가하면 다음과같이 된다.

package com.example.runjob_blog.Service.impl

import com.example.runjob_blog.Service.movieInfoService
import org.jsoup.Jsoup
import org.springframework.stereotype.Service

@Service
class movieInfoServiceImpl:movieInfoService {
    override fun saveTimeTable(movieID: String){
        val movieInfo = HashMap<String, String>()

        val url = "https://movie.daum.net/moviedb/main?movieId=${movieID}"
        val conn = Jsoup.connect(url)
        val html = conn.get()
        val inner_cont = html.select("div.inner_cont dl")
        val movieTit = html.select("span.txt_tit")[0].text()
        movieInfo["이름"] = movieTit

        for(oneCont in inner_cont){
            val contName = oneCont.select("dt").text()
            val contVal = oneCont.select("dd").text()
            movieInfo[contName] = contVal
        }

        println(movieInfo)
    }
}

데이터가 잘 정제되어 들어 감을 볼 수 있다.