====== Perl과 LWP ======
소개할 내용은 Perl LWP모듈의 소개와 간단한 사용 예제 그리고 그것을 이용해서 만들어본 웹페이지의 링크를 체크하는 스크립트이다. \\
LWP ( Library for WWW in Perl ) 모듈은 웹에 존재하는 데이터에 접근하는 매우 인기있고 안정적인 모듈이다. \\
다른 펄의 모듈과 마찬가지로 LWP모듈 또한 완벽한 문서를 가지고 있으므로 그것을 참고 할 수 있다.( 이것이 최선의 방법이다. ) \\
그러나 LWP모듈은 제법 큰 모듈이고 간단한 기능을 구현하기 위해서 방대한 LWP모듈의 문서를 모두 읽어 볼 수는 없으므로 여기서는 간단한 기능을 소개하고 그 예제를 보겠다.
===== LWP의 소개 =====
* LWP모듈은 웹 클라이언트 개발을 위한 간단하고 안정적인 인터페이스를 제공한다.
* 주된 기능은 웹 클라이언트 작성을 위한 클라스와 함수이지만 보다 범용적인 사용을 위한 기능과 HTTP서버를 위한 기능도 포함하고 있다.
==== LWP 특징 ====
* 다음은 LWP모듈의 주요한 특징이다.
- 재사용가능한 컨포넌트
- 객체지향형의 인터페이스
- HTTP 쿠키 사용 가능
- HTTP 인증 사용 가능
- 프락시 서버 사용 가능
- robots.txt Parser와 정중한 robot을 만들기 위한 기능
* 우리는 Perl와 LWP모듈을 이용해서 웹에서 데이터를 다루는 반복적인 일들을 자동화 할 수 있다.
* 예를 들면 아래와 같은 작업들을 Perl과 LWP모듈을 이용해서 자동화 할 수 있을 것이다.
- 특정한 웹페이지를 가져와 원하는 부분만 파일로 저장하기
- 로그인이 필요한 웹페이지에 접속해서 반복적인 작업 수행
- 큰 파일을 메모리 적재 없이 파일로 저장하기
- 웹페이지의 링크를 돌아다니면서 원하는 정보를 얻기 ( 이것을 robot 이라고 한다 )
- 등등
==== LWP 기본예제 ====
이제 Perl과 LWP모듈의 최신 버젼이 설치 되어있다는 가정하에 URL의 문서를 가져와서 출력하는 예제부터 시작하겠다.
* 다음은 LWP모듈의 버젼을 확인하는 방법을 보인 것이다.
use LWP;
print "This is libwww-perl-$LWP::VERSION\n";
* 현재 LWP모듈의 최신버젼은 5.65이고 지금부터 소개하는 예제는 모두 5.65 버젼에서 테스트 했다.
* LWP의 최신버젼은 CPAM(http://www.cpan.org) 또는 LWP의 홈페이지( http://www.linpro.no/lwp/ )에서 구할 수 있다.
* (Activestate사의 Perl을 설치하였다면 이미 LWP모듈이 포함되어 설치된 상태이다)
* 모듈의 설치와 관련된 문서는 펄마니아( http://www.perlmania.or.kr )에서 펄 모듈 설치에 대한 문서를 참고 하기 바란다.
===== 웹문서 가져오기(LWP::Simple) =====
Perl과 LWP를 사용해서 웹문서를 가져오는 가장 간단한 방법은 LWP::Simple모듈의 get함수를 사용하는 것이다. \\
get함수는 우리가 원하는 URL의 웹문서를 가져오기 위해 LWP모듈의 다소 복잡한 조작을 대신하고 결과적으로 우리에게 간단한 함수의 인터페이스를 제공해준다. \\
get함수가 성공하면 웹문서의 내용을 리턴된고 실패한다면 undef를 리턴한다.
다음 예제를 보자.
use LWP::Simple;
$html = get( "http://www.perlmania.or.kr/~kiseok7/perllwp/camelcode.txt" );
if ( $html ) {
print "$html";
}
첫줄에서 LWP::Simple 모듈의 사용을 명시(''use LWP:Simple'')했고 get 함수를 사용해서 특정한 URL의 내용을 $html 변수에 담았다. \\
그리고 함수에서 리턴된 내용 $html이 있다면 화면에 출력한다.
LWP::Simple은 LWP의 많은 기능중 get, head, getprint, getstore, mirror의 함수를 제공하는 LWP모듈의 간단한 인터페이스이다. \\
LWP::Simple 모듈에서 제공하는 함수를 사용하면 웹문서를 가져오고 저장하는 기본적이지만 강력한 작업을 쉽게 할 수 있다. \\
그리고 그것이 우리가 원하는 거의 대부분의 작업일 것이다.
다음은 LWP::Simple에서 제공하는 함수와 그 기능이다.
get($url)
주어진 URL의 문서를 가져와 내용을 리턴한다.
실패시 undef를 리턴
head($url)
주어진 URL의 문서를 가져와 헤더정보의 리스트를 리턴한다.
($content_type, $document_length, $modified_time, $expires, $server)
실패시 빈 리스트를 리턴
getprint($url)
URL의 문서를 가져와 표준출력을 한다.
HTTP 응답 코드를 리턴
getstore($url, $file)
문서를 가져와 파일로 저장한다.
HTTP 응답 코드를 리턴
mirror($url, $file)
해당 URL문서를 가져와서 변경이 있을때만 파일로 저장한다.
HTTP 응답 코드를 리턴
특정 URL의 웹문서를 자신의 PC(또는 서버)에 파일로 동기화 해서 유지하고 싶다면 다음처럼 짧은 스크립트를 하나 만들고 스케줄러에 등록하면 된다.
use LWP::Simple;
mirror(
"http://www.perlmania.or.kr/~kiseok7/perllwp/camelcode.txt",
"/path/camelcode.txt"
);
.
이처럼 LWP::Simple모듈의 함수들은 대부분의 간단한 작업에 유용하다. \\
하지만 가끔 우리는 좀더 강력하고 깊숙한 LWP모듈의 기능(쿠키사용, HTTP인증 사용, 요청헤더 조작, 응답헤더 분석등)을 사용할 수 있어야 한다. \\
LWP모듈의 다른 기능을 사용하기 위해서 그밖의 모듈(클래스)에 대해서 알아보자.
===== 웹문서 가져오기 (LWP::UserAgent) =====
이번에는 LWP::!UserAgent모듈을 이용해서 웹문서를 가져오는 예제를 설명하겠다. \\
LWP::UserAgent모듈(클래스)을 이용해서 객체를 만들고 웹문서를 가져올 경우 HTTP::Response와 HTTP::Request객체에 대해서도 알고 있어야 한다. \\
일단은 HTTP::Response는 요청 헤더를 만들때 사용하고 HTTP::Request는 응답을 분석할때 사용한다고 알아두고 다음 예제를 보자.
use LWP 5.65;
$browser = LWP::UserAgent->new();
$request = HTTP::Request->new(
'GET',
'http://www.perlmania.or.kr/~kiseok7/perllwp/camelcode.txt'
);
$response = $browser->request($request);
if ($response->is_success) {
print $response->content;
} else {
print $response->error_as_HTML;
}
우선 웹문서를 가져오기 위한 준비로 가상 부라우져 객체 $browser를 만들었다. \\
그 다음에는 요청헤더 HTTP::Resuest객체 $request를 만들어서 웹서버에 요청하고 그 응답을 HTTP::Response객체 $response에 담았다. \\
IF문을 통해서 서버의 응답을 $response->is_success 메서드로 검사하고 성공일때 그 내용을 출력하고 그 이외에는 에러를 HTML형식으로 출력했다.
이것이 LWP::UserAgent모듈을 사용하는 정형적인 예제이다. \\
앞에서 본 LWP::Simple의 get함수와는 다르게 HTTP요청과 HTTP응답을 직접 다루는 것을 볼 수 있다 \\
이것은 간단한 작업에서는 불필요 하겠지만 좀더 강력한 제어를 할 수 있다는 장점이 있다.
그리고 위 예제에서 LWP::UserAgent의 get메서드를 사용하면 다음처럼 좀더 간단하게 재작성할 수도 있다.
use LWP 5.65;
$browser = LWP::UserAgent->new();
## request & response
$response = $browser->get(
'http://www.perlmania.or.kr/~kiseok7/perllwp/camelcode.txt'
);
if ($response->is_success) {
print $response->content;
} else {
print $response->error_as_HTML;
}
위 예제에서는 GET방식의 요청헤더를 만들고 웹서버에 요청하는 과정을 get메서드가 대신하고 있다. \\
그리고 get메서드는 HTTP::Response객체를 리턴한다.
여기서 HTTP::Request와 HTTP::Response모듈을 로드하는 부분이 없는 것은 두 모듈(클라스) 모두 LWP모듈내에서 로드되고 객체로 사용 되어지기 때문이다.
두번제 예제의 get메서드 처럼 LWP::UserAgent 클래스에는 몇가지의 shortcut 메서드가 존재하는데 뒤에서 볼 put같은 메서드도 여기에 포함된다. \\
여러 메서드의 종류와 사용법을 모듈의 문서에서 확인하기 바란다.
다음에는 몇가지의 헤더 조작에 관련된 예제를 보겠다.
===== HTTP 요청 헤더 다루기 =====
지금까지 우리가 만든 웹클라이언트(가상 브라우져)들은 URL의 정보가 가지고 있는 간단한 요청을 서버에 했다. \\
우리가 평소에 사용하는 브라우져들(IE, Netscape)은 요청 헤더에 URL정보뿐만 아니라 몇몇의 정보를 더 가지고 있는데, \\
이번에는 요청헤더에 특별한 헤더 정보를 추가해 보자.
앞의 예제에서 보았던 get메서드에 다음 처럼 헤더 key와 value를 추가 할 수 있다. %headers는 헤더의 key와 value의 리스트이다.
$response = $browser->get( $url, %headers );
다음은 우리가 만든 가상 부라우져가 마치 Netscape 처럼 보이게 하기 위해 헤더를 추가한 예제이다.
%headers = (
'User-Agent' => 'Mozilla/4.76 [en] (Win98; U)',
'Accept' => 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*',
'Accept-Charset' => 'iso-8859-1,*,utf-8',
'Accept-Language' => 'en-US',
);
$response = $browser->get( $url, %headers );
물론 %headers 해쉬를 사용하지 않고 직접 get메서드에 넣어 줄 수도 있다.
$response = $browser->get( $url,
'User-Agent' => 'Mozilla/4.76 [en] (Win98; U)',
'Accept' => 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*',
'Accept-Charset' => 'iso-8859-1,*,utf-8',
'Accept-Language' => 'en-US'
);
그밖에 LWP::UserAgent의 생성자 new메서드에서 헤더를 추가하는 방법도 있고 LWP::UserAgent의 헤더 조작에 관련된 메서드를 사용하는 방법도 있다. \\
모듈의 문서에서 확인해 보길 바란다.
이번에는 GET방식과 POST방식의 폼전송에 대해서 알아보겠다.
===== GET과 POST 방식의 폼전송 =====
Google과 같은 검색엔진에서 검색창에 단어를 치고 엔터를 누르면 URL창에 다음과 같이 나타나는것을 볼 수 있다.
http://www.google.co.kr/search?q=perlmania
폼에 q라는 Key와 perlmania라는 value가 URL에 추가된 형태이다. 다음의 코드를 보자.
use URI;
$url = URI->new('http://google.co.kr/search');
$url->query_form(
'q' => 'perlmania'
);
$response = $browser->get( $url );
GET방식의 요청을 하기 위해서 URI라는 모듈을 사용하여 URL뒤에 폼의 key와 value가 추가된 형태의 요청 헤더를 사용를 만들어서 사용하였다.
POST방식의 요청헤더는 URI같은 별도의 모듈없이 put메서드로 만들 수 있다.
$response = $browser->post( $url,
[
'q' => 'perlmania',
],
);
===== Cookie의 사용 =====
기본적으로 LWP::UserAgent객체는 cookie를 사용하지 않는 것으로 되어 있고 이것을 사용 가능한 상태로 만들어 주기 위해서 cookie_jar의 속성을 설정 해주어야 한다. \\
cookie_jar는 쿠키의 작은 데이터 베이스와 같은 것인데 key와 value로 구성 되어 있다. \\
이것을 메모리에 저장하는가, 파일에 저장하느가에 따라 설정하는 형태가 조금 다르고 메모리에 저장하는 경우 프로그램이 종료되면 그 쿠키 정보는 모두 없어진다. \\
다음처럼 빈 메모리를 cookie_jar에세 줄 수 있다.
$browser->cookie_jar({});
파일에 쿠키의 정보를 기록해 둔다면 프로그램이 종료되어도 쿠키의 정보는 파일에 남아있을 것이다. 다음처럼 cookie_jar를 설정할 수 있다.
$browser->cookie_jar({ file => "$ENV{HOME}/my_cookies.txt" });
===== HTTP인증의 사용 =====
===== Robot의 작성 =====
use LWP::RobotUA;
$browser = LWP::RobotUA->new( 'YourSuperBot/1.34', 'you@yoursite.com' );
$request = HTTP::Request->new('GET', $url);
$response = $browser->request($request);
===== 웹문서의 링크체크 스크립트 =====
use LWP;
$browser = LWP::UserAgent->new();
$request = HTTP::Request->new('GET', 'http://www.perlmania.or.kr/links.html');
$response = $browser->request($request);
if ($response->is_success) {
my $content = $response->content;
while( $content =~ /a href=\s*[\"\'](http:.*?)[\"\']/gi ) {
print "$1\t\t" . getState( $1 ) . "\n";
}
} else {
print $response->error_as_HTML;
}
sub getState {
my $url = shift;
my $ua = LWP::UserAgent->new();
$ua->timeout(5);
my $request = HTTP::Request->new('GET', $url);
my $response = $browser->request($request);
if ( $response->is_success ) {
return "valid";
}
else {
return "not valid";
}
}
웹문서를 LWP모듈을 이용해서 가져오고 정규표현으로 링크 태그를 추출하고 그 링크의 유효여부를 검사하는 스크립트 이다.
위에서 HTML태그를 분석하는 일을 펄의 정규표현이 하고 있다. \\
하지만 단순히 위처럼 검사하는 것은 많은 예외에 완벽하게 대처하지 못할 것이다. \\
LWP::Simple모듈와 HTML태그 파서 HTML::TokeParser를 사용해서 위 코드를 다시 작성해 보았다
use LWP::Simple;
use HTML::TokeParser;
my $url = "http://www.perlmania.or.kr/links.html";
my $tmp = "./mirror.html";
my $rc = mirror( $url, $tmp );
if ( is_success($rc) && -e $tmp ) {
my $p = HTML::TokeParser->new( $tmp );
while (my $token = $p->get_tag("a", "A")) {
my $url = $token->[1]{href} || $token->[1]{href};
if ( $url =~ m!^http://! ) {
print "$url\t" . getState( $url ) . "\n";
}
}
}
else {
die "can't mirror $url"
}
sub getState {
my $url = shift;
my $html = get( $url );
if ( $html ) {
return "valid";
}
else {
return "not valid";
}
}
===== 끝으로 =====