본문 바로가기
[ Developer ]/Android

[Android] 안드로이드 Jsoup 이용한 HTML 파싱

by 김현섭. 2016. 8. 13.
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Android JSOUP 이용해 HTML 파싱하기

Android에서 JSOUP을 이용해서 HTML을 파싱을 해보자
우선 이전에 실습했던 비콘 App에서 진행을 한다

가장 최근 로또 당첨 번호를 가져와보자
이전에 생성한 CouponActivity에서 진행을 한다


* activity_coupon.xml
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.ktds.hskim.mybeaconapplication.CouponActivity">
 
    <TextView
        android:id="@+id/tvLatestLotto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
 
        <ImageView
            android:id="@+id/ivNumber1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <TextView
            android:text="+"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <ImageView
            android:id="@+id/ivNumber7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
    </LinearLayout>
    
    <TextView
        android:id="@+id/tvWinGameCount"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    
    <TextView
        android:id="@+id/tvWinGameMoney"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
</LinearLayout>
 
cs


이제 CouponActivity.java에 가서 로직을 작성한다
우선 모든 이미지뷰 텍스트 뷰를 가져온다


* CouponActivity
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
public class CouponActivity extends AppCompatActivity {
 
    private TextView tvLatestLotto;
    private ImageView ivNumber1;
    private ImageView ivNumber2;
    private ImageView ivNumber3;
    private ImageView ivNumber4;
    private ImageView ivNumber5;
    private ImageView ivNumber6;
    private ImageView ivNumber7;
 
    private TextView tvWinGameCount;
    private TextView tvWinGameMoney;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_coupon);
 
        tvLatestLotto = (TextView) findViewById(R.id.tvLatestLotto);
        ivNumber1 = (ImageView) findViewById(R.id.ivNumber1);
        ivNumber2 = (ImageView) findViewById(R.id.ivNumber2);
        ivNumber3 = (ImageView) findViewById(R.id.ivNumber3);
        ivNumber4 = (ImageView) findViewById(R.id.ivNumber4);
        ivNumber5 = (ImageView) findViewById(R.id.ivNumber5);
        ivNumber6 = (ImageView) findViewById(R.id.ivNumber6);
        ivNumber7 = (ImageView) findViewById(R.id.ivNumber7);
 
        tvWinGameCount = (TextView) findViewById(R.id.tvWinGameCount);
        tvWinGameMoney = (TextView) findViewById(R.id.tvWinGameMoney);
cs


인터넷을 접근해야 하므로 Manifest에서 인터넷 접근 권한을 준다
그리고 권한을 가져와야 하므로 이전에 제공한 PermissionRequester 클래스를 하나 등록한다


* PermissionRequester
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import android.app.Activity;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
 
/**
 * Created by Minchang Jang on 2016-06-12.
 */
public class PermissionRequester {
 
    /**
     * 요청 AndroidOS의 버젼이 마쉬멜로우 이상 버젼이 아닐 경우
     */
    public static final int NOT_SUPPORT_VERSION = 2;
 
    /**
     * 요청 권한을 이미 가지고 있을 경우
     */
    public static final int ALREADY_GRANTED = -1;
 
    /**
     * 권한을 System에게 요청한 경우
     * Activity의 onRequestPermissionsResult() 로 결과 리턴됨.
     */
    public static final int REQUEST_PERMISSION = 0;
 
    private Activity context;
    private Builder builder;
 
    private void setBuilder(Builder builder) {
        this.builder = builder;
    }
 
    private PermissionRequester(Activity context) {
        this.context = context;
    }
 
    public int request(final String permission, final int requestCode, final OnClickDenyButtonListener denyAction) {
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            /*
             * 해당 App이 특정 권한을 가지고 있는지 검사함.
             * 리턴결과는 PackageManager.PERMISSION_DENIED 와 PackageManager.PERMISSION_GRANTED로 나눠짐.
             * PackageManager.PERMISSION_DENIED : 권한이 없음
             * PackageManager.PERMISSION_GRANTED : 권한이 있음.
             */
            int permissionCheck = ContextCompat.checkSelfPermission(context, permission);
 
            /*
             * 해당 권한이 없을 경우 처리 방법
             */
            if (permissionCheck == PackageManager.PERMISSION_DENIED) {
 
                /*
                 * 권한을 취득할 때 사용자로부터 확인을 받아야 하는지 확인
                 * 여기서 true가 나올 경우는 해당 앱에서 한번이라도 권한을 Deny한 경우일 때 말고는 없음.
                 * 권한에 대해서 허가하지 않은 경우 다시 한번 권한의 취득을 위해 사용자에게 이유를 고지해야 함.
                 * Marshmellow 버젼 이상부터 사용가능함.
                 */
                if (context.shouldShowRequestPermissionRationale(permission)) {
 
                    /*
                     * 권한 취득해야 하는 이유를 Dialog 등을 통해서 알린다.
                     */
                    AlertDialog.Builder dialog = new AlertDialog.Builder(context);
                    dialog.setTitle(builder.getTitle())
                            .setMessage(builder.getMessage())
                            .setPositiveButton(builder.getPositiveButtonName(), new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    /*
                                     * 권한의 취득을 요청한다.
                                     * 취득하고자 하는 권한을 배열에 넣고 요청한다.
                                     * 뒤에 들어가는 파라미터(requestCode)는 onRequestPermissionsResult() 에서 권한 취득 결과에서 사용된다.
                                     * startActiviryForResult의 Request Code와 유사함.
                                     */
                                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                        context.requestPermissions(new String[]{permission}, requestCode);
                                    }
                                }
                            })
                            .setNegativeButton(builder.getNegativeButtonName(), new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    denyAction.onClick(context);
                                }
                            }).create().show();
 
                    return REQUEST_PERMISSION;
                } else {
                    /*
                     * 권한의 취득 요청을 처음 할 때
                     * 권한의 취득을 요청한다.
                     * 취득하고자 하는 권한을 배열에 넣고 요청한다.
                     * 뒤에 들어가는 파라미터(1000)는 onRequestPermissionsResult() 에서 권한 취득 결과에서 사용된다.
                     * startActiviryForResult의 Request Code와 유사함.
                     */
                    context.requestPermissions(new String[]{permission}, requestCode);
                    return REQUEST_PERMISSION;
                }
 
            } else {
                /*
                 * 이미 권한을 가지고 있을 경우
                 * 해야할 일을 수행한다.
                 */
                return ALREADY_GRANTED;
            }
 
        }
 
        return NOT_SUPPORT_VERSION;
    }
 
    public static class Builder {
 
        private PermissionRequester requester;
 
        public Builder(Activity context) {
            requester = new PermissionRequester(context);
        }
 
        private String title = "권한 요청";
        private String message = "기능의 사용을 위해 권한이 필요합니다.";
        private String positiveButtonName = "네";
        private String negativeButtonName = "아니요";
 
        public String getTitle() {
            return title;
        }
 
        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }
 
        public String getMessage() {
            return message;
        }
 
        public Builder setMessage(String message) {
            this.message = message;
            return this;
        }
 
        public String getPositiveButtonName() {
            return positiveButtonName;
        }
 
        public Builder setPositiveButtonName(String positiveButtonName) {
            this.positiveButtonName = positiveButtonName;
            return this;
        }
 
        public String getNegativeButtonName() {
            return negativeButtonName;
        }
 
        public Builder setNegativeButtonName(String negativeButtonName) {
            this.negativeButtonName = negativeButtonName;
            return this;
        }
 
        public PermissionRequester create() {
            this.requester.setBuilder(this);
            return this.requester;
        }
 
    }
 
    public interface OnClickDenyButtonListener {
 
        public void onClick(Activity activity);
 
    }
 
}
cs


그런 후에 CouponActivity에서 PermissionRequester를 이용해서 권한을 가져오는 로직을 추가해준다


1
2
3
4
5
6
7
8
9
10
11
12
13
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_coupon);
 
        PermissionRequester.Builder request = new PermissionRequester.Builder(this);
        request.create().request(Manifest.permission.INTERNET, 10000new PermissionRequester.OnClickDenyButtonListener() {
            @Override
            public void onClick(Activity activity) {
                Toast.makeText(activity, "인터넷 권한 필요합니다", Toast.LENGTH_SHORT).show();
                activity.finish();
            }
        });
cs


그런 후에 이제 AsyncTask를 이용해서 가져오기 위해서 생성을 해주면 된다
이제 Task 안에서 JSOUP을 사용해본다

Document를 추가해줄 때 JSOUP으로 추가를 해줘야한다




* getLottoNumberTask
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
    private class getLottoNumberTask extends AsyncTask<Void, Void, Map<StringString>> {
        @Override
        protected Map<StringString> doInBackground(Void... params) {
 
            Map<StringString> result = new HashMap<StringString>();
 
            try {
                Document document = Jsoup   .connect("http://www.nlotto.co.kr/common.do?method=main")
                                            .get();
                // 회차 정보 가져오기
                Elements elements = document.select(".lotto_area #lottoDrwNo");
                result.put("latestLotto", elements.text());
 
                // 번호 가져오기
                for ( int i = 1; i < 7; i++ ) {
                    elements = document.select(".lotto_area #numView #drwtNo" + i);
                    result.put("number" + i, elements.attr("src"));
                }
 
                // 보너스 번호 가져오기
                elements = document.select(".lotto_area #numView #bnusNo");
                result.put("number7", elements.attr("src"));
 
                // 1등 당첨자수
                elements = document.select(".lotto_area .winner_num #lottoNo1Su");
                result.put("tvWinGameCount", elements.text());
 
                // 1등 당첨금액
                elements = document.select(".lotto_area .wineer_money #lottoNo1SuAmount");
                result.put("tvWinGameMoney", elements.text());
 
            } catch (IOException e) {
                e.printStackTrace();
            }
 
            return result;
        }
cs


위와 같이 작성하여 각각의 값들을 가져올 수 있다
HTML을 파싱하므로 HTML 태그 명을 이용해서 값을 가져온다

이제는 Task에서 받아온 JSOUP을 onPostExecute에서 처리를 해준다


* onPostExecute
1
2
3
4
5
6
7
8
9
        @Override
        protected void onPostExecute(Map<StringString> map) {
 
            tvLatestLotto.setText(map.get("latestLotto"+ "회 당첨번호");
 
            tvWinGameCount.setText("총 " + map.get("tvWinGameCount"+ " 게임 당첨");
            tvWinGameMoney.setText("1등 " + map.get("tvWinGameMoney"+ "원");
 
        }
cs


이제는 onResume을 통해서 Task를 실행시키면 된다


* OnResume
1
2
3
4
5
6
7
    @Override
    protected void onResume() {
        super.onResume();
 
        getLottoNumberTask task = new getLottoNumberTask();
        task.execute();
    }
cs


우선 번호는 이미지이므로 글자를 파싱해서 보여준다
결과를 확인해 보면 다음과 같다




이미지는 AsyncTask를 이용해서 받아와야 한다


* GetImageTask
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
47
48
49
50
51
52
53
54
55
56
    private class GetIamgeTask extends AsyncTask<String, Void, Bitmap> {
 
        private String numberType;
 
        @Override
        protected Bitmap doInBackground(String... params) {
 
            numberType = params[1];
            
            Bitmap bitmap = null;
 
            try {
                URL url = new URL("http://www.nlotto.co.kr" + params[0]);
 
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setDoInput(true);
                conn.setDoOutput(true);
 
                conn.connect();
 
                bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
 
            return bitmap;
        }
 
        @Override
        protected void onPostExecute(Bitmap bitmap) {
         
            if ( numberType.equals("number1") ) {
                ivNumber1.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number2") ) {
                ivNumber2.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number3") ) {
                ivNumber3.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number4") ) {
                ivNumber4.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number5") ) {
                ivNumber5.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number6") ) {
                ivNumber6.setImageBitmap(bitmap);
            }
            else if ( numberType.equals("number7") ) {
                ivNumber7.setImageBitmap(bitmap);
            }
        }
    }
cs


각각 번호를 가져와서 그림을 표현해주는 역할을 한다
그런 후 결과값을 뿌려주는 onPostExecute에서 그림을 뿌려주는 Task를 실행한다


* onPostExecute - getLottoNumberTask
1
2
3
4
5
6
7
8
9
10
11
12
13
        @Override
        protected void onPostExecute(Map<StringString> map) {
 
            tvLatestLotto.setText(map.get("latestLotto"+ "회 당첨번호");
 
            tvWinGameCount.setText("총 " + map.get("tvWinGameCount"+ " 게임 당첨");
            tvWinGameMoney.setText("1등 " + map.get("tvWinGameMoney"+ "원");
            
            for ( int i = 1; i < 8; i++ ) {
                GetIamgeTask task = new GetIamgeTask();
                task.execute(map.get("number" + i), "number" + i);
            }
        }
cs


크기가 너무 작게 나온다면 activity_coupon.xml에서 ImageView width와 height를 40dp 정도 주면 된다
실행 결과는 다음과 같이 나오게 된다