괴도키드가 웹 크롤링을 한 이유

2024. 7. 18. 11:00개발자 과정/Kotlin

괴도키드의 이야기.

 

글의 재미를 위해 "훔친다" 라는 워딩을 쓰지만, 웹 크롤링 그 자체로는 범법이 아님을 유의해 주세요.

 

서론

괴도키드는 현물을 훔치는 생활을 이어가다 웬만한건 다 훔치고 말았다. 

더 이상 훔칠게 없던 괴도키드는 도파민 부족으로 인생 노잼시기를 지내게 되는데...

 

그러던 어느 날..

길을 지나던 괴도키드는 의문의 중학생이 조선일보 전광판을 훔친것을 보고 머릿속에 엄청난 생각이 스쳐지나간다.

 

정보화 시대 21세기..우리는 눈으로 보이는 세계와는 다른 또 다른 세계에 살고 있다.

바로 정보의 세계...0과 1로 이루어진 세계말이다!!! 

데이터를 훔치자...!!!

 

그렇게 잠들었던 도파민이 용솟음 치기 시작한 괴도키드는 데이터를 훔치는 법을 배우기로 한다. 

허나, 원래 진짜 도둑질은 하지 않던 그는 이번에도 역시 진짜 도둑질인 해킹보단,

자기 기분을 만족시킬 수 있는 웹 크롤링을 선택했다. 

역시 뼛속까지 관종임이 확실하다.

 

핵관종에 화려함을 좋아하는 괴도키드는 묵묵히 개발을 하는 흑백인간들에게 정신을 잃을 뻔했지만, 

무난히 프로그래밍을 배우게 되었고, 그는 이제 제대로 데이터를 도둑질(자기만족)을 계획하게 된다!

 

본론

그는 우선 훔치고 싶은 사이트를 지정했다. 

그 사이트의 정보를 훔치기 위해선 웹 크롤러가 필요했기에, 그는 프로그램 작성에 들어갔다. 

 

dependencies {
    implementation("org.jsoup:jsoup:1.14.1")
}

자신의 웹 크롤링을 도와줄 조수로, Jsoup을 고용했다. 

Jsoup은 웹페이지를 간편하게 가져오고 파싱할 수 있는 여러 지원함수가 많았고,

레퍼런스도 충분했기에, 괴도키드 자신이 하고자 하는 일에 온전히 집중할 수 있도록

도움을 줄 수 있기 때문이었다.

 

괴도 일을 하는데 오른팔이 되어줄 Jsoup조수를 고용하였지만, 

왼팔도 필요한 실정이었다. 긁어온 데이터가 있다면, 그것을 저장을 해야

내것이 되는 거고 그것이 괴도의 덕목 아니겠는가!

 

괴도키드는 고민했다.

TXT = 우리가 흔히 아는 메모장 그 녀석이 맞다.  
CSV = ','로 데이터를 파싱하며, 간단한 데이터를 체계적으로 다루는데 이만한 녀석이 없다.
JSON = key:Value로 데이터를 저장하며, 포맷을 맞추는게 쉽지 않지만, 
	데이터를 세밀하게 저장할 수 있다는 강점이 있다.

이런 저장포맷이 있는데,  Json을 사용하기로 결정한다.

1. 크롤링 데이터를 핸들링 하기 위해선 자신이 설정한 포맷 그대로 잘 저장할 수 있어야 한다.

2. TXT는 변환과정에서 문자열 알고리즘이 불가피 하다.

3. CSV','로 파싱하기 때문에 List등의 자료형을 저장하기에 적합하지 않다.

그리고 포맷을 맞추는게 쉽지 않다는 말도, Json을 지원하는 라이브러리가 많이 생김에 따라

이 또한 옛말이 되었기에, Json을 선택하지 않을 이유가 없었다. 

 

dependencies {
    implementation ("com.google.code.gson:gson:2.8.8")
}

그래서 그는 Json전공 박사학위를 따고 DataCalss <-> Json 변환 자격증을 가진 Gson을 고용하기로 한다. 

물론, 중고 신입 최저임금으로 말이다.

 

이제 괴도일 하러 가볼까!

fun setCrawlUrl(url:String){
    val document = Jsoup.connect(url).get()
    crawl=document
}

 

 

우선 Json에게 우리가 들어갈 사이트를 지정해 주겠다고 한다.

 

private fun example(data: DataClass) {
    crawl.select("body > section > ...")
        .text()
        .split(" ")
        .also {
            data.{value} = it
        }
}

그리고 가져온 사이트 정보에서 타겟으로 하는 원소로 path를 지정해준다.

그 후 가져온 데이터를 파싱하여 DataClass에 적절이 넣어준다.

 

Jsoup이게 네가 할 일이야! 이제 Gson 너는 뭘해야 하냐면

@Configuration
class GsonCustom {
    @Bean
    fun gson(): Gson = GsonBuilder().serializeNulls().create()
}

Gson의 기본세팅은 Null 데이터가 존재하면 저장하지 않기에, Null데이터가 있어도 저장하기 위해선 세팅을 해줘야 한다.

@Bean 주입을 통해 세팅을 받아온다. 

private fun toJson(wineDataList: List<WineData>) = gson.toJson(wineDataList)

Json으로 변경해주는 함수를 작성하고

fun writeData(dataList: List<DataClass>) {
    val stt= LocalDateTime.now()
    logger.info("Start writeData Process")
    File("./data_${fileCnt++}.json")
        .also { it.writeText(toJson(dataList)) }

    val end= LocalDateTime.now()
    
    logger.info("End writeData Process : {}",Duration.between(stt,end))
}

파일을 저장후 파일 카운트를 늘린다. 한 파일의 크기가 비대해지는 것을 방지하고, 

추적에도 훨씬 용이해진다.

 

이제 괴도키드가 등장!

val data=mutableListOf<DataClass>()
for(idx in now downTo min) {
    Thread.sleep(Random.nextLong(1000,3000))
    try {
        data.add(crawler.setCrawlUrl("https://www.idx=${idx}")
            .let { crawler.getCrawlData(idx) })
    }catch (e:Exception){continue}

    if(data.size >= 100){
        fileStreamProcess(data.toList())
        wineData.clear()
    }
}

Jsoup에게 어떤 Url에 진입할 것인지, 

Gson은 데이터가 어느만큼 쌓여야 데이터를 저장할지를 지시해준다. 

그리고 JsoupGson이 같이 움직이면 안되고 각자가 할 일을 해야 하기에

 

private fun fileStreamProcess(wineData: List<WineData>) {
    scope.launch(Dispatchers.IO) {
        filestream.writeData(wineData)
    }
}

Gson에게 Jsoup과 따로 움직일 것을 지시하여 비동기 처리를 한다. 

 

또한, 데이터를 너무 자주 가져오면 서버에 과부하가 걸리고, 경비원들에게 들킬 수도 있으니

Thread.sleep(Random.nextLong(1000,3000))

적당히 여유를 두고, Random하게 예측할 수 없도록 움직인다. 

이것이 괴도의 덕목 아닐까!

 

결론

이렇게 괴도키드는 웹 크롤링 작전을 통해 멋진 괴도 일을 할 수 있었다.! 

괴도키드는 이에 크게 만족했고, 그를 도와준 조수들에게 몫을 나눠 주었다. 

물론, 최저임금으로 말이다.

도파민 풀충전!!!

괴도키드는 자신의 기량을 펼쳐 도둑질을 할 새로운 세계가 생겨 기쁨을 금치못했다. 

그가 훔치지 못하는건 대체 무엇일까?

그의 괴도일은 앞으로도 계속 우리의 곁에서 마음을 훔칠 것이다.