본문 바로가기

Android

[ 안드로이드 - Kotlin ] Jsoup을 사용한 크롤링 ( 코틀린 )

728x90

[ 준비 ]

크롤링할 사이트

이번에 크롤링할 사이트는 flaticon이라는 사이트 입니다.

저는 주로 android 기본 Vector Asset을 많이 사용하는데요!

아무래도 아이콘이 다양하지 않다 보니 여기에 마음에 드는 아이콘이 없을 때 flaticon에서 아이콘을 많이 받아서 쓰곤 합니다.

링크는 아래에도 한 번에 정리해 드리겠습니다.

URL 저장

url 저장은 이전 글에서 하던 방식대로 local.properties에 해주겠습니다.

build.gradle(:app) dependencies 추가

Jsoup, Coroutines을 사용하기 위해 아래 두둘의 코드를 추가해줍니다.

build.gradle(:app)

AndroidManifest.xml에 권한 추가

인터넷 사용을 위해 INTERNET권한도 manifest.xml에 추가해줍니다.

AndroidManifest.xml


[크롤링할 Elements 보기]

먼저 저희가 크롤링하고자 하는 flaticon사이트에서 android관련 아이콘을 검색했다고 가정합시다. 🤖

검색 후에는 개발자 도구에 들어가서 검색을 하면 됩니다. 개발자 도구는 f12를 누르면 들어갈 수 있습니다.

여기서 아래 사진처럼 빨간 화살표로 되어있는 아이콘을 누르거나 Command + shift + c를 누르면 원하는 Element를 선택할 수 있습니다.

개발자도구 들어가는 방법, Element선택 방법

저희는 아이콘을 크롤링할 예정이니 아이콘을 선택합니다. 그럼 모든 icon들을 포함하고 있는 element는 section태그의 class 가 "search-result icons-search-result"인 항목입니다. 보통 웹에서 클래스를 '.'으로 id를 '#'으로 표현한다는 점을 사용하면 아래 콘솔을 통해 쉽게 찾을 수 있습니다. 예를 들어 저희가 선택한 section태그를 id로 찾아보겠습니다.

console에서 검색, class 표현법

이런 식으로 Element들을 파악 후 규칙을 찾아서 크롤링하면 됩니다.

크롤링에 꼭 크롤링하고자 하는 지점까지 Element를 타고 들어가지 않아도 됩니다. 저는 다음과 같이 엘리먼트들을 선택하였습니다.

section.search-result.icons-search-result > ul.icons > div.icon--holder > a.view.link-icon-detail

저는 위에서 선택한 a태그 안에는 data-id, title, href 3가지 attribute와 하위에 img태그 안에 src attribute를 크롤링하도록 하겠습니다.


[기본 설명]

data class

우선 간단히 아이콘의 이름, 이미지 주소, 이미지를 커스텀해서 받는 사이트 이렇게 3가지를 가지는 클래스를 만들어 주도록 하겠습니다.

나중에 데이터를 좀 더 보기 쉽게 표현하기 위하여 toString함수를 재정의 하였습니다.

IconInformation.kt

그리고 크롤링을 진행하다 보니 같은 검색어에 같은 페이지를 검색해도 계속 다른 결과를 보여주도록 되어있다는 걸 알게 되었습니다. 😱

그래서 웹에서 element를 살펴보니 flaticon사이트에서 사용하는 고유 키?로 생각되는 attribute가 있어서 실제 받는 부분에서는 HashMap으로 받으면서 <Key, IconInformation> 형식으로 받았습니다.

OnQueryTextListener (SearchView 리스너)

submit버튼을 사용하기 위하여 isSubmitButtionEnabled를 true로 설정하고 setOnQueryTextListener도 만들어준 리스너로 등록합니다.

MainActivity.kt

검색 시에 텍스트가 없으면 Toast메시지를 띄우고 아무것도 하지 않고 텍스트가 있으면 검색을 수행하도록 하였습니다.

추가로 searchView에서는 submit버튼을 클릭했을 경우만 검색을 하도록 하였습니다. 다음 글에서 onQueryTextChange()와 함께 사용하는 글을 올리도록 하겠습니다.

MainActivity.kt

Coroutines (스코프 설정)

크롤링 시 UI처리를 하는 메인 스레드를 사용하다가 ANR(Application Not Responding)이 발생할 수 있음으로 RXJava나 Coroutines을 사용해야 합니다. 여기서는 단순히 크롤링 후 성공 시 텍스트만 보여줄 예정이라 간단히 Dispatchers.Main으로 메인 스코프 안에서 크롤링하는 함수는 IO 스코프를 사용하도록 하였습니다. 크롤링하는 함수가 모두 끝나면 크롤링 정보를 textView에 표시하기 위하여 크롤링 함수에 suspend키워드를 달아주었습니다.

(사실 Coroutines에 관한 글을 먼저 쓰고 진행할까 했는데 저도 아직 공부 중이라 이후에 정리해서 올려드리겠습니다.😅)

MainActivity.kt

전체 코드를 보시면 아시겠지만 제가 코루틴 관련해서 로그를 남겨놨는데 액티비티는 그대로 실행되고 Main 스코프에서  IO 스코프인 getIconInformation() 함수 작업이 모두 끝나면 차례로 끝나는 것을 보실 수 있습니다.

getIconInformation (크롤링 함수)

드디어 크롤링하는 부분입니다. 😭

위에서 미리 크롤링하고자 하는 부분을 정했다면 이제 Jsoup을 통해서 바로 크롤링할 수 있습니다.

저 같은 경우는 url에서 검색할 부분을 %s 페이지를 %d로 적어두고 String.format으로 page와 searchText를 넣어주었습니다.

url 형식
MainActivity.kt


[실행화면]

다음 포스팅에서는 불러온 src를 기준으로 Glide라이브러리를 활용하여 recyclerView에 보여주도록 추가해 볼 겁니다.

아직 테스트 단계라서 로딩이나 불러오는 페이지를 무조건 1로 설정을 하였는데 이러한 부분도 적용해서 올려보도록 하겠습니다. 🔥🔥🔥

실행화면

[코드]

전체 코드는 제 github jsoup_crawling branch에서 clone 하시면 됩니다. 🧑🏻‍💻

 

IconInformation.kt

data class IconInformation(
    val name:String,
    val src:String,
    val siteUrl:String
){
    override fun toString(): String {
        return "ICON : {\n" +
                "\t$name\n" +
                "\t$src\n" +
                "\t$siteUrl}\n\n"
    }
}

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.widget.SearchView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.ddns.yline.imagesearch.databinding.ActivityMainBinding
import net.ddns.yline.imagesearch.item.IconInformation
import org.jsoup.HttpStatusException
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.select.Elements

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val iconList = hashMapOf<String, IconInformation>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        setListener()
    }

    private fun setListener(){
        binding.apply {
            searchIcon.also {
                it.isSubmitButtonEnabled = true
                it.setOnQueryTextListener(OnQueryTextListener())
            }
        }
    }

    // 이미지를 가져오고 textview에표시하는 함수
    private fun getItemList(searchText: String){
        // 메인 코루틴 스코프
        CoroutineScope(Dispatchers.Main).launch {
            Log.d("coroutine Dispatchers.Main start", "test")
            getImageInformation(iconList, searchText, 1)
            Log.d("coroutine Dispatchers.Main end", "test")
            Toast.makeText(applicationContext, iconList.size.toString(), Toast.LENGTH_SHORT).show()

            // 크롤링한 정보 표시
            binding.textviewTest.text = iconList.toString()
        }
    }

    inner class OnQueryTextListener:SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            Log.d("coroutine activity start", "test")
            // 이미지를 가져오고 textview에표시
            if(query == null){
                Toast.makeText(applicationContext, "검색한 단어가 없습니다.", Toast.LENGTH_SHORT).show()
                return false
            }else{
                // 임시로 크롤링한 데이터 매번 지우기
                iconList.clear()
                getItemList(query)
            }
            Log.d("coroutine activity end", "test")

            return true
        }

        // 지금은 사용 안함
        override fun onQueryTextChange(newText: String?): Boolean {
            
            return false
        }
    }
}

// 이미지 크롤링 하는 함수
suspend fun getImageInformation(iconList:HashMap<String, IconInformation>, searchText:String, page:Int):Boolean = withContext(Dispatchers.IO){
    Log.d("coroutine Dispatchers.IO start", "test")
    // 페이지 끝 여부
    var isEnd = false

    try {
        // https://www.flaticon.com/search/%d?word=%s&license=selection&style=all&order_by=4&type=icon
        // 주어진 검색어와 페이지를 검색
        val jsoup = Jsoup.connect(String.format(BuildConfig.URL_ICON_SITE, page, searchText))
        val doc:Document = jsoup.get()
        // 크롤링 하고자 하는 엘리먼트들을 저장
        val elements:Elements = doc
            .select("section.search-result.icons-search-result")
            .select("ul.icons")
            .select("div.icon--holder")
            .select("a.view.link-icon-detail")
        //만일 해당 엘리먼트가 없으면 페이지에 마지막에 도달한 것으로 간주
        if(elements.size == 0) isEnd = true
        else{
            for(element in elements){
                element.run {
                    iconList[attr("data-id")] = IconInformation(
                        attr("title"),
                        attr("href"),
                        select("img").attr("src")
                    )
                }
            }
        }
    }
    // status오류
    catch (httpStatusException : HttpStatusException){
        isEnd = true
        Log.e("getImageInformation", httpStatusException.message.toString())
        httpStatusException.printStackTrace()
    }
    // 기타 오류
    catch (exception:Exception){
        isEnd = true
        Log.e("getImageInformation", exception.message.toString())
        exception.printStackTrace()
    }

    Log.d("coroutine Dispatchers.IO end", "test")
    isEnd
}

 

Free Vector Icons and Stickers - Thousands of resources to download

Download Free Vector Icons and Stickers for your projects. Resources made by and for designers. PNG, SVG, EPS, PSD and CSS formats

www.flaticon.com

 

[안드로이드-Kotlin] API KEY 관리

[API KEY 관리법] API KEY를 관리하는 이유 api key 같은 상수들을 관리하는 다양한 이유가 많지만 여기서는 두 가지 정도 이야기를 해보면 첫째로 코드에 그대로 작성하는 것은 보안상의 이유로 좋지

yline.tistory.com

 

jsoup: Java HTML parser, built for HTML editing, cleaning, scraping, and XSS safety

jsoup: Java HTML Parser jsoup is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors. jsoup implements the WHATWG H

jsoup.org

 

Coroutines | Kotlin

 

kotlinlang.org

 

GitHub - yline0808/IconSearch

Contribute to yline0808/IconSearch development by creating an account on GitHub.

github.com

제가 잘못 알고 있거나 잘못된 부분이 있을 경우 알려주시고 추가로 궁금한 점 있으신 분들도 댓글이나 메일 주시면 성실히 답변해 드리겠습니다.🧑🏻‍💻

감사합니다~😄

728x90