728x90

안드로이드 스튜디오를 통해 앱개발을 하고있는 도중

 

웹크롤링을 해야되가지고 jsoup를 통해 웹크롤링을 시도하였는데 되지가 않았다..

 

이걸로 삽질을 몇시간은 한거같은데 저처럼 삽질할분들을위해 정확하게 해결방법을 알려드리겠습니다.

 

 

1. build.gradle(app)에서 dependencies확인

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'org.jsoup:jsoup:1.11.3'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'

}

 

2. AndroidMainfest.xml 인터넷사용권한 확인, access, read, write, write_external, read_external 권한 추가

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


<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

 

 

AndroidMainfest전체코드 

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


    <application
        android:usesCleartextTraffic="true"<!-- 최신안드로이드들의 경우 https통신이 기본이라 http통신을 가능하게 하려면 따로 설정해줘야함 !-->
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplicationㅁㄴㅇㅇㅁㄴ">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

1번과2번을 잘 해결햇다면

 

jsoup 크롤링코드를 메인activity에서 잘짜면된다

주의할점은

백그라운드에서 실행되어야 된다는점

이거때문에 몇시간동안 삽질한거같다...

 

AsyncTask를 이용하여 백그라운드 처리를 해주면된다.

다른방법으로는 php에서 파싱처리한 결과를 안드로이드에서 JSON으로 가져오는 방법도있다고하지만

여기까지는 저도 아직 부족한거같아서 백그라운처리를통해 크롤링을 배웠습니다.

 

 

MainAcitivity.java

package com.example.myapplication;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;

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

import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {


    Context context;
    TextView textView1, textView2;
    String text1,text2,text3;
    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = MainActivity.this;

        // 네트워크 연결상태 체크
        if (NetworkConnection() == false)
            NotConnected_showAlert();
        checkPermissions();
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
                            android.Manifest.permission.READ_EXTERNAL_STORAGE,
                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
                    }).check();

        } else {
            initView();
        }
    }

    private void initView() {
        textView1 = findViewById(R.id.weather_rs1);
        textView2 = findViewById(R.id.weather_rs2);

        String path1 = "https://weather.naver.com/today/02273610"; //안산 단원구
        String path2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008";
        new getData1().execute(path1);
        new getData2().execute(path2);
    }

    private class getData1 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get();
                Elements elements = document.select(".weather"); // 내용중에서 원하는 부분을 가져온다.
                String[] str = elements.text().split(" ");

                Document document1 = Jsoup.connect(params[0].toString()).get();
                Elements elements1 = document.select(".current"); // 내용중에서 원하는 부분을 가져온다.
                String[] str1 = elements1.text().split(" ");

                Document document2 = Jsoup.connect(params[0].toString()).get();
                Elements elements2 = document.select(".value"); // 내용중에서 원하는 부분을 가져온다.
                String[] str2 = elements2.text().split(" ");


                String text = "\n날씨 : "+str[0] + "\n온도 : " + str1[1] + "\n미세먼지 : " + str2[0];

                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView1.setText("<안산>  "+result);
        }
    }

    private class getData2 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get();
                Elements elements = document.select("em");
                Element targetElement1 = elements.get(1);//1.현시간 2.온도 3.미세먼지
                Element targetElement2 = elements.get(2);//1.현시간 2.온도 3.미세먼지
                Element targetElement3 = elements.get(3);//1.현시간 2.온도 3.미세먼지
                String text1 = targetElement1.text();
                String text2 = targetElement2.text();
                String text3 = targetElement3.text();
                String text = "시간 : "+text1 + " ,날씨 : " + text2 + " ,미세먼지 : " + text3;

                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            //textView2.setText("\n\n\n부산 : "+result);
        }
    }

    private void NotConnected_showAlert() {

        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();

    }

    private boolean NetworkConnection() {
        int[] networkTypes = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if (activeNetwork != null && activeNetwork.getType() == networkType) {
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }
}



실행결과

안드로이드스튜디오 

백그라운드 관련 글 

itmining.tistory.com/5

itmining.tistory.com/6

itmining.tistory.com/7

<코드설명>

Context context; // perrmission을 체크하기 위해 선언
PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

PermissioinListener 체크하기 위해  객체를 만들었으며

Override함수재정의를 통해 권한이 모두 허가되어있지않을경우 토스트를 띄우며

권한이 허용되어있을경우 initView()함수를 실행한다. 

 

private void initView() {
        textView1 = findViewById(R.id.weather_rs1);
        textView2 = findViewById(R.id.weather_rs2);

        String path1 = "https://weather.naver.com/today/02273610"; //안산 단원구
        String path2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008";
        new getData1().execute(path1);
        new getData2().execute(path2);
    }

initView함수의 경우 textView를 Mainactivity의 텍스트뷰를 각각 연결시켜주고

path에(path2는 실질적으로 사용x)에 안산의 네이버날씨 주소를 저장해놓고

getData()함수와 execute함수를 통해 데이터를 알맞게 크롤링한후 setText해준다.

 

public final boolean execute(
    java.lang.String sql,
    int[] columnIndexes)
    
    매개 변수
SQL 문이 포함된 문자열입니다.
자동 생성 키의 열 인덱스를 사용할 수 있도록 해야 하는지 여부를 나타내는 int의 배열입니다.


반환 값
첫 번째 결과가 결과 집합이면 true이고, 그렇지 않으면 false입니다.

 

private class getData1 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get();
                Elements elements = document.select(".weather"); // 내용중에서 원하는 부분을 가져온다.
                String[] str = elements.text().split(" ");

                Document document1 = Jsoup.connect(params[0].toString()).get();
                Elements elements1 = document.select(".current"); // 내용중에서 원하는 부분을 가져온다.
                String[] str1 = elements1.text().split(" ");

                Document document2 = Jsoup.connect(params[0].toString()).get();
                Elements elements2 = document.select(".value"); // 내용중에서 원하는 부분을 가져온다.
                String[] str2 = elements2.text().split(" ");


                String text = "\n날씨 : "+str[0] + "\n온도 : " + str1[1] + "\n미세먼지 : " + str2[0];

                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView1.setText("<안산>  "+result);
        }
    }

 

백그라운드 실행을위해 public class extends AsyncTash<String, void, String> { } 선언을 하였고

 

백그라운드에서 실행되는함수 doIngBackground()함수를 통해 크롤링을 진행해준다.

 

그리고 결과값을 반환해주는(setText 해주는) onPostExecute()함수

 

 

private void NotConnected_showAlert() {

        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();

    }

 

 

 

 

NotConnected_showAlert()함수는 네트워크 연결여부를 체크하는게아니라 

 

if()문을통해 인터넷에 연결되어있지않을경우 

 

"네트워크가 연결이 되어있지않았다고" 해당 문자열을 출력하고 죵료하는 함수

 

private boolean NetworkConnection() {
        int[] networkTypes = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if (activeNetwork != null && activeNetwork.getType() == networkType) {
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }

NetworkConnection()함수는 네트워크 연결여부를 체크하는 함수로

네트워크가 연결되어있을경우 true값을

네트워크에 연결되어있지않을경우 false값을 반환한다.

 

 

private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
                            android.Manifest.permission.READ_EXTERNAL_STORAGE,
                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
                    }).check();

        } else {
            initView();
        }
    }

Build.VERSION.SDK_INT >= 23 안드로이드(6.0)이 Build.VERSION.SDK_INT 23 인가보다...? 쩃든 6.0 이상일경우 

 

TedPermission.with(context)
.setPermissionListener(permissionlistener)
.setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
.setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
.setPermissions(new String[]{
android.Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
}).check();

 

이 긴 문법을통해 접근권한이 필요하다고 띄운후 각각의 권한들을 요구하고 체크한다

만약 false(하나라도 권한이 허용되어있지않을경우 종료됨)

728x90

+ Recent posts