原文網址:http://hi.baidu.com/0soul/blog/item/91098701f5051880e850cd4b.html
原文:http://www.perl.com/pub/a/2002/08/20/perlandlwp.html
著作權資訊有點多,其它的我寫在最後。
我覺得這個Perl的LWP文檔挺不錯的,所以就轉過來,方便隨時查閱,呵呵。
下面是轉載內容:
************************************************************************************The Fuck Cutline************************************************************************************
LWP 與 WEB 的基本使用簡介
LWP (“Library for WWW in Perl” 的縮寫) 是一個由多個模組組成,用來擷取網路資料的的模組組。 和很多 Perl 的模組一樣。每一個 LWP 模組都內建詳細的文檔,做為對這個模組的完整介紹。可是面對 LWP 裡的眾多模組,有時候即使是完成最簡單的工作,新手們也常常不知道從那裡開始。要對 LWP 做全面的介紹需要一整本書,很幸運,Perl & LWP 已經出版。而這篇文章向你介紹了最常見的 LWP 用法。
使用 LWP::Simple 擷取網頁
如果只是要拿到某個網頁,那使用 LWP::Simple 裡的函數是最簡單的。通過調用 get($url) 函數,就可以得到相關網址的內容。如果沒有發生錯誤,get 函數返回此網頁,否則,返回 undef。例子如下:
my $url = 'http://freshair.npr.org/dayFA.cfm?todayDate=current'
use LWP::Simple;
my $content = get $url;
die "Couldn't get $url" unless defined $content;
# $content 裡是網頁內容,下面是對此內容作些分析:
if($content =~ m/jazz/i) {
print "They're talking about jazz today on Fresh Air!n";
} else {
print "Fresh Air is apparently jazzless today.n";
}
如果要在命令列裡運行,getprint 函數非常方便。如果沒有發生錯誤,它會把網頁內容輸出到 STDOUT,否則將會有錯誤資訊輸出到 STDERR。 例如:
% perl -MLWP::Simple -e "getprint 'http://cpan.org/RECENT'"
上面的網址指向一個文字檔,列有最近兩個星期內 CPAN 更新過的檔案。如果想要知道 Acme:: 的模組是否有更新,有就email 自己,你可以把它和 Shell 結合到一起來實現。如下:
% perl -MLWP::Simple -e "getprint 'http://cpan.org/RECENT'"
| grep "/by-module/Acme" | mail -s "New Acme modules! Joy!" $USER
LWP::Simple 還有一些非常有用的函數,包括一個運行HEAD請求的函數,用來檢查連結是否有效,網頁是否更新。另外兩個用來儲存和鏡像網址的函數也值得一提。具體請看 LWP::Simple 的文檔或 Perl & LWP 的第二章.
LWP Class 模型基礎
LWP::Simple 在做簡單的工作時很方便。但因為不支援 cookies,使用者認證,對 HTTP request header (請求標題)的編輯,和 HTTP resonse header(響應標題)的讀寫(主要是 HTTP 的 error message)。因此,當需要這些特性的時候,就要使用 LWP Class 模型。在眾多的 LWP Class 裡,LWP::UserAgent 和 HTTP::Response 是必須理解的。LWP::UserAgent 就像一個虛擬瀏覽器用來作 request (請求)。HTTP::Response 用來儲存 request (請求) 產生的 response(響應)。
最基本的用法是 $response = $browser->get($url), 或寫的更完整些 :
# 程式開始:
use LWP 5.64; # 載入較新版本的 LWP classes
my $browser = LWP::UserAgent->new;
...
# get request:
my $url = 'http://freshair.npr.org/dayFA.cfm?todayDate=current';
my $response = $browser->get( $url );
die "Can't get $url -- ", $response->status_line
unless $response->is_success;
die "Hey, 我想要 HTML 格式而不是 ", $response->content_type
unless $response->content_type eq 'text/html';
# 或者任何其他的 content-type
# 成功的話就對內容處理
if($response->content =~ m/jazz/i) {
print "Fresh Air 今天在討論爵士!n";
} else {
print "Fresh Air 今天討論的和爵士一點邊都不沾.n";
}
上面有兩個相關的 object: $browser,是 LWP::UserAgent 的一個object。$response 是屬於 HTTP::Response 類的一個object。一個程式裡只需要一個 $browser object,但是每次發出一個 request,就會得到一個新的 HTTP::Response object。HTTP::Response object 有以下一些有價值的屬性:
- 一個 status code(狀態碼值),表示成功或失敗。你可以使用 $response->is_success 來檢測它。
- http status line(http 狀態原因),觀察 $response->status_line 的結果 ( 比如 “404 Not Found” ) 會協助你理解這個詞的意思。
- MIME content-type(檔案類型)通過 $response->content_type 來獲得。例如 “text/html”,”image/gif”,”application/xml” 等等。
- content of the response(響應返回的內容)儲存在 $response->content。內容可能是 html 格式。如果是 GIF 格式,$response->content 裡是二進位的 GIF 資料。
- 許多其他 methods 都可以在 HTTP::Response 和它的 superclasses (父 class) HTTP::Message 和 HTTP::Headers 裡找到。
添加其他 HTTP 要求 headers
request (請求) 常用的方法是 $response = $browser->get($url),但如果需要,你可以在 $url 後跟一個索引值的列表來給你的 request 加上其他 HTTP headers。象這樣 :
$response = $browser->get( $url, $key1, $value1, $key2, $value2, ... );
舉個例子,如果你要對一個只允許 Netscape 瀏覽器連入的網站發出請求,那就需要發出類似 Netscape 的 header,如下:
my @ns_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, @ns_headers);
如果不打算重複使用這個 array,你可以把它寫到 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',
);
如果只是打算修改 User-Agent,可以通過 LWP::UserAgent 的 agent 方法把預設的agent ‘libwww-perl/5.65’(或者別的)改掉。
$browser->agent('Mozilla/4.76 [en] (Win98; U)');
使用 cookies
預設的LWP::UserAgent對象像一個不支援cookies的瀏覽器一樣工作。有不只一種的辦法可以設定它的cookie_jar屬性,從 而讓它 支援cookies。“cookie jar” 是一個用來儲存 HTTP cookie 的容器。你可以把它存到硬碟(像Netscape使用cookies.txt一樣)或記憶體裡。存到記憶體裡的 cookies 會在程式完成後消失。記憶體式的 cookie 使用方法:
$browser->cookie_jar({});
也可以把 cookie 儲存到硬碟的檔案裡:
use HTTP::Cookies;
$browser->cookie_jar( HTTP::Cookies->new(
'file' => '/some/where/cookies.lwp',
# 儲存 cookies 的地址
'autosave' => 1,
# 當完成後自動儲存到硬碟裡
));
檔案裡的 cookie 是以 LWP 自定的格式儲存,如果你想在 netscape 裡使用這個 cookie 檔案,可以使用 HTTP::Cookies::Netscape class :
use HTTP::Cookies;
# yes, loads HTTP::Cookies::Netscape too
$browser->cookie_jar( HTTP::Cookies::Netscape->new(
'file' => 'c:/Program Files/Netscape/Users/DIR-NAME-HERE/cookies.txt',
# where to read cookies
));
你也可以象上面一樣使用 ‘autosave’ => 1 。 但 Netscape 的 cookie 有時會在寫入硬碟之前就被丟掉,至少在寫這篇文章的時候還是這樣。
通過 POST提交表格
大部分HTML表格使用HTML POST 向伺服器提交資料,在這裡你可以這樣:
$response = $browser->post( $url,
[
formkey1 => value1,
formkey2 => value2,
...
],
);
或者你也可以把 HTTP header 也一起發出
$response = $browser->post( $url,
[
formkey1 => value1,
formkey2 => value2,
...
],
headerkey1 => value1,
headerkey2 => value2,
);
下一個例子向 AltaVista 的搜尋引擎發送 HTTP POST 請求,然後從HTML裡提取出符合匹配的總數。
use strict;
use warnings;
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $word = 'tarragon';
my $url = 'http://www.altavista.com/sites/search/web';
my $response = $browser->post( $url,
[ 'q' => $word, # the Altavista query string
'pg' => 'q', 'avkw' => 'tgz', 'kl' => 'XX',
]
);
die "$url error: ", $response->status_line
unless $response->is_success;
die "Weird content type at $url -- ", $response->content_type
unless $response->content_type eq 'text/html';
if( $response->content =~ m{AltaVista found ([0-9,]+) results} ) {
# 從 "AltaVista found 2,345 results" 裡匹配出結果
print "$word: $1n";
} else {
print "Couldn't find the match-string in the responsen";
}
通過 GET 提交表格
一些HTML表格不使用 POST 請求,而是使用 GET 請求來傳輸資料。例如,在 imdb.com 裡檢索電影名字 ‘Blade Runner’, 提交後在瀏覽器的網址欄裡將顯示 :
http://us.imdb.com/Tsearch?title=Blade%20Runner&restrict=Movies+and+TV
下面是使用 LWP 實現同樣的結果 :
use URI;
my $url = URI->new( 'http://us.imdb.com/Tsearch' );
# makes an object representing the URL
$url->query_form( # And here the form data pairs:
'title' => 'Blade Runner',
'restrict' => 'Movies and TV',
);
my $response = $browser->get($url);
第5章詳細描述了 HTML 表格和表格式資料,第6章到第9章描述了怎樣從獲得的HTML資料裡提取出有用的資訊。
URL 處理
上面提到的 URI class 提供很多擷取和修改 URL 的方法。例如 如果想要知道 url 是什麼類型 (http, ftp 等等) 可以使用 $url->schema 來得到,如果要提取網址裡的主機名稱,可以使用 $url->host。不過,可能最有用的是我前面提到的 query_form 方法,以及把相對網址路徑(如”../foo.html”)轉換成絕對路徑(如”http: //www.perl.com/stuff/foo.html”)的 new_abs 方法。例子如下:
use URI;
$abs = URI->new_abs($maybe_relative, $base);
現在回憶一下擷取最新 CPAN 模組的那個例子。
use strict;
use warnings;
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $url = 'http://www.cpan.org/RECENT.html';
my $response = $browser->get($url);
die "Can't get $url -- ", $response->status_line
unless $response->is_success;
my $html = $response->content;
while( $html =~ m/chunk86920392chunklt;A href=\"(.*?)\"/g ) {
print "$1n";
}
輸出的結果是
MIRRORING.FROM
RECENT
RECENT.html
authors/00whois.html
authors/01mailrc.txt.gz
authors/id/A/AA/AASSAD/CHECKSUMS
...
你可以使用 URI 模組的 new_abs 方法來得到完全網址路徑,修改 while 迴圈:
while( $html =~ m/<A href=\"(.*?)\"/g ) {
print URI->new_abs( $1, $response->base ) ,"n";
}
$response->base 方法可以在 HTTP::Message 裡找到。它返回的 URL 通常被用來和相對路徑合并來得到完全路徑。現在得到的結果是
http://www.cpan.org/MIRRORING.FROM
http://www.cpan.org/RECENT
http://www.cpan.org/RECENT.html
http://www.cpan.org/authors/00whois.html
http://www.cpan.org/authors/01mailrc.txt.gz
http://www.cpan.org/authors/id/A/AA/AASSAD/CHECKSUMS
...
請參考 Perl & LWP 的第四章,以得到對 URI objects 更詳細的描述。當然,使用 regexp (Regex) 來匹配 url 相對簡單,如果情況複雜,需要更強大的匹配工具,可以考慮 HTML 分析模組 HTML::LinkExtor 或 HTML::TokeParser,甚至 HTML::TreeBuilder
其他瀏覽器屬性
LWP::UserAgent objects 有幾個值得注意的屬性 :
詳細請參見 LWP::UserAgent 文檔.
寫一個有禮貌的機器人
如果想遵循 robots.txt 和避免在較短的時間發出太多的請求,你可以採用 LWP::RobotUA 而不是 LWP::UserAgent。LWP::RobotUA 用法與 LWP::UserAgent 一樣:
use LWP::RobotUA;
my $browser = LWP::RobotUA->new(
'YourSuperBot/1.34', 'you@yoursite.com');
# 機器人名字和 email 地址
my $response = $browser->get($url);
HTTP::RobotUA 多了幾個特性:
- 如果 $url 請求的伺服器的 robots.txt 禁止了你對 $url的訪問,那麼 $browser 就不會發出對於這個地址的請求,而是返回 403 代碼和一個錯誤資訊 “Forbidden by robots.txt”。
die "$url -- ", $response->status_line, "nAborted"
unless $response->is_success;
然後你會得到這樣的錯誤資訊:
http://whatever.site.int/pith/x.html -- 403 Forbidden
by robots.txt
Aborted at whateverprogram.pl line 1234
- 如果 $browser 發現請求的地址是剛剛請求過的,就會暫停 (sleep) 來避免發送太多的請求。預設暫停 1 分鐘,但可以通過 $browser->delay( minutes ) 來設定。比如:
$browser->delay( 7/60 );
詳細請參見 LWP::RobotUA文檔.
使用代理
有時你希望(或者是必須)通過代理來串連某些網站或協議,就比如你的LWP程式是運行在某台處於防火牆之內的機器上。代理通常儲存在環境變數 HTTP_PROXY 裡。LWP 可以通過 user-agent object 裡的 env_proxy 函數把環境變數裡的Proxy 位址裝載。
use LWP::UserAgent;
my $browser = LWP::UserAgent->new;
# And before you go making any requests:
$browser->env_proxy;
詳細請參照 LWP::UserAgent 文檔裡的 proxy, env_proxy 和 no_proxy 方法.
HTTP 認證
許多網站都是通過 HTTP 認證來限制串連. 當使用者請求一個限制頁面時, HTTP 伺服器回複 “That document is part of a protected ‘realm’ and you can access it only if you re-request it and add some special authorization headers to your request”. ( 你現在請求了一個限制地區 , 如果你需要重新發送一個帶有認證資訊的 header 才可以連入. )Unicode.org 的管理員為了防止機器人訪問郵件組擷取發信人地址,要求先進行 HTTP 認證。使用者名稱和密碼是公開的:使用者名稱: unicode-ml 密碼: unicode
假設一個限制頁面的地址是
http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html
如果你在瀏覽器裡請求這個地址,一個新的視窗跳出,顯示 “Enter username and password for ‘Unicode-MailList-Archives’ at server ‘www.unicode.org’”。請你輸入使用者名稱和密碼,就像這樣:
單純使用 LWP 請求這個網址 :
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $url =
'http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html';
my $response = $browser->get($url);
die "Error: ", $response->header('WWW-Authenticate') ||
'Error accessing',
# ('WWW-Authenticate' is the realm-name)
"n ", $response->status_line, "n at $urln Aborting"
unless $response->is_success;
你將得到以下錯誤:
Error: Basic realm="Unicode-MailList-Archives"
401 Authorization Required
at http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html
Aborting at auth1.pl line 9. [or wherever]
這是因為 LWP 不知道 host www.unicode.org 裡的 “Unicode-MailList-Archives” 區的使用者名稱和地址。解決這個問題最簡單的方法是使用 credentials 方法來提供使用者名稱和密碼:
$browser->credentials(
'servername:portnumber',
'realm-name',
'username' => 'password'
);
通常, 連接埠是 80. credentials 函數要在發請求之前調用.比如:
$browser->credentials(
'reports.mybazouki.com:80',
'web_server_usage_reports',
'plinky' => 'banjo123'
);
我們的 unicode.org 的例子可以寫成
$browser->credentials( # add this to our $browser 's "key ring"
'www.unicode.org:80',
'Unicode-MailList-Archives',
'unicode-ml' => 'unicode'
);
串連 HTTPs URLs
只要有安裝 LWP 的HTTPs 的支援, 訪問 HTTPs URLs 的方法和 HTTP 一樣
use LWP 5.64;
my $url = 'https://www.paypal.com/'; # Yes, HTTPS!
my $browser = LWP::UserAgent->new;
my $response = $browser->get($url);
die "Error at $urln ", $response->status_line, "n Aborting"
unless $response->is_success;
print "Whee, it worked! I got that ",
$response->content_type, " document!n";
如果沒有 HTTPs 的支援, 如下錯誤資訊會顯示
Error at https://www.paypal.com/
501 Protocol scheme 'https' is not supported
Aborting at paypal.pl line 7. [or whatever program and line]
如果你安裝了 LWP 的 HTTPS 支援的話,你的請求應該是成功的,你可以像對待普通的 HTTP 要求一樣處理 $response 對象。關於安裝 HTTPS 支援的資訊可以在libwww-perl 裡的README.SSL 檔案找到.
擷取大檔案
請求較大的檔案時, 普通的要求方法 ( 例如 $response = $browser->get($url) ) 會給你帶來記憶體問題. 因為 $response 儲存著整個檔案. 如果請求了一個 30MB 的檔案, 那可能不是什麼明智的做法.一個解決方案是把檔案存到硬碟
$response = $ua->get($url,
':content_file' => $filespec,
);
比如:
$response = $ua->get('http://search.cpan.org/',
':content_file' => '/tmp/sco.html'
);
當使用 content_file 時, headers 還是在$response, 但$response->content 是空.值得注意的是LWP 5.66 之前的版本不支援content_file . 你應該使用 use LWP 5.66; 如果你的程式可能運行在低版本的 LWP 上, 你也可以使用下面的例子來保證相容性, 這與 content_file 有同樣效果.
use HTTP::Request::Common;
$response = $ua->request( GET($url), $filespec );
資源
以上只是對 LWP 常用函數的簡介, 如果想瞭解更多關於 LWP 和 LWP 相關的東西, 請參閱以下文檔
- LWP::Simple: 提供簡易的 get, head, mirror 方法.
- LWP: libwww-perl 模組的綜述
- HTTP::Response: 發出 LWP 請求後所得到的響應 , $response = $browser->get(…).
- HTTP::Message and HTTP::Headers: HTTP::Response 很多方法都是來自兩者.
- URI: Class 處理完全和相對網址路徑.
- URI::Escape: 來正確處理和轉換 URL 裡的不規則字元 (比如 “this & that” 和 “this%20%26%20that” 之間的轉換).
- HTML::Entities: 來正確處理和轉換 HTML 裡的不規則字元 (比如 “C. & E. Bront??” 和 “C. & E. Bront??” 之間的轉換).
- HTML::TokeParser and HTML::TreeBuilder: Classes 分析 HTML
- HTML::LinkExtor: Class :在 HTML 找到連結
- 當然還有我的 Perl & LWP.
與 Sean Burke 討論後的一些筆記
在翻譯這篇文章的時候,我聯絡到了文章作者, Sean Burke. 他同意把原文章裡需要補充和更新的地方指出. 我與作者也通過 MSN 交流了一下. 這裡我把自己對 LWP 的理解和作者的補充寫在這裡.
- 在文章裡提到了使用
use LWP::ConnCache;
$browser->conn_cache(LWP::ConnCache->new()):
這告訴 browser object 使用 HTTP/1.1 “keep-Alive” 特性, 即重複使用先前的 socket 來加快請求速度.你也可以在 new LWP::UserAgent 時為 $browser 加上 “keep-Alive” 特性, 如下
use LWP;
$browser = new LWP::UserAgent(keep_alive => 1);
- 不要忘記 response object 的 header 通常有很多非常值得注意的資訊, 你可以通過 headers_as_string 和 as_string 函數來得到. 下面是使用 headers_as_string 返回的例子
use LWP;
my $br = LWP::UserAgent->new;
my $resp = $br ->get('http://www.pulse24.com');
print $resp->headers_as_string";
輸出結果:
Cache-Control: private, max-age=0
Connection: close
Date: Sun, 16 Jan 2005 04:18:26 GMT
Server: Microsoft-IIS/6.0
Content-Length: 432
Content-Type: text/html
Content-Type: text/html; charset=iso8859-1
Client-Date: Sun, 16 Jan 2005 04:18:09 GMT
Client-Peer: 207.61.136.40:80
Client-Response-Num: 1
REFRESH: 0;URL=http://www.pulse24.com/Front_Page/page.asp
X-Meta-Robots: noindex
X-Powered-By: ASP.NET
你也可以通過 $response->header(’field’) 來獲得想要的特別 header. 象上面的例子, 如果要訪問的網頁使用了 meta refresh :
<META HTTP-EQUIV="REFRESH" CONTENT="0;URL=http://www.pulse24.com/Front_Page/page.asp">
你可以使用 $response->header(’refresh’) 來拿到 refresh 的 url 地址, 選擇是否繼續跟進.
- 有 些時候, 瀏覽器可以正常訪問到的地址, LWP 卻不行. 一般是因為你的 LWP 的 header, referer , cookie 或 user-agent 等的設定與對方網路伺服器允許連入的不同. 為了找到問題所在, 你需要比較瀏覽器發出的請求和你的 LWP 發出的請求有何不同, 然後修改再嘗試. 很多時候這是反反覆複的工作. 我最早使用 Ethereal 來監視,抓取資料, 目前使用 Firefox 的 LiveHTTPHeaders 外掛程式. 現在LWP 也內建一個資料分析模組 LWP::DebugFile 來協助你找到問題.
- 另外, 文章裡提到了 HTTP::Cookies::Netscape , 現在LWP Cookies 模組支援更多瀏覽器 Mozilla , Safari , Omniweb
- 很多時候表格與 javascript 一起使用, LWP 沒有分析 Javascript 的引擎,所以你必須分析網頁源碼裡 Javascript 來決定怎樣處理.
function Submit()
{
.........
self.document.location.href="verify.php";
return false;
}
........
<form>
......
<input type=button value="Submit your page" onClick="javascript:Submit();return false;//">
上面的這個例子通過表格提交來觸動 javascript 的 submit 函數, 最後調用了 verify.php. 現在你就可以跳過所有的 javascript 而直接對 verify.php 來提交.
<input type=button value="Submit your page" onClick="javascript:Submit();return false;//">
上面的這個例子通過表格提交來觸動 javascript 的 submit 函數, 最後調用了 verify.php. 現在你就可以跳過所有的 javascript 而直接對 verify.php 來提交.
************************************************************************************The Fuck Cutline************************************************************************************
譯者/作者:qiang
出處:中國Perl協會 FPC(Foundation of Perlchina)
作者:Sean M. Burke – Perl & LWP 作者(O’Reilly 出版)
原名:Web Basics with LWP
發表:2002 年 2月 28 日
原文:http://www.perl.com/pub/a/2002/08/20/perlandlwp.html
請保護作者的著作權,維護作者勞動的結晶。