본문 바로가기
안드로이드 자바

[Java][Android] 안드로이드 웹 스크래퍼 만들기: Jsoup으로 간단한 뉴스 크롤러 구현

by teamnova 2025. 1. 6.
728x90

이번에는 Jsoup을 사용해서 웹 페이지의 내용을 가져와 보겠습니다.

 

https://jsoup.org/

jsoup: 자바 HTML 파서
jsoup는 실제 HTML 및 XML 작업을 간소화하는 Java 라이브러리입니다. DOM API 메서드, CSS 및 xpath 선택기를 사용하여 URL 페칭, 데이터 구문 분석, 추출 및 조작을 위한 사용하기 쉬운 API를 제공합니다.

jsoup는 WHATWG HTML5 사양을 구현하고 최신 브라우저와 동일한 DOM으로 HTML을 파싱합니다.

URL, 파일 또는 문자열에서 HTML을 스크래핑하고 구문 분석합니다 .
DOM 트래버설이나 CSS 선택기를 사용하여 데이터를 찾아 추출합니다 .
HTML 요소 , 속성, 텍스트를 조작합니다.
XSS 공격을 방지하기 위해 안전 목록에 있는 사용자가 제출한 콘텐츠를 정리합니다 .
깔끔한 HTML을 출력합니다 .

 

 

그럼 왜 웹 스크래퍼를 만들어서 사용할까요?

 

  • 자동화된 데이터 수집
    • 반복적으로 웹페이지에서 정보를 복사해오는 번거로운 작업을 자동화합니다.
    • 시간과 노력을 절약할 수 있습니다.
  • 외부 데이터 활용
    • 기존에 제공되지 않는 API 없이도 필요한 정보를 활용할 수 있습니다.
    • 예: 특정 웹페이지의 데이터만 제공될 경우 직접 스크래핑으로 데이터 수집
  • 커스터마이징 가능
    • 원하는 데이터를 선택적으로 추출할 수 있어 효율적입니다.
    • 특정 요소(예: 제목, 본문, 이미지 등)를 선택적으로 처리 가능
  • 앱 기능 확장
    • 실시간 정보를 제공하는 앱을 제작하거나, 외부 데이터를 기반으로 새로운 기능을 제공할 수 있습니다.
  • 경제적 효율성
    • 비용이 드는 API를 사용하지 않아도 데이터를 얻을 수 있는 방법이 됩니다

그럼 어디에 사용하면 좋을까요?

 

  • 뉴스 앱이나 콘텐츠 수집 앱 개발
    • 특정 웹사이트의 뉴스, 블로그, 또는 콘텐츠를 가져와 앱 사용자들에게 보여줄 수 있습니다.
    • 예: 실시간 뉴스 피드, 블로그 통합 뷰어
  • 데이터 분석 및 리서치 도구
    • 특정 웹페이지에서 필요한 데이터를 수집하여 분석에 활용할 수 있습니다.
    • 예: 상품 가격 비교, 키워드 트렌드 분석
  • 교육 및 학습 앱
    • 학습 자료나 참고 문서를 웹에서 수집해 제공하는 앱을 만들 수 있습니다.
    • 예: 특정 주제의 최신 논문, 기술 자료 제공
  • 이벤트 또는 정보 통합 플랫폼
    • 지역 행사, 스포츠 경기 일정, 또는 기타 정보를 여러 웹사이트에서 수집해 한 곳에서 보여주는 데 사용할 수 있습니다.
    • 예: 영화 상영 정보, 스포츠 경기 결과 모니터링
  • 퍼스널 프로젝트
    • 개인이 관심 있는 데이터를 자동으로 모으고 정리하는 데 활용할 수 있습니다.
    • 예: 주식 시장 데이터 추적, 여행 정보 수집

 

 

정보를 가져옴에 따라 주의 사항이 몇가지 있습니다.

 

  • 법적 문제
    • 웹 스크래핑은 사이트 이용 약관에 따라 법적 문제가 발생할 수 있으므로 사전에 확인이 필요합니다.
  • 웹사이트 구조 변경
    • 웹페이지 구조가 변경되면 스크래퍼가 작동하지 않을 수 있으므로 지속적인 유지보수가 필요합니다.
  • 성능
    • 큰 데이터를 스크래핑하면 성능 이슈가 발생할 수 있으므로 최적화가 필요합니다.

 

MainActivity

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;

import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    TextView resultTextView;
    EditText urlText;
    ExecutorService executorService;
    String TAG = "MainActivity";
    Button urlButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 스크랩 해올 내용 보여주기 뷰
        resultTextView = findViewById(R.id.resultTextView);
        // 스크랩 할 주소
        urlText = findViewById(R.id.urlEditText);
        // 스크랩 찾기 버튼
        urlButton = findViewById(R.id.searchButton);
        // ExecutorService 초기화
        executorService = Executors.newSingleThreadExecutor();

        urlButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String url = urlText.getText().toString().trim();
                if (!url.isEmpty()) {
                    scrapeWebData(url);
                } else {
                    urlText.setText("주소를 입력하세요!");
                }
            }
        });
    }

    void scrapeWebData(String url) {
        executorService.execute(() -> {
            try {

                // Jsoup으로 HTML 문서 가져오기
                Document doc = Jsoup.connect(url).get();

                // 웹 페이지 제목 가져오기
                String title = doc.title();

                // 특정 태그(<p>) 데이터 가져오기
                Elements paragraphs = doc.select("p");
                StringBuilder content = new StringBuilder("Title: " + title + "\n\nContent:\n");

                for (int i = 0; i < Math.min(paragraphs.size(), 5); i++) {
                    content.append(paragraphs.get(i).text()).append("\n");
                }

                // 메인 스레드에서 UI 업데이트
                new Handler(Looper.getMainLooper()).post(() -> resultTextView.setText(content.toString()));

            } catch (IOException e) {
                e.printStackTrace();
                new Handler(Looper.getMainLooper()).post(() -> resultTextView.setText("Error: " + e.getMessage()));
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
        }
    }
}

 

activity_main.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/urlEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="웹사이트 URL을 입력하세요"
        android:inputType="textUri"
        android:padding="8dp" />

    <Button
        android:id="@+id/searchButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="검색"
        android:layout_marginTop="8dp" />

    <TextView
        android:id="@+id/resultTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:padding="8dp"
        android:background="#EEEEEE"
        android:scrollbars="vertical"
        android:overScrollMode="always"
        android:gravity="start"
        android:text = "Loading"
        android:textSize="14sp" />

</LinearLayout>

 

 

시연영상