Kotlin을 활용한 웹 스크래핑: 단계별 가이드

Kotlin은 웹 스크래핑을 위한 강력한 도구입니다. 이 가이드에서 설정 방법, 기술, 효율적이고 윤리적인 스크래핑을 위한 모범 사례를 다루며 효과적인 사용법을 배워보세요.
5 분 읽기

이 튜토리얼에서는 Kotlin 웹 스크래핑 스크립트 구축 방법을 배웁니다. 구체적으로 다음을 학습하게 됩니다:

  • 사이트 스크래핑에 Kotlin이 적합한 이유
  • 최고의 Kotlin 스크래핑 라이브러리는 무엇인가
  • Kotlin 스크레이퍼를 처음부터 구축하는 방법

자, 시작해 보겠습니다!

Kotlin은 웹 스크래핑에 적합한 선택인가요?

간단히 말해: 네, 가능합니다! 심지어 자바보다 더 나을 수도 있습니다!

Kotlin은 정적 타입 지정, 크로스 플랫폼, 범용 프로그래밍 언어로, 표준 라이브러리가 Java 클래스 라이브러리에 의존합니다. Kotlin을 특별하게 만드는 것은 간결하고 재미있는 코딩 방식입니다. Google이 Android 개발을 위한 선호 언어로 선택한 바 있습니다.

JVM과의 상호 운용성 덕분에 모든 자바 스크래핑 라이브러리를 지원합니다. 따라서 방대한 자바 라이브러리 생태계의 이점을 누리면서도 더 간결하고 직관적인 구문을 사용할 수 있습니다. 일석이조의 상황이지요!

또한 Kotlin은 HTML 파서 및 브라우저 자동화 라이브러리를 포함한 일부 네이티브 라이브러리를 제공하여 데이터 추출을 단순화합니다. 가장 인기 있는 라이브러리 몇 가지를 살펴보세요!

최고의 Kotlin 웹 스크래핑 라이브러리

다음은 Kotlin용 최고의 웹 스크래핑 라이브러리 목록입니다:

  • skrape{it}: HTML/XML 테스트 및 웹 스크래핑을 위한 Kotlin 기반 라이브러리로, HTML 파싱과 해석을 수행합니다. 여러 데이터 페쳐를 포함하여 skrape{it}이 전통적인 HTML 파서 역할과 클라이언트 측 DOM 렌더링을 위한 헤드리스 브라우저 역할을 동시에 수행할 수 있게 합니다.
  • chrome-reactive-kotlin: Chromium 기반 브라우저를 프로그래밍 방식으로 제어하기 위해 Kotlin으로 작성된 저수준 DevTools Protocol 클라이언트입니다.
  • ksoup: Jsoup에서 영감을 받은 경량 Kotlin 라이브러리입니다. Ksoup은 HTML 파싱, HTML 태그/속성/텍스트 추출, HTML 엔티티 인코딩/디코딩을 위한 메서드를 제공합니다.

Kotlin은 Java와 상호 운용 가능하다는 점을 잊지 마세요. 이는 Java의 다른 웹 스크래핑 라이브러리도 사용할 수 있음을 의미합니다. 그중 하나가 가장 널리 사용되는 HTML 파서 중 하나인 Jsoup입니다. Jsoup을 활용한 웹 스크래핑 가이드에서 자세히 알아보세요.

필수 조건

웹 스크래핑 Kotlin 환경을 설정하려면 아래 지침을 따르세요.

환경 설정

컴퓨터에서 Kotlin 애플리케이션을 작성하고 실행하려면 로컬에 JDK(Java Development Kit)가 설치되어 있어야 합니다. Oracle 사이트에서 최신 LTS 버전의 JDK를 다운로드하고 설치 프로그램을 실행한 후 설치 마법사의 안내를 따르세요. 본 문서 작성 시점 기준 최신 버전은 Java 21입니다.

다음으로, Kotlin 애플리케이션의 종속성을 관리하고 빌드할 도구가 필요합니다. Gradle 과 Maven 모두 훌륭한 옵션이므로 선호하는 Java 빌드 도구를 자유롭게 선택하세요. Gradle은 Kotlin을 DSL(도메인 특정 언어)로 지원하므로 Gradle을 선택하겠습니다. Maven 사용자도 튜토리얼을 쉽게 따라갈 수 있다는 점을 기억하세요.

Maven 또는 Gradle을 다운로드하여 설치하세요. Gradle은 특히 Java 버전에 민감하므로 올바른 패키지를 다운로드해야 합니다. Java 21에서 작동하는 Gradle 버전은 8.5 이상입니다.

마지막으로 Kotlin IDE가 필요합니다. Kotlin 언어 확장 기능을 갖춘 Visual Studio Code와 IntelliJ IDEA Community Edition은 모두 훌륭한 무료 선택지입니다.

완료! 이제 Kotlin 준비 환경이 구축되었습니다!

Kotlin 프로젝트 생성

Kotlin 웹 스크래핑 프로젝트를 위한 프로젝트 폴더를 생성하고 터미널에 다음 명령어를 입력하세요:

mkdir KotlinWebScraper

cd KotlinWebScraper

여기서는 디렉터리 이름을 KotlinWebScraper로 지정했지만, 원하는 이름으로 자유롭게 지정하세요.

다음으로 프로젝트 폴더에서 Gradle 애플리케이션을 생성하기 위해 아래 명령어를 실행하세요:

gradle init --type kotlin-application

초기화 과정에서 몇 가지 질문이 표시됩니다. 빌드 스크립트 DSL로 “Kotlin”을 선택하고 com.kotlin.scraper와 같은 적절한 패키지 이름을 지정하세요. 나머지 질문은 기본값으로 설정해도 무방합니다.

초기화 과정이 완료되면 다음과 같은 화면이 표시됩니다:

빌드 스크립트 DSL 선택:

  1: Kotlin

  2: Groovy

선택 입력 (기본값: Kotlin) [1..2] 1

프로젝트 이름 (기본값: KotlinWebScraper):

소스 패키지 (기본값: kotlinwebscraper): com.kotlin.scraper

Java 대상 버전 입력 (최소 8) (기본값: 21):

새로운 API 및 동작으로 빌드 생성 (일부 기능은 차기 마이너 릴리스에서 변경될 수 있음)? (기본값: no) [yes, no]

> 작업 :init

https://docs.gradle.org/8.5/samples/sample_building_kotlin_applications.html에서 샘플을 살펴보며 Gradle에 대해 자세히 알아보세요

2분 10초 만에 빌드 성공

실행 가능한 작업 2개: 2개 실행됨

훌륭합니다! KotlinWebScraper 폴더에 Gradle 프로젝트가 생성되었습니다.

Kotlin IDE에서 폴더를 열고, 필요한 백그라운드 작업이 완료될 때까지 기다린 후 com.kotlin.scraper 패키지 내의 메인 App.kt 파일을 확인하세요. 파일 내용은 다음과 같아야 합니다:

/*

 * 이 Kotlin 소스 파일은 Gradle 'init' 작업에 의해 생성되었습니다.

 */

package com.kotlin.scraping.demo

class App {

    val greeting: String

        get() {

            return "Hello World!"

        }

}

fun main() {

    println(App().greeting)

}

터미널에 “Hello World!”를 출력하는 간단한 Kotlin 스크립트입니다.

작동 여부를 확인하려면 다음 Gradle 명령어로 스크립트를 실행하세요:

./gradlew run

프로젝트가 빌드되고 실행될 때까지 기다리면 다음과 같은 결과가 표시됩니다:

> Task :app:run

Hello World!

BUILD SUCCESSFUL in 3s

3 actionable tasks: 2 executed, 1 up-to-date

Gradle 로그 메시지는 무시해도 됩니다. 대신 “Hello World!” 메시지에 집중하세요. 이는 스크립트에서 기대했던 정확한 출력 결과입니다. 즉, Kotlin 환경이 의도한 대로 작동하고 있음을 의미합니다.

이제 Kotlin으로 웹 스크래핑을 수행할 시간입니다!

웹 스크래핑 Kotlin 스크립트 구축하기

이 단계별 섹션에서는 Kotlin으로 웹 스크레이퍼를 구축하는 방법을 살펴보겠습니다. 특히 Quotes 스크레이핑 샌드박스 사이트에서 데이터를 추출하는 자동화 스크립트를 정의하는 방법을 배울 것입니다.

대략적으로, 여러분이 코딩할 Kotlin 웹 스크래핑 스크립트는 다음과 같은 작업을 수행합니다:

  1. 대상 페이지에 연결합니다.
  2. 페이지에서 인용문 HTML 요소를 선택합니다.
  3. 해당 요소에서 원하는 데이터를 추출합니다.
  4. 사이트의 모든 인용문에 대해 이 작업을 반복하며, 각 페이지네이션 페이지를 방문합니다.
  5. 수집된 데이터를 CSV 형식으로 내보냅니다.

대상 사이트의 모습은 다음과 같습니다:

Example from the Quotes to Scrape website

아래 단계를 따라 Kotlin으로 웹 스크래핑을 수행하는 방법을 알아보세요!

1단계: 스크래핑 라이브러리 설치

가장 먼저 목표에 가장 적합한 Kotlin 웹 스크래핑 라이브러리를 파악해야 합니다. 이를 위해 대상 사이트를 검사해야 합니다.

브라우저에서 Quotes To Scrape 샌드박스 사이트를 방문하세요. 빈 영역을 마우스 오른쪽 버튼으로 클릭하고 “검사” 옵션을 선택하여 개발자 도구를 엽니다. “네트워크” 탭으로 이동한 후 페이지를 다시 로드하고 “Fetch/XHR” 섹션을 살펴보세요.

다음과 같은 내용을 확인해야 합니다:

Network tab of the developer tools on Chrome

AJAX 요청이 없습니다! 즉, 대상 페이지가 JavaScript를 통해 동적으로 데이터를 가져오지 않습니다. 이는 서버가 HTML 코드에 필요한 모든 데이터를 포함하여 페이지를 클라이언트에게 반환한다는 의미입니다.

따라서 HTML 파싱 라이브러리만으로도 충분합니다. 브라우저 자동화 도구를 사용할 수는 있지만, 브라우저에서 페이지를 로드하고 렌더링하는 것은 성능 오버헤드만 발생시킬 뿐 실질적인 이점은 없습니다.

따라서 skrape{it} 은 웹 스크래핑 목표를 달성하는 데 탁월한 선택이 될 것입니다. build.gradle.kts 파일의 dependencies 객체에 다음 줄을 추가하여 프로젝트의 종속성에 포함시키세요:

implementation("it.skrape:skrapeit:1.2.2")

반면 Maven 사용자인 경우, pom.xml 파일의 <dependencies> 태그에 다음 줄을 추가하세요:

<dependency>

    <groupId>it.skrape</groupId>

    <artifactId>skrapeit</artifactId>

    <version>1.2.2</version>

</dependency>

IntelliJ IDEA를 사용 중이라면 IDE가 프로젝트 종속성을 재로드하고 새 라이브러리를 설치하는 버튼을 표시합니다. 이 버튼을 클릭하여 skrape{it}을 설치하세요.

동일하게, 다음 Gradle 명령어로 새 종속성을 수동으로 설치할 수도 있습니다:

./gradlew build --refresh-dependencies

설치 과정은 다소 시간이 걸릴 수 있으니 기다려 주세요.

다음으로 App.kt 스크립트에 다음 임포트 문구를 추가하여 skrape{it}을 사용할 준비를 하세요:

import it.skrape.core.*

import it.skrape.fetcher.*

skrape{it}에는 다양한 데이터 페쳐가 포함되어 있음을 잊지 마세요. 여기서는 간편함을 위해 모두 가져왔습니다. 동시에, 주어진 URL로 HTTP 요청을 보내고 파싱된 응답을 반환하는 클래식 HTTP 클라이언트인 HttpFetcher만 필요할 것입니다.

훌륭합니다! 이제 Kotlin으로 웹 스크래핑을 수행하는 데 필요한 모든 것을 갖추셨습니다!

2단계: 대상 페이지 다운로드 및 HTML 파싱

App.kt에서 App 클래스를 제거하고 skrape{it}을 사용하여 대상 페이지에 연결하기 위해 main() 함수에 다음 줄을 추가하세요:

skrape(HttpFetcher) {

// 지정된 URL로 HTTP GET 요청 수행

   request {

    url = "https://quotes.toscrape.com/"

   }

}

내부적으로 skrape{it}은 앞서 언급한 HttpFetcher 클래스를 사용하여 지정된 URL로 동기식 HTTP GET 요청을 수행합니다.

스크립트가 원하는 대로 작동하는지 확인하려면 skrape(HttpFetcher) 정의에 다음 섹션을 추가하세요:

response {

   // HTML 소스 코드를 가져와 출력

   htmlDocument {

       print(html)

   }

}

이는 skrape{it}이 서버 응답을 어떻게 처리할지 지시합니다. 구체적으로, 파싱된 응답에 접근한 후 페이지의 HTML 코드를 출력합니다.

이제 App.kt Kotlin 스크래핑 스크립트에는 다음 내용이 포함되어야 합니다:

package com.kotlin.scraper

import it.skrape.core.*

import it.skrape.fetcher.*

fun main() {

    skrape(HttpFetcher) {

// 지정된 URL로 HTTP GET 요청 수행

        request {

url = "https://quotes.toscrape.com/"

        }

response {

    // HTML 소스 코드를 가져와 출력

        htmlDocument {

            print(html)

        }

    }

}

스크립트를 실행하면 다음과 같이 출력됩니다:

<!doctype html>

<html lang="en"> 

 <head>

  <meta charset="UTF-8">

  <title>스크래핑할 인용문</title>

  <link rel="stylesheet" href="/static/bootstrap.min.css">

  <link rel="stylesheet" href="/static/main.css">

 </head>

 <body>

 <!-- 간결함을 위해 생략... -->

이것이 바로 대상 페이지의 HTML 코드입니다. 잘하셨습니다!

3단계: 페이지 콘텐츠 검사

다음 단계는 스크래핑 로직을 정의하는 것입니다. 하지만 페이지에서 요소를 선택하는 방법을 모른다면 어떻게 할 수 있을까요? 그래서 대상 페이지의 구조를 확인하는 추가 단계를 거치는 것이 중요합니다.

브라우저에서 다시 ‘Open Quotes To Scrape’를 엽니다. 인용문 요소 하나를 마우스 오른쪽 버튼으로 클릭하고 “검사”를 선택하여 아래와 같이 개발자 도구를 엽니다:

Inspecting the element of a specific quote

여기서 각 인용문 카드는 .quote HTML 요소로 감싸져 있음을 확인할 수 있습니다:

  1. 인용문 텍스트가 포함된 .text 요소
  2. 저자의 이름을 포함한 .author 요소.
  3. 단일 태그를 표시하는 여러 개의 .tag 요소.

모든 인용문에 태그 섹션이 있는 것은 아닙니다:

Example of a specific quote by Ayn Rand

위의 CSS 선택기는 페이지에서 원하는 DOM 요소를 선택하여 데이터를 추출하는 데 도움이 됩니다. 또한 이 데이터를 저장할 클래스가 필요합니다. 따라서 웹 스크래핑 Kotlin 스크립트 상단에 다음 Quote 클래스 정의를 추가하세요:

class Quote(var text: String, var author: String, tags: List<String>?) {

    var tags: MutableList<String> = ArrayList()

    init {

        if (tags != null) {

            this.tags.addAll(tags)

        }

    }

}

페이지에 여러 인용문이 포함되어 있으므로 main()에서 Quote 객체의 리스트를 인스턴스화하세요:

val quotes: MutableList<Quote> = ArrayList()

스크립트 마지막에 quotes에는 사이트에서 수집한 모든 인용문이 포함됩니다.

여기서 이해하고 정의한 내용을 활용하여 다음 단계에서 스크래핑 로직을 구현하세요!

4단계: 스크래핑 로직 구현

skrape{it}은 페이지에서 HTML 노드를 선택하는 독특한 방식을 사용합니다. 페이지에 CSS 선택자를 적용하려면, htmlDocument 내부에 CSS 선택자와 동일한 이름의 섹션을 정의해야 합니다:

skrape(HttpFetcher) {

    // 요청 섹션...

    response {

        htmlDocument {

            // 페이지 내 모든 ".quote" HTML 요소 선택

            ".quote" {

                // 스크래핑 로직...

            }

        }

    }

}

“.quote” 섹션 내부에서 findAll 섹션을 정의할 수 있습니다. 이 섹션은 지정된 CSS 선택기로 선택된 각 quote HTML 노드에 적용될 로직을 포함합니다. 반면 findFirst는 선택된 첫 번째 요소만 반환합니다.

배경에서는 이러한 모든 섹션이 Kotlin 람다 함수에 불과합니다. 따라서 findAll 내부의 forEach 섹션에서 이를 통해 단일 DOM 요소에 접근할 수 있습니다. 익숙하지 않다면, 이는 람다의 단일 매개변수에 대한 암시적 이름입니다.

메서드와 속성을 기반으로 유사한 논리를 따릅니다. 이후 각 인용문에서 원하는 데이터를 추출하는 스크래핑 로직을 구현하고, Quote 객체를 생성하여 다음과 같이 quotes 목록에 추가할 수 있습니다:

".quote" {

findAll {

forEach {

// 단일 quote 요소에 대한 스크래핑 로직

            val text = it.findFirst(".text").text

            val author = it.findFirst(".author").text

            val tags = try {

                it.findAll(".tag").map { tag -> tag.text }

            } catch(e: ElementNotFoundException) {

                null

            }

// Quote 객체 생성 및 목록에 추가

            val quote = Quote(

                text = text,

                author = author,

                tags = tags

            )

            quotes.add(quote)

        }

    }

}

text 속성을 통해 HTML 요소의 내부 텍스트를 가져올 수 있습니다. 모든 quote HTML 요소가 태그를 포함하는 것은 아니므로 ElementNotFoundException을 처리해야 합니다. 이 예외는 지정된 CSS 선택자가 페이지의 어떤 노드와도 일치하지 않을 때 findAll에 의해 발생합니다.

ElementNotFoundException을 다음으로 임포트하세요:

import it.skrape.selects.ElementNotFoundException

모든 스니펫을 결합하고 quotes 배열에 포함된 데이터를 로깅합니다:

package com.kotlin.scraper

import it.skrape.core.*

import it.skrape.fetcher.*

import it.skrape.selects.ElementNotFoundException

// Kotlin에서 스크랩된 데이터를 표현할 클래스 정의

class Quote(var text: String, var author: String, tags: List<String>?) {

    var tags: MutableList<String> = ArrayList()

    init {

        if (tags != null) {

            this.tags.addAll(tags)

        }

    }

}

fun main() {

    // 스크랩된 데이터 저장 위치

    val quotes: MutableList<Quote> = ArrayList()

    skrape(HttpFetcher) {

// 지정된 URL로 HTTP GET 요청 수행

        request {

url = "https://quotes.toscrape.com/"

        }

response {

htmlDocument {

// 페이지 내 모든 ".quote" HTML 요소 선택

                ".quote" {

findAll {

forEach {

// 단일 인용문 요소에 대한 스크래핑 로직

                            val text = it.findFirst(".text").text

                            val author = it.findFirst(".author").text

                            val tags = try {

                                it.findAll(".tag").map { tag -> tag.text }

                            } catch(e: ElementNotFoundException) {

                                null

                            }

// Quote 객체 생성 및 리스트에 추가

                            val quote = Quote(

                                text = text,

                                author = author,

                                tags = tags

                            )

                            quotes.add(quote)

                        }

                    }

                }

            }

        }

    }

// 스크랩한 데이터 출력

    for (quote in quotes) {

        println("Text: ${quote.text}")

        println("Author: ${quote.author}")

        println("Tags: ${quote.tags.joinToString("; ")}")

        println()

    }

}

태그 목록을 쉼표로 구분된 문자열로 병합하기 위해 joinToString()을 사용한 점에 유의하십시오.

스크립트를 실행하면 다음과 같은 결과가 출력됩니다:

Text: “우리가 창조한 세상은 우리의 사고 과정이다. 우리의 사고를 바꾸지 않고서는 세상을 바꿀 수 없다.”

Author: Albert Einstein

Tags: change; deep-thoughts; thinking; world

# 간결함을 위해 생략...

Text: “햇빛 없는 하루는, 알다시피, 밤과 같다.”

Author: Steve Martin

Tags: humor; obvious; simile

와! 방금 Kotlin으로 웹 스크래핑을 수행하는 방법을 배웠습니다!

5단계: 크롤링 로직 추가하기

단일 페이지에서 데이터를 스크래핑했지만, 명언 목록은 여러 페이지에 걸쳐 있습니다. 페이지 끝까지 스크롤하면 다음 페이지로 연결되는 “다음 →” 버튼이 있습니다:

"Next" text inside an <a href> tag

마지막 페이지를 제외한 모든 페이지에서 이 버튼이 나타납니다:

The inspection of the last page code

Kotlin으로 웹 크롤링을 수행하고 사이트의 각 명언을 스크래핑하려면 다음을 수행해야 합니다:

  1. 현재 페이지의 모든 인용문을 스크래핑합니다.
  2. “다음 →” 요소가 존재하면 이를 선택하고, 다음 페이지의 URL을 추출합니다.
  3. 새 페이지에서 첫 번째 단계를 반복합니다.

위 알고리즘을 다음과 같이 구현합니다:

단일 페이지만 추출하고 중지하는 대신, 이제 스크립트는 while 루프를 사용합니다. 이 루프는 더 이상 추출할 페이지가 없을 때까지 반복됩니다. 이는 .next a CSS 선택자가 ElementNotFoundException 예외를 발생시킬 때 발생하며, 이는 “다음 →” 버튼이 페이지에 없음을 의미하므로 사이트의 마지막 페이지네이션 페이지에 도달한 것입니다.

htmlDocument 섹션에는 여러 CSS 선택기 섹션이 포함될 수 있습니다. 각 섹션은 지정된 순서대로 실행됩니다. 웹 스크래핑 Kotlin 스크립트를 다시 실행하면 이제 사이트의 모든 100개 명언이 저장됩니다.

훌륭합니다! Kotlin 웹 스크래핑 및 크롤링 로직이 준비되었습니다. 이제 데이터 내보내기 로직과 함께 로깅 코드만 제거하면 됩니다.

7단계: 스크래핑된 데이터를 CSV로 내보내기

수집된 데이터는 현재 Quote 객체 목록에 저장됩니다. 터미널에 출력하는 것도 유용하지만, CSV로 내보내는 것이 데이터를 최대한 활용하는 최선의 방법입니다. 이를 통해 팀의 다른 구성원들도 해당 데이터를 필터링하고, 읽고, 분석할 수 있습니다.

Kotlin은 CSV 파일을 생성하고 데이터를 채우는 데 필요한 모든 기능을 제공하지만, 라이브러리를 사용하면 작업이 훨씬 간편해집니다. CSV 파일 읽기/쓰기에 널리 사용되는 Kotlin 네이티브 라이브러리는 kotlin-csv입니다.

build.gradle.kts의 프로젝트 종속성에 추가하세요:

implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.3")

Maven을 사용하는 경우:

<dependency>

    <groupId>com.github.doyaaaaaken</groupId>

    <artifactId>kotlin-csv-jvm</artifactId>

    <version>1.9.3</version>

</dependency>

라이브러리를 설치하고 App.kt 파일에 임포트하세요:

import com.github.doyaaaaaken.kotlincsv.dsl.*

이제 몇 줄의 코드로 인용문을 CSV 파일로 내보낼 수 있습니다:

val header = listOf("quote", "author", "tags")

val csvContent: List<List<String>> = quotes.map { quote ->

listOf(

        quote.text,

        quote.author,

        quote.tags.joinToString("; ")

        )

}

csvWriter().open("quotes.csv") {

writeRow(header)

    writeRows(csvContent)

}

참고: kotlin-csv에서 CSV 레코드는 List<String>으로 표현됩니다. 먼저 헤더 행을 위한 레코드를 정의합니다. 그런 다음 인용문을 원하는 데이터로 변환합니다. 다음으로 CSV 라이터를 초기화하고 quotes.csv 파일을 생성한 후 writeRow() 및 writeRows()로 데이터를 채웁니다.

자, 이제 Kotlin 웹 스크래핑 스크립트의 최종 코드를 살펴볼 차례입니다.

8단계: 모든 것을 합치기

다음은 Kotlin 스크레이퍼의 최종 코드입니다:

package com.kotlin.scraper

import it.skrape.core.*

import it.skrape.fetcher.*

import it.skrape.selects.ElementNotFoundException

import com.github.doyaaaaaken.kotlincsv.dsl.*

// Kotlin에서 스크래핑된 데이터를 표현할 클래스 정의

class Quote(var text: String, var author: String, tags: List<String>?) {

    var tags: MutableList<String> = ArrayList()

    init {

        if (tags != null) {

            this.tags.addAll(tags)

        }

    }

}

fun main() {

    // 스크랩된 데이터를 저장할 위치

    val quotes: MutableList<Quote> = ArrayList()

    // 방문할 다음 페이지의 URL

    var nextUrl: String? = "https://quotes.toscrape.com/"

    // 방문할 페이지가 있을 때까지

    while (nextUrl != null) {

        skrape(HttpFetcher) {

            // 지정된 URL로 HTTP GET 요청 수행

            request {

                url = nextUrl!!

            }

            response {

                htmlDocument {

                    // 페이지 내 모든 ".quote" HTML 요소 선택

                    ".quote" {

                        findAll {

                            forEach {

                                // 단일 인용문 요소 스크래핑 로직

                                val text = it.findFirst(".text").text

                                val author = it.findFirst(".author").text

                                val tags = try {

                                    it.findAll(".tag").map { tag -> tag.text }

                                } catch (e: ElementNotFoundException) {

                                    null

                                }

                                // Quote 객체 생성 및 목록에 추가

                                val quote = Quote(

                                    text = text,

                                    author = author,

                                    tags = tags

                                )

                                quotes.add(quote)

                            }

                        }

                    }

                    // 크롤링 로직

                    try {

                        ".next a" {

                            findFirst {

                                nextUrl = "https://quotes.toscrape.com" + attribute("href")

                            }

                        }

                    } catch (e: ElementNotFoundException) {

                        nextUrl = null

                    }

                }

            }

        }

    }

    // "quotes.csv" 파일 생성 및 스크랩된 데이터로 채움

    //

    val header = listOf("quote", "author", "tags")

    val csvContent: List<List<String>> = quotes.map { quote ->

        listOf(

            quote.text,

            quote.author,

            quote.tags.joinToString("; ")

            )

    }

    csvWriter().open("quotes.csv") {

        writeRow(header)

        writeRows(csvContent)

    }

}

믿을 수 있나요? skrape{it} 덕분에 100줄도 안 되는 코드로 사이트 전체 데이터를 가져올 수 있습니다!

웹 스크래핑 Kotlin 스크립트를 실행하려면 다음을 사용하세요:

./gradlew run

스크레이퍼가 대상 사이트의 각 페이지를 처리하는 동안 잠시 기다려 주세요. 완료되면 프로젝트 루트 디렉토리에 quotes.csv 파일이 생성됩니다. 파일을 열면 다음과 같은 데이터를 확인할 수 있습니다:

list of scraped quotes

자, 이제 온라인 페이지의 비정형 데이터가 탐색하기 쉬운 CSV 파일로 변환되었습니다!

프록시를 사용해 Kotlin에서 IP 차단 방지하기

Kotlin으로 웹 스크래핑을 수행할 때 가장 큰 과제 중 하나는 봇 방지 기술에 의해 차단되는 것입니다. 이러한 시스템은 스크립트의 자동화된 특성을 감지하고 IP를 차단할 수 있습니다. 이렇게 하면 스크래핑 작업이 중단됩니다.

이를 피하는 방법은? 웹 프록시를 사용하세요!

아래 단계를 따라 Bright Data 프록시를 Kotlin에 통합하는 방법을 알아보세요.

Bright Data에서 프록시 설정하기

Bright Data는 시중 최고의 프록시 서버로, 전 세계 수천 개의 프록시 서버를 모니터링합니다. IP 로테이션을 고려할 때 가장 적합한 프록시 유형은 주거용 프록시입니다.

시작하려면 계정이 있다면 Bright Data에 로그인하세요. 없으면 무료로 계정을 생성하세요. 다음 사용자 대시보드를 이용할 수 있습니다:

Bright Data's control panel main page

아래와 같이 “프록시 제품 보기” 버튼을 클릭하세요:

Clicking on view proxy products

다음과 같은 “프록시 및 스크래핑 인프라” 페이지로 이동합니다:

The proxies and scraping infrastructure services list on Bright Data's control panel

아래로 스크롤하여 “주거용 프록시” 카드를 찾은 후 “시작하기” 버튼을 클릭하세요:

Getting started with the residential proxies network

주거용 프록시 설정 대시보드로 이동합니다. 안내된 마법사를 따라 필요에 맞게 프록시 서비스를 설정하세요. 프록시 설정 방법에 대해 궁금한 점이 있으면 24시간 연중무휴 지원팀에 문의하세요.

Configuring the residential proxy settings

“액세스 매개변수” 탭으로 이동하여 다음과 같이 프록시의 호스트, 포트, 사용자 이름, 비밀번호를 확인하세요:

Copying the access parameters for the residential proxies

“호스트” 필드에는 이미 포트가 포함되어 있음을 유의하세요.

이 정보만 있으면 프록시 URL을 생성하여 skrape{it}에서 사용할 수 있습니다. 모든 정보를 조합하여 다음 구문으로 URL을 생성하세요:

<사용자명>:<비밀번호>@<호스트>

예를 들어, 이 경우 다음과 같습니다:

brd-customer-hl_4hgu8dwd-zone-residential:[email protected]:XXXXX

“Active proxy”를 토글하고 마지막 지침을 따르시면 준비 완료입니다!

Activating the proxy once all configurations are done

Kotlin에서 프록시 통합하기

skrape{it}에서 Bright Data 통합을 위한 스니펫은 다음과 같습니다:

skrape(HttpFetcher) {

    request {

        url = "https://quotes.toscrape.com/"

        proxy = proxyBuilder {

            type = Proxy.Type.HTTP

            host = "brd.superproxy.io"

            port = XXXXX

        }

        authentication = basic {

            username = "brd-customer-hl_4hgu8dwd-zone-residential"

            password = "ZZZZZZZZZZ"

        }

    }

    // ...

}

보시다시피, 모든 것은 프록시와 인증 요청 옵션을 사용하는 것으로 귀결됩니다. 이제부터 skrape{it}은 Bright Data 프록시를 통해 지정된 URL로 요청을 수행할 것입니다. IP 차단 안녕!

Kotlin 웹 스크래핑 작업을 윤리적이고 존중하는 방식으로 수행하세요

웹 스크래핑은 다양한 용도로 유용한 데이터를 수집하는 효과적인 방법입니다. 최종 목표는 해당 데이터를 가져오는 것이지 대상 사이트를 손상시키는 것이 아님을 명심하세요. 따라서 올바른 예방 조치를 취하며 이 작업에 접근해야 합니다.

책임감 있는 Kotlin 웹 스크래핑을 위해 아래 팁을 따르세요:

  • 공개적으로 이용 가능한 정보만 대상으로 하세요: 사이트에서 공개적으로 접근 가능한 데이터 수집에집중하세요 . 로그인 자격 증명이나 기타 인증 방식으로 보호되는 페이지는 피하세요. 적절한 허가 없이 사적 또는 민감한 데이터를 스크래핑하는 것은 비윤리적이며 법적 결과를 초래할 수 있습니다.
  • robots.txt 파일 준수: 모든 사이트에는 자동 크롤러의 페이지 접근 방식을 정의하는 robots.txt 파일이 있습니다. 윤리적인 스크래핑 관행을 유지하려면 해당 지침을 반드시 준수해야 합니다. 자세한 내용은 웹 스크래핑용 robots.txt 가이드에서 확인하세요.
  • 요청 빈도를 제한하세요: 짧은 시간에 너무 많은 요청을하면 서버 과부하가 발생하여 모든 사용자의 사이트 성능에 영향을 미칩니다. 이는 속도 제한 조치로 이어져 차단될 수도 있습니다. 따라서 요청 간에 무작위 지연을 추가하여 대상 서버에 과도한 부하가 걸리지 않도록 하세요.
  • 사이트 이용약관 확인 및 준수: 사이트 스크래핑 전 반드시 이용약관을 검토하세요. 여기에는 저작권, 지적 재산권 정보 및 데이터 사용 방법과 시기에 관한 지침이 포함될 수 있습니다.
  • 신뢰할 수 있고 최신 상태의 스크래핑 도구 사용: 평판이 좋은 공급자를선택하고 , 잘 관리되며 정기적으로 업데이트되는 도구와 라이브러리를 선택하세요. 그래야만 최신 윤리적 Kotlin 스크래핑 원칙에 부합함을 보장할 수 있습니다. 의문이 있다면, 최고의 웹 스크래핑 서비스 선택 방법에 관한 저희 글을 참고하세요.

결론

이 가이드에서는 Kotlin이 특히 Java와 비교할 때 웹 스크래핑에 탁월한 언어인 이유를 살펴보았습니다. 또한 최고의 Kotlin 스크래핑 라이브러리 목록을 확인했습니다. 이어서 skrape{it}을 활용해 실제 사이트의 여러 페이지에서 데이터를 추출하는 스크래퍼를 구축하는 방법을 배웠습니다. 여기서 경험했듯이 Kotlin을 이용한 웹 스크래핑은 간단하며 몇 줄의 코드만으로 가능합니다.

스크래핑 작업의 주요 도전 과제는 봇 방지 솔루션입니다. 웹사이트는 자동화된 스크립트로부터 데이터를 보호하기 위해 이러한 시스템을 도입하여 페이지 접근 전에 차단합니다. 이를 모두 우회하는 것은 쉽지 않으며 고급 도구가 필요합니다. 다행히 Bright Data가 해결책을 제공합니다!

Bright Data가 제공하는 스크래핑 제품 일부는 다음과 같습니다:

  • 웹 스크레이퍼 API: 수십 개의 인기 도메인에서 구조화된 웹 데이터에 프로그래밍 방식으로 접근할 수 있는 사용하기 쉬운 API입니다.
  • 스크래핑 브라우저: 클라우드 기반 제어 가능 브라우저로, 자바스크립트 렌더링 기능을 제공하며 브라우저 지문 인식, CAPTCHA, 자동 재시도 등을 처리해 줍니다. Playwright, Puppeteer 등 가장 널리 사용되는 자동화 브라우저 라이브러리와 통합됩니다.
  • 웹 언락커: 모든 페이지의 원시 HTML을 원활하게 반환하여 모든 스크래핑 방지 조치를 우회하는 언락킹 API입니다.

웹 스크래핑 자체는 다루고 싶지 않지만 온라인 데이터에는 여전히 관심이 있으신가요? Bright Data의 즉시 사용 가능한 데이터 세트를 살펴보세요!