'Hpple'에 해당되는 글 2건

  1. 2012.07.10 html 파싱 with apple 이게 최고다 2
  2. 2012.07.06 [Hpple Parser] HTML Parser - 파싱하기
iOS2012. 7. 10. 18:42

어제 클리앙 모두의 공원 게시판은 NSString 관련 메소드만 가지고 파싱을 해 보았는데요. 코드도 꽤 길어지고 굉장히 무식한 방법이었습니다. 어제 포스팅 마지막에 언급했던대로 좀 더 편한 방법들을 살펴보려고 하는데요. hpple라는 API가 있습니다. 나온지도 오래되었고(업데이트 된지도...), 제가 원래 외부API를 잘 사용안하는데요. 뭐 대체제가 없다보니 써 보겠습니다.


일단 아래 주소에서 받으실 수 있구요.

https://github.com/topfunky/hpple


프로젝트에 6개의 파일을 포함시켜 주시면 됩니다.

TFHpple.h

TFHpple.m

TFHppleElement.h

TFHppleElement.m

XPathQuery.h

XPathQuery.m


그리고 환경설정을 두개 해 주셔야 하는데 PROJECT환경설정에서(Build Settings) All을 누르시면 여러가지 설정들을 찾아보실 수 있는데요. 설정해야될 것은 아래와 같습니다.

Header Search Paths  : ${SDKROOT}/usr/include/libxml2

Other Linker Flags : -lxml2


그리고 마지막으로 html파싱이 이루어지는 프로젝트에 TFHpple.h를 import 시켜주시면 설정은 다 끝났습니다.


프로젝트에 포함시킨 소스파일들을 한번 살펴보면 실제 파싱은 XPathQuery에서 이루어 지고 있음을 확인할 수 있습니다. 다른 소스들도 복잡하지 않으니 한번 살펴보시면 좋을것 같고요.


어제 클리앙 모공을 파싱했던 부분 기억나시나요. 가져온 데이터가 게시물제목, 링크, 아이디, 조회수, 글쓴시간이었는데요. 실제 데이터 파싱하는 부분만 구현 해 보겠습니다.


// 클리앙 모공

NSString * path = @"";

NSError * error;

NSString * stringFromURL = [[NSString allocinitWithContentsOfURL:[NSURLURLWithString:@"http://clien.career.co.kr/cs2/bbs/board.php?bo_table=park"encoding:NSUTF8StringEncodingerror:&error];

    

if(stringFromURL == nil)

{

    NSLog(@"Error reading URL at %@\n%@", path, [error localizedFailureReason]);

}

윗 부분은 어제 했던 부분과 동일하고요.


NSData * data = [stringFromURL dataUsingEncoding:NSUnicodeStringEncoding];

        

// Create parser

TFHpple * xpathParser = [[TFHpple allocinitWithHTMLData:data];

        

// 타이틀 & URL

NSArray * elements = [xpathParser searchWithXPathQuery:@"//tr//td[2]//a"];

for(int i = 0 ; i < [elements count] ; i++)

{

    NSLog(@"%@", [[elements objectAtIndex:i] content]);                             // 타이틀

    NSLog(@"%@", [[[elements objectAtIndex:i] attributes] valueForKey:@"href"]);    // URL

}

타이틀과 URL을 파싱해서 가져오는 방법입니다. 오렌지색으로 하이라이트 된 부분과 일치하는 형태를 찾아서 elements에 배열형태로 넣는 방법인데요. tr태그 내부에 td태그가 2번 나온 뒤 a태그가 나오는 형태를 찾으라는 말인데요.


<tr class="mytr">

<td>10816621</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816621' >[차단공시]삭제요청에 의한 차단공시</a></td>

<td class="post_name"><img src='../data/member/ci/cipher.gif' width='54' height='16' align='absmiddle' border='0'></td>

<td><span title="2012-02-15 17:25:24">17:25</a></td>

<td>1</td>

</tr>

<tr class="mytr">

<td>10816618</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816618' >초고도 근시인데 라섹했습니다</a></td>

<td class="post_name"><img src='../data/member/hi/hikari.gif' width='60' height='16' align='absmiddle' border='0'></td>

<td><span title="2012-02-15 17:25:11">17:25</a></td>

<td>10</td>

</tr>

<tr class="mytr">

<td>10816613</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816613' >암스텔담에선 자전거 뒤에 타려면  정도 운동 신경은 가져야...</a></td>

<td class="post_name"><span class='member'>Guylian</span></td>

<td><span title="2012-02-15 17:25:01">17:25</a></td>

<td>36</td>

</tr>

위의 html 중 색칠해진 부분들이 배열에 들어가게 됩니다. 여기서 한가지 보셔야 할 것은..


content의 경우 해당 태그가 열리고 닫힌 사이의 값, 즉 아래에서 색칠된 부분입니다.

<tr class="mytr">

<td>10816618</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816618' >초고도 근시인데 라섹했습니다</a>

<a 태그가 열리고, </a>로 닫힌 사이의 값입니다.


attribute의 경우 해당 태그 내부에서 key=value 형태로 저장된 값입니다. NSDictionary형의 값이기 때문에 태그의 key가 딕셔너리의 키가 되고 태그의 value가 딕셔너리의 value가 됩니다.

<tr class="mytr">

<td>10816618</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816618' >초고도 근시인데 라섹했습니다</a>

여기에서는 오렌지색이 key가 되고  청록색이 value가 되기 때문에 

[[[elements objectAtIndex:i] attributes] valueForKey:@"href"]


이런식으로 value를 가져올 수 있었습니다.


그럼 아이디를 가져오는 방법을 살펴볼텐데요. 아이디의 경우 두가지 형태가 있기 때문에 조금 복잡합니다. 일단 html 로 아이디가 스트링인 경우와 아이디가 이미지인 경우를 살펴보면요.

<tr class="mytr">

<td>10816618</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816618' >초고도 근시인데 라섹했습니다</a></td>

<td class="post_name"><img src='../data/member/hi/hikari.gif' width='60' height='16' align='absmiddle' border='0'></td>

<td><span title="2012-02-15 17:25:11">17:25</a></td>

<td>10</td>

</tr>


<tr class="mytr">

<td>10816613</td>

<td class="post_subject">&nbsp;&nbsp;<a href='../bbs/board.php?bo_table=park&wr_id=10816613' >암스텔담에선 자전거 뒤에 타려면  정도 운동 신경은 가져야...</a></td>

<td class="post_name"><span class='member'>Guylian</span></td>

<td><span title="2012-02-15 17:25:01">17:25</a></td>

<td>36</td>

</tr>

3번째 td의 다음 element가 달라지는 것을 알 수 있는데요. 몇가지 방법이 떠오르긴 합니다만 직관적으로 스트링을 가져와서 nil이면 이미지로 판단해서 처리하는 방법으로 진행하겠습니다.

// 아이디

elements = [xpathParser searchWithXPathQuery:@"//tr//td[3]"];


for (int i = 0; i < [elements count]; i++) 

{

    if(((NSString *)[[[[elements objectAtIndex:i] childrenobjectAtIndex:0]content]) != nil)

    {

        // 아이디가 스트링이면 스트링 출력

        NSLog(@"%@" , ((NSString *)[[[[elements objectAtIndex:i] childrenobjectAtIndex:0content]) ) ;

        

    }else

    {

        // 아이디가 이미지이면 링크 출력

        NSLog(@"%@", ((NSString *)[[[[[elements objectAtIndex:i] childrenobjectAtIndex:0attributes]valueForKey:@"src" ]));

    }

}

위에서 한 방법과 똑같은 방법이기 때문에 따로 상세설명은 안하겠습니다. 이 코드 위쪽에 첨부된 html부분과 잘 비교해 보세요.


뭐 여기까지 왔다면 글 작성시간과 조회수는 쉽습니다.

// 글작성시간

elements = [xpathParser searchWithXPathQuery:@"//tr//td[4]//span"];

NSLog(@"%@", [[elements objectAtIndex:0content]);

        

// 조회수

elements = [xpathParser searchWithXPathQuery:@"//tr//td[5]"];

NSLog(@"%@", [[elements objectAtIndex:0content]);

이렇게 필요한 정보를 다 가져올 수 있었습니다. 다음번에는 정규식을 이용한 파싱을 해 보겠습니다.


Stack overflow에서 보니 절대 html파싱은 정규식으로 하지 말라는 말이 많이 보입니다. 정규식이 다 좋은데 익숙해지지 않으면 읽기가 쉽지 않기때문에 그런데요. 사실 저도 정규식에는 근본없는 배움인지라...


Posted by 다오나무
Hpple2012. 7. 6. 13:29

(모든 사진들은 클릭하여 원본크기로 보실수 있습니다.)


파싱하려는 사이트는 기상청(http://www.kma.go.kr/index.jsp)입니다.

날씨가 아닌 국외 지진목록(http://www.kma.go.kr/weather/earthquake/internationallist.jsp)을 파싱해보겠습니다.

================================================================================


뷰컨트롤러를 추가합니다.








ParserHppleAppDelegate.h 로 갑니다.



#import "FirstViewController.h"

===============================================================================

만들어준 FirstViewController를 참조합니다. 


ParserHppleAppDelegate.m


FirstViewController *fView = [[FirstViewController allocinit];

[self.window addSubview:fView.view];

================================================================================

FirstViewController클래스의 인스턴스를 가리키는 fView 인스턴스 변수를 만듭니다.

init메서드를 이용해서 객체를 초기화 시킵니다.


FirstViewController.h



#import <UIKit/UIKit.h>
#import "TFHpple.h"

@interface FirstViewController : UIViewController {
    
}

- (void)connectWebsite;

@end

================================================================================

#import "TFHpple.h"

Hpple를 사용하기위해 임포트 시킵니다.


- (void)connectWebsite;

홈페이지와 통신을 위한 메서드를 하나 만들어줍니다.


FirstViewController.m


#import "FirstViewController.h"

@implementation FirstViewController

- (id)init {
    self = [super init];
    if (self != nil) {
        [self connectWebsite];
    }
    return self;
}

- (void)connectWebsite {
    NSLog(@"connectWebsite");
    
    NSString *htmlURL = [NSString stringWithContentsOfURL:
                         [NSURL URLWithString:@"http://www.kma.go.kr/weather/earthquake/internationallist.jsp"encoding:-2147481280 error:nil];
    NSData *htmlData = [htmlURL dataUsingEncoding:NSUnicodeStringEncoding];
    if (htmlData != nil) {
        TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:htmlData];
        NSArray *parserArray  = [xpathParser search:@"//*"];
        for (int i = 0; i < [parserArray count]; i++) {
            TFHppleElement *element = [parserArray objectAtIndex:i];
            NSLog(@"%@", [element content]);
        }
    }
    else {
        UIAlertView *alert = [[UIAlertView allocinitWithTitle:@"연결 실패"
                                                        message:@"데이터를 가져올 수 없습니다."
                                                       delegate:self cancelButtonTitle:@"확인" otherButtonTitles:nil];
        [alert show];
        [alert release];
    }
    NSLog(@"connectWebsiteEnd");
}

===============================================================================

- (id)init {
    self = [super init];
    if (self != nil) {
        [self connectWebsite];
    }
    return self;
}

초기화 시키는 메서드입니다.

self(자신)를 super로 init(초기화) 시킨뒤에 self가 nil이 아닐때, 즉 성공적으로 초기화가 됬을때 connectWebsite를 불러줍니다.

그후 self를 반환합니다.


- (void)connectWebsite {
    NSLog(@"connectWebsite");

성공적으로 connectWebsite가 불려졌다면 로그를 찍어확인합니다.


NSString *htmlURL = [NSString stringWithContentsOfURL:
                         [NSURL URLWithString:@"http://www.kma.go.kr/weather/earthquake/internationallist.jsp"encoding:-2147481280 error:nil];

"http://www.kma.go.kr/weather/earthquake/internationallist.jsp"라는 스트링을 NSURL형식으로 변환해준뒤, 다시 NSString형식의stringWithContentsOfURL메소드로 encoding과 error를 넣어서 변환합니다.


NSData *htmlData = [htmlURL dataUsingEncoding:NSUnicodeStringEncoding];

위에서 만들어준 htmlURL를 dataUsingEncoding:NSUnicodeStringEncoding사용해서 htmlData에 넣어줬습니다.


if (htmlData != nil) {
        TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:htmlData];
        NSArray *parserArray  = [xpathParser search:@"//*"];
        for (int i = 0; i < [parserArray count]; i++) {
            TFHppleElement *element = [parserArray objectAtIndex:i];
            NSLog(@"%@", [element content]);
        }

}

htmlData가 nil이 아닐경우(통신에 성공해서 데이터를 받아왔을경우), 

htmlData를 TFHpple의 initWithHTMLData메서드로 xpathParser에 넣어줍니다.

NSArray를 하나 만들어준뒤에 xpathParser에 들어간 자료로 파싱을 시작합니다.

지금은 @"//*"로 서치를 하기때문에 필요한 자료이외에 가비지들도 보여집니다.


만들어진 NSArray를 for문으로 돌려서 TFHppleElement로 설정해준 엘레멘트를 뽑아냅니다.

이걸 로그로 출력하면 다음과같은 결과를 얻을수 있습니다.



스크롤을 올려보시면 위에값도 확인하실수 있는데요, 스샷에 보이는 부분이 http://www.kma.go.kr/weather/earthquake/internationallist.jsp 페이지의 제일 하단과 같음을 확인하실수 있습니다.


else {
        UIAlertView *alert = [[UIAlertView allocinitWithTitle:@"연결 실패"
                                                        message:@"데이터를 가져올 수 없습니다."
                                                       delegate:self cancelButtonTitle:@"확인" otherButtonTitles:nil];
        [alert show];
        [alert release];

}

통신이 실패했을경우 UIAlertView를 띄워 유저가 알수있게 합니다.


NSLog(@"connectWebsiteEnd");

NSLog로 메소드의 끝을 확인합니다.


===============================================================================

이제 해당 페이지소스를 보면서 NSArray *parserArray  = [xpathParser search:@"//*"];에 search:@"//*" 부분을 손봐주면 필요한 자료만 뽑을수 있게 됩니다.


2장끗...

Posted by 다오나무