2014년 10월 8일 수요일

디아블로 전정실 안드로이드앱으로 html 파싱해서 보자(StringTokenizer 이용)



처음 파싱해야 하는 내용은 영웅들의 목록(영웅들의 레벨과 type(hard core,normal 등등)이다.

파싱하는 방법은 jericho 라이브러리가 있긴 한데.... 공부도 할겸 여기에서는 간단하게 만들도록 하겠다.

html 읽어오기

html을 읽어 오기 위해서는 소켓을 열고 connection을 한 뒤 http header에 데이터를 보내서 읽어 오면 되겠지만 이미 java에는 html connection 관련 라이브러리(HttpURLConnection)가 준비되어 있다.
따라서 검색해보니 예제들을 참고 해서 구현해 보았다.
( http://markan82.tistory.com/32 )

GetHttp.java


public class GetHttp {
 static final int ERROR_CODE_HTTP_NOT_FOUND = -404;
 static final int ERROR_CODE_HTTP_UNAUTHORIZED = -401;
 static final int ERROR_CODE_HTTP_ELSE = -1;
 static final int ERROR_CODE_HTTP_EXCEPTION = -1000;
 static final int ERROR_CODE_NOERROR = 0;
 StringBuilder html = null;
 int errorCode = 0;
 public String get() {
  return html.toString();  
 }
 public int getErrorCode() {
  return errorCode;
 }
 public boolean execute(String addr) {
  return execute(addr, 5, 10);
 }
 public boolean execute(String addr, int connTimeOutSec, int readTimeOutSec) {
  html = new StringBuilder();
  try {
   //인터넷상의 자원이나 서비스 주소값을 URL 객체로 생성합니다.
   URL url = new URL(addr);
   
   //접속에 성공하면 양방향 통신이 가능한 연결 객체(HttpURLConnection)가 리턴됩니다.
   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
   if( conn != null ) {
    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
    //연결 제한 시간을 1/1000 초 단위로 지정합니다. 
    //0이면 무한 대기입니다.
    conn.setConnectTimeout(1000*connTimeOutSec);
    
    //읽기 제한 시간을 지정합니다. 0이면 무한 대기합니다.
    conn.setReadTimeout(1000*readTimeOutSec);
    
    //캐쉬 사용여부를 지정합니다.
    conn.setUseCaches(false);
    
    //http 연결의 경우 요청방식을 지정할수 있습니다. 
    //지정하지 않으면 디폴트인 GET 방식이 적용됩니다.
    //conn.setRequestMethod("GET" | "POST");
    
    //서버에 요청을 보내가 응답 결과를 받아옵니다.
    int resCode = conn.getResponseCode();
    
    //요청이 정상적으로 전달되엇으면 HTTP_OK(200)이 리턴됩니다.
    //URL이 발견되지 않으면 HTTP_NOT_FOUND(404)가 리턴됩니다.
    //인증에 실패하면 HTTP_UNAUTHORIZED(401)가 리턴됩니다.
    if( resCode == HttpURLConnection.HTTP_OK ) {
     
     //요청에 성공했으면 getInputStream 메서드로 입력 스트림을 얻어 서버로부터 전송된 결과를 읽습니다.
     InputStreamReader isr = new InputStreamReader(conn.getInputStream(),"utf-8");
     
     //스트림을 직접읽으면 느리고 비효율 적이므로 버퍼를 지원하는 BufferedReader 객체를 사용합니다.
     BufferedReader br = new BufferedReader(isr);
     for(;;) {
      String line  = br.readLine();
      if( line == null ) break;
      html.append(line + "\n");
     }
     br.close();
    } else if( resCode == HttpURLConnection.HTTP_NOT_FOUND ) {
     errorCode = ERROR_CODE_HTTP_NOT_FOUND;
     return false;
    } else if( resCode == HttpURLConnection.HTTP_UNAUTHORIZED ) {
     errorCode = ERROR_CODE_HTTP_UNAUTHORIZED;
     return false;
    } else {
     errorCode = ERROR_CODE_HTTP_ELSE;
     return false;
    }
    conn.disconnect();
   } else {
    errorCode = ERROR_CODE_HTTP_ELSE;
    return false;
   }
  } catch(Exception e) {
   errorCode = ERROR_CODE_HTTP_EXCEPTION;
   return false;
  }
  errorCode = ERROR_CODE_NOERROR;
  return true;
 } 
}

위 클래스를 테스트 해볼 수 있는 테스트 코드이다.


GetHttp html = new GetHttp();
html.execute("http://kr.battle.net/d3/ko/profile/donarts-3994/career");
System.out.println( "bText = " + html.get());
Util.FileWrite("D:\\test.html",html.get());


저장된 html 파일을 열어서 어떤걸 파싱 해야 하는지 저장하고 html 내용을 봤다. (크롬이나,ie를 이용해서 html을 저장해도 되고 위에서 사용한 예제를 이용한 파일을 봐도 동일하다.)

html 중간쯤 보면 아래와 같은 부분이 나온다.



html 내에 javascript를 처리하는 부분인데 정리가 아주 잘 되어있다.
해당 정보를 이용해서 추출하는 코드를 만들어보자.
StringTokenizer class를 이용하였다. 해당 class는 문자열에서 token 단위로 건너뛰면서 문자열을 검사 할 수 있는 함수이다. 마지막임의 판단을 '>' 문자열로 하였다. 그 이유는 토큰이 안되는 문자를 미리 정의할 수 있는데(" \t\r\n,':=[{}];/()":이 문자로 정의함)'>'의 경우를 정의하지 않아서 html에서 토큰으로 인식하게 된다. 


String data = Util.FileRead("D:\\test.html");
//기본적으로 공백 문자가 구획문자로 사용됩니다 
//(space character , tab character  ,newline character , carriage-return, form-feed)
StringTokenizer st = new StringTokenizer(data," \t\r\n,':=[{}];/()");
while(st.hasMoreTokens()){
 String temp = st.nextToken();
 //System.out.println(" " +temp+"\n");
 if(temp.equals("Profile.heroes")){
  while(st.hasMoreTokens()){
   //{ id: 30559319, name: 'donartsA', level: 70, 'class': 'barbarian', historyType: 'hero' },
   temp = st.nextToken(); // id
   if( temp.equals(">") ) break;
   System.out.println(" " +st.nextToken());
   st.nextToken();// name
   System.out.println(" " +st.nextToken());
   st.nextToken();// level
   System.out.println(" " +st.nextToken());
   st.nextToken();// class
   System.out.println(" " +st.nextToken());
   st.nextToken();// historyType
   System.out.println(" " +st.nextToken());
  }
 }
}


여기에서 FileRead 함수는 아래와 같이 구현하였다.

public static String FileRead(String path)
{
 String lines = null;
 try {
  ////////////////////////////////////////////////////////////////
  BufferedReader in = new BufferedReader(new FileReader(path));
  String line =null;
  while((line = in.readLine()) != null){
   lines += (line + "\n"); 
  }
  ////////////////////////////////////////////////////////////////
 } catch (IOException e) {
  return null;
 }
 return lines;
}


위 함수의 결과는 아래와 같다.


 30559319
 donartsA
 70
 barbarian
 hero
 30559318
 donartsB
 70
 witch-doctor
 hero
 30559317
 donartsC
 70
 demon-hunter
 hero
 33559916
 donarts
 70
이하 생략...







댓글 없음:

댓글 쓰기