Mobile/안드로이드

[안드로이드/android] jsoup을 이용해 웹 크롤링하기 (동행복권 당첨 숫자 가져오기)

냠냠:) 2020. 6. 25. 18:44

Jsoup이란?

자바로 만들어진 HTML 파서. 즉, URL, 파일, 문자열을 소스로 하여 HTML을 파싱 할 수 있는 자바 라이브러리이다.

 

 

jar 파일을 다운로드 받아 라이브러리에 직접 추가해도 되고, 아래와 같이 gradle에 추가해도 된다.

implementation 'org.jsoup:jsoup:1.11.3'        

 

인터넷을 통해 데이터를 가져올 것이기 때문에 Manifest 부분에 아래 퍼미션을 추가한다.

<uses-permission android:name="android.permission.INTERNET"/>

 

1. XML 정의

jsoup을 알게되었을 때 이론보다는 실습을 통해 먼저 공부했다. 이후 여러 블로그들을 다니면서 jsoup의 개념을 알게 되었지만 실습을 통해 알게 된 부분이 더 많았으므로 실습을 바로 해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
 
    <TextView
        android:id="@+id/number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
</androidx.constraintlayout.widget.ConstraintLayout>
cs

- activity_main.xml에 크롤링한 숫자를 띄어줄 TextView를 간단하게 만들어준다.

 

 

2. 크롤링할 웹페이지 선정

 

그 다음 크롤링할 동행 복권 사이트를 들어가서 우리가 원하는 당첨번호가 Elements의 어느 부분에 있는지 확인해준다. 우리가 원하는 당첨번호들은 span태그에 id가 drwtNo1 ~ drwtNo6에 적혀있었고, 보너스 번호는 bnusNo으로 id를 가지고 있었다. 우리는 이 부분을 가져올 것이다.

 

 

3. MainActivity - 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
     String nums;                    //복권 번호을 저장할 변수
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        textView = (TextView) findViewById(R.id.number);
 
 
        Document doc = null;
        try {
            doc = Jsoup.connect("https://dhlottery.co.kr/common.do?method=main").get();
            Elements contents = doc.select("#lottoDrwNo");          //회차 id값 가져오기
            nums += contents.text() +"회 :";
 
            for(int i = 1; i < 7; i++){
                contents = doc.select("#drwtNO"+i);                 //복권 번호 6개 가져오기
                nums += " "+contents.text();
            }
            nums += doc.select("#bnusNo").text();                   //보너스 번호 contents 변수를 사용하지 않고 가져오는 방법
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        textView.setText(nums);
 
    }
cs

위와 같이 Jsoup을 이용해 동행 복권 사이트에 연결하여 우리가 원하는 값들을 가져올 수 있다.

하지만 이렇게만 하고 코드를 끝내면 아래와 같은 엄청난 에러가 뜬다.

이유는 프로그램 단에서는 네트워크에서 데이터를 받아오려면 별도의 Thread가 필요하기 때문이다. 만약 메인 Thread에서 네트워크를 통해 데이터를 받아오는 과정에서 오류가 발생한다면 앱 전체 실행에 영향을 줄 수 있고, 추가로 많은 양의 데이터를 받아오는 과정에서 하나의 Thread로만 프로그램이 실행된다면 긴 시간을 데이터를 받아오는 데 사용하기 때문이다. 이를 방지하기 위함인 것으로 보인다.

 

 

4. Thread를 적용한 MainActivity 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Thread(){
            @Override
            public void run() {
                Document doc = null;
                try {
                    doc = Jsoup.connect("https://dhlottery.co.kr/common.do?method=main").get();
                    Elements contents = doc.select("#lottoDrwNo");          //회차 id값 가져오기
                    nums += contents.text() +"회 :";
 
                    for(int i = 1; i < 7; i++){
                        contents = doc.select("#drwtNo"+i);                 //복권 번호 6개 가져오기
                        nums += " "+contents.text();
                    }
                    nums += doc.select("#bnusNo").text();                   //보너스 번호 contents 변수를 사용하지 않고 가져오는 방법
 
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
cs

크롤링을 하는 부분을 이렇게 Thread()로 묶어주면 메인 쓰레드가 아닌 또 다른 쓰레드에서 크롤링을 담당하게 된다.

하지만 이렇게 해도 우리는 직접적으로 View에 UI를 건드리지 못한다.

무슨말이냐면 View에 데이터를 뿌려주는 역할은 메인 쓰레드에서 담당한다. 만약 Thread() 안에서 textView.setText()를 사용하면 오류가 뜰 것이다. 이는 Handler(핸들러)를 통해 Thread() 내부에서 메인 쓰레드로 데이터를 보내주어야 한다.

 

 

5. Handler 기능을 추가한 MainActivity (전체 코드)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MainActivity extends AppCompatActivity {
    String nums;                    //복권 번호을 저장할 변수
    TextView textView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.number);
        final Bundle bundle = new Bundle();
 
        new Thread(){
            @Override
            public void run() {
                Document doc = null;
                try {
                    doc = Jsoup.connect("https://dhlottery.co.kr/common.do?method=main").get();
                    Elements contents = doc.select("#lottoDrwNo");          //회차 id값 가져오기
                    nums += contents.text() +"회 :";
 
                    for(int i = 1; i < 7; i++){
                        contents = doc.select("#drwtNo"+i);                 //복권 번호 6개 가져오기
                        nums += " "+contents.text();
                    }
                    nums += doc.select("#bnusNo").text();                   //보너스 번호 contents 변수를 사용하지 않고 가져오는 방법
 
                    bundle.putString("numbers", nums);                               //핸들러를 이용해서 Thread()에서 가져온 데이터를 메인 쓰레드에 보내준다.
                    Message msg = handler.obtainMessage();
                    msg.setData(bundle);
                    handler.sendMessage(msg);
 
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
 
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Bundle bundle = msg.getData();
            textView.setText(bundle.getString("numbers"));                      //이런식으로 View를 메인 쓰레드에서 뿌려줘야한다.
        }
    };
}
cs

 

6. 결과화면 

출력이 잘되는 모습을 볼 수 있다.

 

 

 

 

 

여기까지 Jsoup을 이용해 동행 복권 사이트에서 이번 주 당첨번호를 가져오는 실습을 해보았다.

Jsoup은 안드로이드 프로젝트를 시작하면서 웹에서 데이터를 가져와야 하는 상황이 생겨 알게 되었다.

Jsoup을 이용한 웹 크롤링을 알아보면서 다양한 지식들을 얻을 수 있었다. 동적 웹페이지를 크롤링하는 Selenium, WebView를 통한 웹 크롤링, 파이썬으로 웹 크롤링하는 방법, 유저 에이전트, REST API 사용 방법, 크롬 개발자 도구로 Request, Response 활용하기 등 다양한 지식들을 알게 되었다. 

시간이 된다면 다음에는 파이썬으로 웹 크롤링하는 예제도 한번 올려보겠다.

반응형