php2012. 7. 24. 22:09

몇해전에 PHP 를 이용해서 멋진 라이브러리를 만든적이 있습니다.

아직도 애용하고 있죠 ㅎㅎ

웹페이지의 특정 정보를 얻기위해 로긴하고 게시물을 본다거나 글을 올리는 행위(?)를 꼭 브라우저를 쓰지않고 자동으로 동작하게 할수 없을까, 그리고 이걸 스케줄에 넣어서 자동화 할수 없을까 하다가 하나 만들게 된게 Autosurf 라는 php 라이브러리입니다.

당시에 잘 쓰지 않는 php 의 클래스를 써서리 멋지게 만들었지요.

그래서 무단 갈무리 해오기에 무척이나 좋게 되었고, 한때 유행하던 sms 보내는걸 제 홈피에 내가 서비스 하는것처럼 올릴수도 있었죠.

그당시 sms 가 무료라서 상당히 좋았었는데.. 지금은 유료화가 되서...

이걸 만들면서 http 통신 방식에 대해 많은걸 이해하게 되었답니다.


그 다음은 html 파서인데.. 이전에 펄(perl)을 좀 다루면서 펄에 내장 라이브러리로 있던 html 파서가 무척이나 마음에 들었는데.. 같은 방식으로 오히려 더 좋게 php 버전으로 만들었습니다.

html 태그 각 항목을 입맛대로 수정할수 있었지요.

autosurf 로 웹페이지를 가져온다음에 html파서로 정보 변경해서 제 홈피에 올리는 불펌.. 이랄까.. 그외에도 참 많이 애용 되고 있답니다.

Autosurf 소스 받기
autosurf.php 내용 보기
Html Parse 소스 받기


AutoSurf 1.0 Beta1

AutoSurf 는 자동으로 웹서핑을 할수 있는 프로그램입니다.
예들 들어 우리가 웹브라우저를 통해서 웹페이지를 서핑하는걸 프로그램으로
처리하는거지요.
쿠키/섹션을 이용한 인증처리도 통과가 가능합니다.
사용시에는 간단한 스크립터를 사용하면됩니다.

이걸 이용한다면, 문자메세지 전송프로그램도 제작가능하겠지요.
상대서버가 인증을 거치더라도 가능합니다.
특별한 경우를 제외하고는 자동웹서핑이 가능합니다.
특별한 경우라는건, 웹페이지가 자바스크립터를 이용해 쿠키세팅할때와
웹서버인증(입력폼이 뜨는..)에서는 아직 지원안합니다.

동작방식은, GET 과 POST 두개를 이용합니다.

라이브러리 사용법
require "autosurf.php";

$a = new autosurf();
$a->set_key("변수명", "값");
$a->set_key("변수명", "값");
$a->cfg_run("스크립터파일");


스크립터 명령어들..
debug: (yes/no) - http header 송신/수신을 보여줍니다. 가장상단에 한번적으시면 됩니다.
url: (domain) - 접속 주소
retry_num: (숫자) - 재전송 횟수. retry 명령어의 문자열을 만나면 재전송한다. form_action: (path 포함파일명) - 읽을 html 파일 혹은 디렉토리, http url은 제외 (

태그와 같음)
form_method: (get/post) - 메세지 전송양식( 태그와 같음) get 혹은 post
form_input: (key=value) - 파라메터 값전송( 태그화 같음)
show_html: (yes/no) - 전송받은 페이지 출력여부
retry: (문자열) - 결과 페이지 출력에 이 문자열을 만나면 재전송한다.


예제1)
# http://ping.dongman.pe.kr/new/main.php 로 접속한후 메세지를 보여줍니다.

- dongman.cfg 파일
debug: no

url: ping.dongman.pe.kr
form_action: /new/main.php
form_method: get
show_html: yes

- dongman.php 실행 프로그램
require "autosurf.php";

$a = new autosurf();
$a->cfg_run("dongman.cfg");

예제2)
# 라이코스를 통한 문자메세지 전송 스크립터 예제입니다.
아이디와 패스워드는 XXX 로 표현했습니다.
먼저 oneid.lycos.co.kr 로 접속하여 인증을 통과합니다.
debug: no 를 통해 http 헤더값을 출혁하지 않습니다.
각 url 접속시 show_html: yes 값을 주지않으면 페이지내용을 출력하지않습니다.
인증후 mobile-mail.lycos.co.kr 로 접속후 메세지를 전송합니다.
메세지 보낼시 [receiver], [recall1] 등등 괄호가 보이는건 변수입니다.

- lycos.cfg 스크립터 파일
debug: no

url: oneid.lycos.co.kr
form_action: /logon.jsp
form_method: post
form_input: LOGIN=YES
form_input: SOURCE=MMAIL
form_input: ID=XXXXX
form_input: PASSWD=XXXXX

url: mobile-mail.lycos.co.kr
form_action: /Korean/message/send_msg_final.asp
form_method: post
form_input: receiver=[receiver]
form_input: recall1=[recall1]
form_input: recall2=[recall2]
form_input: recall3=[recall3]
form_input: ResOrNot=soon
form_input: content=[content]
form_input: mycount=[mycount]
form_input: current_date=[current_date]
form_input: current_time=[current_time]
show_html: no 


- sms.php 실행 프로그램
require "autosurf.php";

$a = new autosurf();
$a->set_key("receiver", $sms_num1);
$a->set_key("recall1", $num1);
$a->set_key("recall2", $num2);
$a->set_key("recall3", $num3);
$a->set_key("content", $sms_message);
$a->set_key("mycount", strlen($sms_message));
$a->set_key("current_date", date("Ymd"));
$a->set_key("current_time", date("Hi"));
$a->cfg_run("lycos.cfg");

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