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 다오나무