프로젝트 개요

말하기 앞서, 기말고사 기간이라 현재 진행하는 프로젝트 팀원들이 시험을 준비하는 관계로 혼자 갖고 놀만한 웹 크롤링에 대해 공부해보려고 합니다.
안드로이드 스튜디오에 내에서 비동기로 웹 크롤링 함수를 만들어 사용해 데이터를 빼온다음 이를 파이어베이스 파이어스토어에 저장(중간에 대학교 웹사이트 에러걸리는 거 방지)해서 뺴오는 방식으로 진행할 예정입니다.
프로젝트에서 파이어스토어 다루는 것은 어렵지 않으니 html코드 분석과 이를 이용해 원하는 정보만 정리해서 빼오는 코드를 적는 것이 목표입니다.

순서

페이지 탐색

2019-06-19 15;23;38.PNG
: 이러한 형식의 페이지에서 학식 메뉴 정보를 빼올 것입니다.(여러분은 다른 거 하셔도 됩니다.ㅎㅎ)

html코드 확인

: 찾고자 하는 부분을 드레그 -> 마우스 우클릭 -> '검사' 클릭
2019-06-19 14;36;15.PNG

html코드 상 원하는 부분 분석

: 태그 위주로 이런 것을 골라서 빼오면 되겠구나 파악
2019-06-19 14;38;15.PNG
(예를 들어, 상단에 div class="listType02 type 2" id="day" 라고 있는데 여기서 id="day"라는 부분만 빼면 될 것 같다. 라는 판단이 필요합니다. <- html구조에 대한 이해가 필요함.)

안드로이드 스튜디오를 실행하고 비동기로 작성

: 파이참이나 이클립스같은 개발환경에서는 굳이 비동기로 작성할 필요없지만 안드로이드 스튜디오에서 async(비동기)로 안적으면 에러가 발생합니다.

 //코틀린 Anko 라이브러리 설치 필수~!

 doAsync {
     //여기다가 코드 적으시면 됩니다.
 }

크롤링 코드 작성

Jsoup 간단 사용법

  • 특정 url로부터 html 코드 가져오기
    val doc = Jsoup.connect(가져오고 싶은 url).get()
  • 단일태그 가져오기
    val divTag = doc.select("div")  //div 태그를 모두 가져옵니다.
    val tdTag = doc.select("td")    //td 태그를 모두 가져옵니다.
  • class 가져오기
    val class = doc.select("td.name")
    : 예를 들어, html 코드 중 <td class="name">에서 name클래스를 가져올 때 사용합니다.
  • id 가져오기
    val id = doc.select("div#name")
    : 예를 들어, html 코드 중 <div id="name">에서 name이라는 id 부분을 가져올 때 사용합니다.
  • 여러 태그 가져오기
    val multi = doc.select("div").select("tbody")
    : div태그가 있는 것'중에서' tbody태그를 가져오겠다. 는 의미입니다.
  • 가져온 것중 특정 번째만 뽑기
    2019-06-19 15;02;07.PNG
    : 이럴 경우 몇번째 <tr>태그를 가져올지 고민하게 됩니다. 그럴 경우엔 인덱스를 사용해줍시다.
    val trTag_first = doc.select("tr")[0] //첫번째 tr
    val trTag_first = doc.select("tr")[1] //두번째 tr
    val trTag_first = doc.select("tr")[2] //세번째 tr

크롤링 코드(2019.6.17)
: 처음 공부하면서 적은 코드라 지저분합니다.

//비동기는 코틀린 Anko 라이브러리를 이용함.
doAsync {
    val document = Jsoup.connect(address1).get()

    /**
     *조식, 중식, 석식 제목과 그 안에 내용
     * **/

    //제목부분
    val title = document.select("div#day tbody").select("th")

    //조식, 중식, 석식 단어를 나눔.
    val titleSplitList = title.html().split("\n").toTypedArray()


    //내용부분

    //코너A ~마지막 메뉴(html태그들도 모두 긁어옴)
    val desc = document.select("div#day tbody").select("td.al")

    //긁어온 것들 중에 html태그만 없앰
    val descSplitList = desc.html().split("<br>").toTypedArray()
    var descResult = ""
    descSplitList.forEach {
         //태그를 없애는 대신 그 자리에 줄바꿈시켜줌
         descResult += it + "\n"
    }
    var L = descResult.split("코너 A").toTypedArray()

    var result = mutableMapOf<String, String>()
    var position = 0
    L.forEach {
       if(it =="") return@forEach//L배열에 맨 앞에 빈 거 무시함.
       else{
               result.put(titleSplitList[position], "코너 A"+it)
            position+=1
       }
    }
    //조식, 중식, 석식 별로 안에 내용까지 다 있는 맵 만듬
    println(result.toString())
}

: 원래 파이썬 BeautifulSoup을 사용해서 웹 크롤링을 해보려고 했지만 프로젝트 자체가 안드로이드 폰에서 돌리는 것이므로 굳이 파이썬으로 웹 크롤링한 데이터를 다시 서버로 보내고 이러고 하면 시간상 오래 걸릴 것 같기도 하고, 실제 비슷한 코드로 코틀린(Jsoup), 파이썬(BeautifulSoup)을 돌려본 결과 코틀린과 자바에서 사용되는 Jsoup이 더 빠른 것 같아 체택했습니다.

크롤링코드(2019.6.18)
:Jsoup 인덱스를 알고 바꾼 코드입니다.

  doAsync {
                val document = Jsoup.connect(address).get()

                //내용(메뉴들)
                val breakfastDesc = document.select("div#day").select("td.al")[0]
                val lunchDesc = document.select("div#day").select("td.al")[1]
                val dinnerDesc = document.select("div#day").select("td.al")[2]

                var resultMap = mutableMapOf<String, String>()

                resultMap["조식"] = makeLineText(breakfastDesc)
                resultMap["중식"] = makeLineText(lunchDesc)
                resultMap["석식"] = makeLineText(dinnerDesc)

                println(resultMap.toString())

//html을 가지고 태그없애고 줄바꿈있는 텍스트로 만듬
  fun makeLineText(element : Element) : String{

        val list = element.html().split("<br>").toTypedArray()

        var result = ""
        list.forEach {
            if(it == " ") return@forEach
            result+=it+"\n"
        }
        return result
  }

:결과는 비슷한데 코드가 훨씬 깔끔해졌습니다.(사실 결과도 훨씬 깔끔합니다.ㅎㅎ) 파이썬 BeautidulSoup를 끄적이다가 인덱스가 쓰이길래 Jsoup에도 있나? 해서 사용해봤는데 있길래 사용했더니 코드가 훨씬 간결해졌습니다. 참고로 <br>태그를 없애주고 그 자리에 다시 줄바꿈을 넣을 수 있는 코드를 함수로 적어서 더 간결하게 바꿔습니다.

크롤링코드(2019.6.19)

doAsync {
                val document = Jsoup.connect(address).get()

                //조식, 중식, 석식 메뉴 모두 가져옴.
                val menu = document.select("div#day").select("td.al")

                var resultMap = mutableMapOf<String, String>()

                resultMap["조식"] = makeLineText(menu[0])
                resultMap["중식"] = makeLineText(menu[1])
                resultMap["석식"] = makeLineText(menu[2])

                println(resultMap.toString())
            }

  fun makeLineText(element : Element) : String{

        val list = element.html().split("<br>").toTypedArray()

        var result = ""
        list.forEach {
            if(it == " ") return@forEach
            result+=it+"\n"
        }
        return result
  }

: 조금이나마 더 간단하게?ㅋㅋㅋ

--계속 수정될 글입니다.--