2008년 07월 01일
아 졸립고 뻐근하고 멍하고 언츤거같고 손에 체중이 묵었다

# by | 2008/07/01 18:04 | 트랙백 | 덧글(0)

# by | 2008/07/01 18:04 | 트랙백 | 덧글(0)
# by | 2008/06/12 14:50 | 트랙백 | 덧글(0)
Devel::REPL이 perl debugger보다 부족한점을 리스트 해보겠다.
여러 종류의 perl을 위한 REPL 환경을 제공하는 모듈들이 CPAN 에 많이 올라와 있지만 특히 완성도가 좀 있는Perl Console 같은것도 확인 되었지만 현재 Devel::REPL만크 모던 스크립팅 언어 추세를 좀더 이해하고 플러긴 시스템을지원한다는점이있어 선택하게 되었다.
현재 인기상승중인 Moose를 이용해 개발 되었으며 플러긴 개발을 매우 쉽게 해줄수 있는인터페이스를 제공하고 있는 점을 고려 해본다면 아직 미완성인 부분들이 있지만 현존하는 모든 언어들의 REPL중에서 매우 좋은측에 선다고 생각된다.
Devel::REPL을 이용한 간단한 web scraping 예제
보통 우리가 perl script을 작성 하고 테스트 할때 바로바로 실행해서 확인 하는데엔 무리가 없지만 웹에서 컨텐츠를가져오거나 여러 모듈을 실험해 볼때는 이런 방법은 시간을 많이 소요하게된다.
특히 웹에서 컨텐츠를 가져오는 작업은 보통 받아온컨텐츠를 이리저리 굴려 보면서 원하는 값을 긁어 오는 작업에 집중해야 하는데 추출하는 로직을 수정해야 할때 마다 웹에서 컨텐츠를가져와야 하게 된다면 매우 피곤하고 단순하면서 코딩 흐름을 방해하는 부분이 되겠다. 이를 해결해 주는 것이Devel::REPL이 되겠다!!
우선 re.pl (Devel::REPL을 스크립으로 돌릴시 기본적으로 사용되는 이름이 되겠다. 여기서 re.pl이 REPL이라고 지적해주신 yuni님께 감사~) 의 이름을 가진 스크립안에 아래와 같은 코드를 작성에Devel::REPL을 돌리도록 하겠다.
use strict;
use warnings;
use Devel::REPL;
my $repl = Devel::REPL->new;
$repl->load_plugin($_) for qw(
History
DumpHistory
LexEnv
MultiLine::PPI
Commands
Completion
DDS
FancyPrompt
ShowClass
Timing
);
$repl->run;
이 스크립을 perl re.pl로 돌리면 아래와 같은 interactive shell 환경이 실행 될것이다. 여기서 몇개의 주요 플러긴을 간략하게 소개 하도록 하겠다.
re.pl(main):075:0> $track_nodes[0]->findnodes('./tr')->[1]->findvalue('./td')
Took 0.0673990249633789 seconds.
$XML_XPathEngine_Literal1 = 6/6/2008 9:01:22 AMArtist :DJ Fuma buy;
re.pl(main):076:0>
여기서 scraping할 테스트 페이지는 내가 주로 듣는 라디오 playlist를 보여주는 페이지가 되겠는데 여기서 recently played items에 있는 track 정보들을 가져와서 출력해주는 예제가 되겠다.
우선 아래의 코드가 REPL을 통해 구현된 완성된 코드가 되겠다.
#!/usr/bin/perl
use strict;
use warnings;
use WWW::Mechanize;
use HTML::TreeBuilder::XPath;
my $url = "http://www.lounge-radio.com/code/pushed_files/recently.html";
my $mech = WWW::Mechanize->new;
$mech->get($url) or die "eeek no such page to retrieve! $!";
my $tree = HTML::TreeBuilder::XPath->new;
$tree->parse($mech->content) or die "No contents to parse";
$tree->eof;
my @track_nodes = $tree->findnodes('//tbody[@class="table"]');
foreach my $track (@track_nodes) {
print "-"x25,"\n";
foreach my $tr ($track->findnodes('./tr')) {
my $td = $tr->findvalue('./td');
$td =~ /
(
\d+\/\d+\/\d+
\s
\d+:\d+:\d+\s\w{2}
) # match datetime and capture
Artist # match Artist
\s # match whitespace
: # match delimiter
(.*) # match the artist name and capture
\s+ # match any length of whitespace
buy # match buy
/ox
and print "Date: $1\nArtist: $2\n";
$td =~ /Track\s:(.*)/o and print "Track: $1\n";
$td =~ /Album\s:(.*)/o and print "Album: $1\n";
}
print "-"x25,"\n";
}
우선 $tree->eof; 라인까지는 REPL이 필요없어도 쉽게 작성할수 있는 코드가 되어서 복사해서 repl에 붙여 넣으면 되겠다.
중간에 $mech->get($url) or die "eeek no such page to retrieve! $!"; 부분에서 2초 가까이 걸리는것을 알수 있겠다.
re.pl(main):023:0> $mech->get($url) or die "eeek no such page to retrieve! $!";
Took 2.48410296440125 seconds.
$HTTP_Response1 = HTTP::Response=HASH(0x9561674);
REPL의 장점은 우리가 관심있는 데이타를 실제 스크립을 다시 로딩 안해도 지속적인 작업을 받아온 데이타에 할수 있다는 점이 되겠다.
이제 웹에서 컨텐츠를 WWW::Mechanize로 가져온 상태고 이를 HTML::TreeBuilder::XPath로 파싱을 한 상태가 되겠다.
이제 여기서 부터 REPL의 기능을 최대한 누려 최대한 적은 양의 시간과 노력을 들여 개발을 시작해 보도록 하겠다.
우선 우리가 원하는 트랙 정보의 XPATH 값의 '//tbody[@class="table"]' 으로 쉽게 가져올수 있다는것을 확인할수 있겠다.
각 트랙을 담고 있는 테이블을 @track_nodes에 아래의 코드를 이용해 담도록 하겠다.
my @track_nodes = $tree->findnodes('//tbody[@class="table"]');
REPL에서 확인하면 몇개의 노드가 존재 하는지 알수가 있다 하지만 아쉽게도 아직 어떤 값들이 객체에 저장되어 있는가 알수가 없다.
re.pl(main):029:0> my @track_nodes = $tree->findnodes('//tbody[@class="table"]');
Took 0.279267072677612 seconds.
$ARRAY1 = [
HTML::Element=HASH(0x95e0b30),
HTML::Element=HASH(0x95e5964),
HTML::Element=HASH(0x95edfa8),
HTML::Element=HASH(0x95f3b80),
HTML::Element=HASH(0x95fc35c),
HTML::Element=HASH(0x9600fdc),
HTML::Element=HASH(0x96082bc),
HTML::Element=HASH(0x9610c08),
HTML::Element=HASH(0x9614690),
HTML::Element=HASH(0x961ddb4)
];
이 객체들이 어떤 메소드를 제공하는지 symbol table을 검색하여 확인 할수 있으며 객체 종류를 아는 이상 CPAN의
문서를 검색해서 알수 있겠다.
심지어 플러긴을 추가 해서 실시간으로 객체에 대한 문서를 끄집어 낼수도 있는 기능도 차후에나올수도
있겠다. 이런 플러긴 개념으로 인해 다른 REPL에선 상상하기 힘든 부분도 고려하게 되는 점이 매력적이다.
우선 첫번째 트랙에 무엇이있는지 확인해 보도록 하자.
re.pl(main):030:0> $track_nodes[0]
Took 0.00596117973327637 seconds.
$HTML_Element1 = HTML::Element=HASH(0x95e0b30);
이것을 가지고 이제 이리저리 만져 보도록 하겠다. 우선 HTML::Element라는 객체가 첫번째 트랙으로 저장되어 있는걸알수 있지만 HTML::Element은 우리에게 약간 생소한 모듈이기 때문에 어떤 메소들 제공하는지 확인 하도록 하자.
keys %HTML::Element라고 REPL에 치면 아래와 같은 매우 긴 리스트가 나오겠다.
$ARRAY1 = [
'PRUNE_UP',
'__ANON__',
.
.
.
생략
.
'look_down',
'insert_element',
상당히 많은 리스트가 나온다 그래서 기억에 HTML관련 함수 가 있었던 것을 기억하고 아래와 같이 검색 해보았다.
re.pl(main):041:0> grep /html/i, keys %HTML::Element::
Took 0.0062408447265625 seconds.
$ARRAY1 = [
'as_HTML',
'html_uc'
];
as_HTML이란 함수를 발견 했다 이를 이욯해 출력을 해보겠다.
re.pl(main):043:0> $track_nodes[0]->as_HTML
Took 0.0187687873840332 seconds.
<tbody "waking=""Waking" -="-" 6/6/2008="6/6/2008" 9:01:22="9:01:22" @="@" album:="Album:" am.="AM." by="by" class="table" dj="DJ" from="from" fuma="Fuma" fuma's="Fuma's" lounge="Lounge" onmouseout="oMO(this)" onmouseover="oMOv(this)" played="played" title="Fuma's Lounge v3 - " up"="Up"" up""="Up""" v3="v3" waking="Waking"><tr><td width="111"></td><td align="left" rowspan="5" width="0"></td><td align="left" rowspan="5" valign="middle" width="42">
... 생략
매우 지저분한 HTML 코드가 출력이 되었다 어떤 데이타가 들어 있는지 좀더 쉽게 볼수 있도록 해보자메뉴얼을 좀더 확인 해보니 as_HTML에 인자값을 넣어 인덴트를 할수 있다는것을 확인 할수 있었다.그래서 아래와 같이 다시 한번 돌려 보았다.
re.pl(main):070:0> $track_nodes[0]->findnodes('./tr')->[1]->as_HTML(undef, "\t")
Took 0.0138430595397949 seconds.
<tr>
<td align="left" class="artist" rowspan="3" valign="middle">
<div align="center">6/6/2008 9:01:22 AM</div>
</td>
<td align="left" class="artist" rowspan="3" valign="middle">
</td>
<td align="left" class="artist" valign="middle"><img height="12" src="imgs/spacer.gif" width="1" /><b>Artist :</b></td>
<td align="left" class="artist" valign="middle"><img height="12" src="imgs/spacer.gif" width="1" /><b>DJ Fuma</b></td>
<td align="left" class="artist" rowspan="3" valign="middle">
<div align="center"><a "=""" class="style1" dj="DJ" fuma="Fuma" href="Error: Field 'BUYITMS' not found" onclick="return clickreturnvalue()" onmouseover="dropdownmenu(this, event, 'anylinkdj_fuma_-_fumas_lounge_v3_-_waking_up_-_fumas_lounge_v3_-_waking_up.mp3')" target="_blank" title="buy ! Fuma's Lounge v3 - " up"="Up"" von="von" waking="Waking"> buy</a></div>
</td>
</tr>
여기서 확실히 내가 원하는 값인 6/6/2008 9:01:22 AM 그리고 Artist값이 DJ Fuma를
어디서 가져올수 있는지 쉽게 보여 주었다. as_HTML에 이런 기능이 있다는것을
바로바로 알수 있게된 점이 매우 기쁘다.
내가 원하는 값들이 tr태그로 행으로 구분되어 있고 정작 원하는 값들은 td 태그안에 포함되어 있는 것을 확인 하였다.
다시 노드 검색을 ./tr XPATH값을 이용해 가져온 후 이 값을 findvalue를 이용해 안에 있는 텍스트 값을 가져올수 있겠다.
어떤 위치에 내가 원하는 값이 있는지 여러번의 시도가 필요 했는데 곧바로 REPL에 대고 실험 할수 있어서 매우 편했다.
여기서 첫번째 값에서 트랙이 플레이된 시간과 아티스트 이름을 알수 있는데 이를 정규식으로 잡아 보도록 하겠다.
우선 날짜를 아래와 같이 구할수가 있었다.
re.pl(main):060:0> $track_nodes[0]->findnodes('./tr')->[1]->findvalue('./td') =~ /(\d+)\/(\d+)\/(\d+)/
Took 0.0140421390533447 seconds.
$ARRAY1 = [
( 6 ) x 2,
2008
];
그리고 정규식을 조금씩 조금씩 키워 나가 아래와 같이 만들수 있었다.
re.pl(main):061:0>track_nodes[0]->findnodes('./tr')->[1]->findvalue('./td') =~ /(\d+\/\d+\/\d+\s\d+:\d+:\d+\sAM|PM)Artist\s:(.+)/
Took 0.0144000053405762 seconds.
$ARRAY1 = [
'6/6/2008 9:01:22 AM',
'DJ Fuma buy'
];
이를 응용해 내가 원하는 값을 위와 같은 방법으로 가져올수 있었었고 지금까지 내가 사용한 라인들을 모아 아까 완료되었던 코드들을 완성할수 있었다.
여기서 소개된 툴들과 방법론은 현대 스크립팅언어를 대표하는 Python과 Ruby에선 이미 기본적으로 사용되는것이었었다. perl도 기본적으로 제공을 하고 있었지만 많은 인지도를 얻지를 못한듯하다. 허나 Devel::REPL을 이용하여플러긴을 제작할수 있고 CPAN에 잠재워진 수많은 모듈들을 이를 통해 매우 손쉽게 접근할수 있는점을 생각하면 다른 언어에서제공하는 REPL과는 수준이 다르다는 것을 알수가 있겠다.
아직 perl의 언어 특성상 완벽한 REPL은 지원하지 못하지만 (package변수랑 lexical변수가 나주어져 있다던지, 클로져 구분이라던지..) PPI나 Moose를 통한 수준 높은 툴들에 위해 금방 해결 되라라 믿는다.
# by | 2008/06/07 02:10 | 트랙백 | 덧글(0)
에효효;;; 파트2 완성~ 현재 나는 손목 부상에 오십견...
그리고!! YAPC 여러분~! 이 부족한 그림 봐 주셔서 강철 감사!

# by | 2008/06/05 20:05 | 트랙백 | 핑백(1) | 덧글(0)
# by | 2008/06/05 14:16 | 트랙백 | 덧글(0)