模塊和包的使用
模塊解釋
鏈接檢查
網頁增長得很快,每個網站可能有很多的(比如千個以上的連接),現在該是我們做某些事的時候了。特別是,尋找,修正,刪除他們的時候。
我當然不會點擊這么多的連接去看他們是否正常。我們需要一個程序來做這些事情。yahoo列出了這些檢查網頁的程序,但是沒有發現任何一個程序是:簡單,免費,為了我的連接。
特性
我們稱這個“linkcheck”。為了在網頁上檢查這個連接,我們可以這樣寫:
linkcheck http://my.isp.com/page.html
這個程序會給我們這樣的報告 :
Checked 1 pages, 49 links
Found 0 broken links
-r
正如所展示的,linkcheck檢查一個網頁上的所有的連接,但是我們希望檢查在一個站點上的所有的連接。我們可以通過找到到其它網頁的連接,再檢查那些網頁來做到。
linkcheck -r http://my.isp.com/page.html
Checked 144 pages, 1025 links
Found 3 broken links
如果我們檢查所有的連接,我們可能不可能結束像蜘蛛網一樣的網絡。為了避免這些,我們應該僅僅只檢查在我們自己網頁上的連接 。
-o
我們一般不想檢查那些脫機的網頁,但是我們仍有可能要檢查這些脫機的網頁。如果我們想要檢查脫機的網頁的話,我們可以設置-o這個標志位。
linkcheck -o -r http://my.isp.com/page.html
Checked 144 pages, 1131 links
Found 3 broken links
-v verbosity
如果我們找到了那些壞的連接,我們有可能想知道這些連接是什么。-v verbosity 這個標志位控制我們得到的輸出的數量。
-v 0
顯示 壞連接的數量 (默認)
-v 1
同時列出壞連接
-v 2
同時列出檢查的網頁
-v 3
同時列出檢查的連接
-t twiddle
網頁可能要花很長的時間來下載。當我們在等待的時候,我們總想看一些輸出的結果,以此來知道程序在做一些事情,也因此我們不會感到太無聊。我們可以使用 -v 標志,但是可能要把結果輸出到一個文件或是一個管道。因此,我們提供了這個標志。
-t 0
什么也沒有(默認)
-t 1
徽調控制項: | / -
-t 2
程序報告:"$Pages pages, $Links links, $Broken brokenr"
輸出是輸出到標準輸出,twiddle的是從標準錯誤輸出的。這個我們是可以重定向的。它也確保了twiddle不會從緩沖中讀取,它展示的是實時的。
運算法則
這是檢查一個網頁的大體的大鋼。
給定一個url,我們必須
從url解析主機名,
打開一個tcp連接到服務器,
發送一個http請求,
接收一個http響應,
處理重定向 ,
從http響應中解開html網頁,
解釋出這個網頁中的所有的連接,
處理相關連接,
檢查每個連接的后輟名,
為了把這個算法轉化成一個程序,我們必須
解析命令行,
確定腹鳴機連接,
為已經訪問的網頁和檢查的連接做個記號 ,
產生文件 ,
從最原始的開始做這個工作將是個很大的工作。幸運的是,我們不必這么做。大多數繁重的工作別人已經做好了,而且做成了模塊提供了。這里是linkcheck所用到的模塊,
Getopt::Std
HTML::Parser
LWP::UserAgent
Pod::Usage
URI
使用這些模塊,我們可以使用僅僅幾百行的代碼來構成這個程序。在以下的部份,我們展示怎樣編寫這個程序。
模塊
首先,我們復習一下模塊
Getopt::Std
Getopt::Std 解析命令行的選項。更詳細的說明參見GetOpt::.
URL
URL管理URL: 每個URL的對象代表一個單一的URL。URL可以有很多的方法來構建操作,分析URL,但是我們僅僅需要其中的 一些功能。
創建一個URL對象,我們這樣寫:
$uri = new URI 'http://my.isp.com/page1.html#section1';
我們可以用new_abs來分解相關連接
$uri2 = new_abs 'page2.html', $uri; # http://my.isp.com/page2.html
訪問器解開一個url的成分:
$uri->scheme; # http
$uri->authority; # my.isp.com
$uri->fragment; # section1
傳送一個參數到訪問器,設置那個成份。空的成份代表沒有定義:
$uri->fragment('section2'); # http://my.isp.com/page1.html#section2
$uri->fragment(undef); # http://my.isp.com/page1.html
as_string() 方法返回一個代表url對象的字符。我們可以使用Url對象在任何可以使用字符的地方:
print "$urin";
$Visited{$uri} = 1;
LWP::UserAgent
LWP是在perl中用來訪問www的一個庫。我們用它來得到web頁面。也許得到一個網頁的最簡單的方法是使用LWP::Simple這個模塊。
use LWP::Simple;
$content = get($uri);
get() 這個方法返回了web頁面的內容,失敗時返回undef.然而,我們需要更進一步的信息,所以我們應該使用LWP::UserAgent這個模塊。
一個user agent是任何http client類型。LWP::UserAgent在perl中執行一個http client.為了得到一個web頁面,我們產生一個LWP::UserAgent對象, 發送一個http請求,接收一個http響應。
$ua = new LWP::UserAgent;
$request = new HTTP::Request GET => $uri;
$response = $ua->request($request);
$response 包括web頁面的內容:
$content = $response->content;
如果我們只需要http頭來檢查一個頁面的修改時間,或是它的存在,我們可以使用一個head的請求:
$request = new HTTP::Request HEAD => $uri;
request()方法自動處理重定向。我們可以從最后得到的頁面覆蓋原來的url,就像這樣 :
$uri = $response->request->uri;
HTML::Parser
一旦我們有了一個web頁面,我們希望找到上面的所有的網頁。HTML::Parser解析web頁面。我們并不是直接使用HTML::Parser這個模塊。然而我們創建使用它的一個子程序:
use HTML::Parser;
package HTML::Parser::Links;
use base qw(HTML::Parser);
為了解析一個web頁面,我們創建一個我們子程序的對象,傳遞網頁的內容到parse方法。
$parser = new HTML::Parser::Links;
$parser->parse($content);
$parser->eof;
parse像回調一樣調用我們子程序中的方法:
sub start
{
my($parser, $tag, $attr, $attrseq, $origtext) = @_;
無論什么時候parse定義了一個html標簽的開始,它就調用start子程序。參數是:
$parser
HTML::Parser::Links 對象
$tag
html的名字,比如:h1,a,strong
%$attr
一個鍵值對的哈希列表
@$attrseq
在列表中的屬性列表,以它們最初的排列
$origtext
標簽的原始文本
我們只是關心一些標簽和它們的屬性。如果找到一個基本的標簽,我們捕獲這個url,以便我們可以解析相關的連接
$tag eq 'base' and
$base = $attr->{href};
我們找到一個(錨)標簽,我們捕獲href標簽
$tag eq 'a' and $attr->{href} and
$href = $attr->{href};
還有name(為了碎片)標簽
$tag eq 'a' and $attr->{name} and
$name = $attr->{name};
Pod::Usage
使用pod格式在程序中嵌入一個perl文檔是很平常的。Pod::Usage這個模塊解析它所找到的所有的源程序中的pod格式的文本,并把它打印出來 。這使得增加程序的說明和幫助很容易。
pod2usage(); # print synopsis
pod2usage(VERBOSE=>1); # print synopsis and options
pod2usage(VERBOSE=>2); # print entire man page
當命令行上有錯誤時,pod2usage這個模塊是被經常用的,它使得在打印出pod文檔以前退出程序。
Packages
模塊和包是相關的,但卻又是不同的概念。一個模塊是包含perl源代碼的文檔。一個包是包含perl子程序或者變量的名字空間。
模塊的作者一般都把他們的代碼放在模塊后面命名的包里。這樣可以把它們封裝起來,并避免名字的沖突。相反的,包的作者可能把包放在模塊里,以便其他人可以使用這個程序。
不過,我們還可以通過申明一個包,把包直接嵌入我們的程序里。
package Spinner;
我們在我們的程序里使用包是為了:
產生一個內部的界面
支持封裝
避免名字的沖突
如果我們寫模塊的話,我們應該
產生一個完整的,一般性的界面
選擇一個好的模塊的名字
提供文檔
然而,包僅僅在我們的程序里才是可見的,因此,我們不必太正式:我們可以以我們的方便來創建和使用包。這里有我們在linkcheck里使用的包
Spinner
HTML::Parser::Links
Page
Link
Spinner
-t 選項顯示一個微調控制項。這是一個1個字符的操作,由下列字符分割:
| / -
下面是這個完整的包:
package Spinner;
use vars qw($N @Spin);
@Spin = ('|', '/', '-', '');
sub Spin
{
print STDERR $Spin[$N++], "r";
$N==4 and $N=0;
}
這個包并不是太大。$N,@Spin,&Spin都在Spinner::這個包的名字空間里。為了預備這個spinner,我們這樣調用 :
Spinner::Spin();
package Spinner;
my $N;
my @Spin = ('|', '/', '-', '');
如果Spinner是一個模塊,這也許會更好 。然而,在這里這并不會真正提供任何的封裝。File-scoping并不妨礙包的聲明,所以任何的File-scoping詞匯都會分享同個名字空間,從而和在整修文檔的其它的File-scoping詞匯產生名字沖突。
HTML::Parser::Links
是HTML::Parser的一個子程序。上面展示的一些片斷說明了它的基本的界面接口。在我們的子程序中,我們有額外的實時的數據來代替解析后的html頁面。以及訪問器(accessors)返回關于這個頁面的信息。
新的方法是我們的構造器:
sub new
{
my($class, $base) = @_;
my $parser = new HTML::Parser;
$parser->{base } = $base;
$parser->{links} = [];
$parser->{fragment} = {};
bless $parser, $class
}
為了產生一個HTML::Parser::Links對象,我們
產生一個HTML::Parser對象
增加我們的即時的變量到對象
在我們的類中重新神圣引用
下面是完整的開始的方法:
sub start
{
my($parser, $tag, $attr, $attrseq, $origtext) = @_;
$tag eq 'base' and
$parser->{base} = $attr->{href};
$tag eq 'a' and $attr->{href} and do
{
my $base = $parser->{base};
my $href = $attr->{href};
my $uri = new_abs URI $href, $base;
push @{$parser->{links}}, $uri;
};
$tag eq 'a' and $attr->{name} and do
{
my $name = $attr->{name};
$parser->{fragment}{$name} = 1;
};
}
我們只是關心最基礎的以及一個標簽。如果我們發現一個基本的元素,我們保存超連接,因此,我們可以解析相關連接。當我們發現一個連接,我們創建一個新的URI對象,把它加到連接的列表中。最后,我們找到一個片斷,把它加到片斷的哈希列表中。
我們有兩個訪問器:
$parser->links()
如果$fragment存在于這個頁面,它返回true.
Page
Page包得到和解析web頁面。web頁面是交互連接的。也許可能有很多相同連接都是指向同一個頁面。然而,下載頁面花費時間,因此,我們不想下載同一個頁面多次。
Page把web頁面緩存在%Page::Content中。URL是哈希的鍵,頁面的內容是它的值。當我們第一次請求頁面的時候,Page下載它,并將它放在緩存中。任何以下的相同請求都是從緩存中讀取。
Page包還解析web頁面。解析一個web頁面,并不需要網絡的I/O,但是這仍然花費時間,如果我們對每個片斷都檢查解析的話,時間也許就會用得很多。
為了避免這個,Page包緩存了解析后的內容在%Page::Parse中。哈希的鍵是URL,值是一個HTML::Parser::Links對象。
下面是一個外部的接口:
$page = new Page $uri;
$uri = $page->uri;
$links = $page->links;
$content = get $page;
$parser = parse $page;
Link
Link包檢查一個連接的有效性。
它的外部接口非常簡單:
$link = new Link $uri;
$ok = $link->check;
和Page包一樣,Link有許多對不必要的操作的優化。
檢查連接分為兩個部份。如果連接有碎片的話,
http://my.isp.com/page.html#section
那么我們就必須下整個的頁面。事實上,我們甚至不必下載它。一個頭部請求就可以告訴我們這個頁面是否存在,而這個就是我們所關心的。
在內部,check()方法分別調用check_fragment()或者check_base(),來處理這兩個事件。check_fragment()使用Page包來下載,解析包,然后,它檢查看是否碎片存在于這個頁面。check_base()發出一個頭部請求,直接看是否存在這個頁面。
無論在哪個事件,check()緩存結果到%Link::Check。因此,我們可以不必對相同的連接檢查兩次。
Program
由于所有的基礎都由包和模塊提供了,我們可以在100行代碼內完成linkcheck這個程序。下面是主程序:
package main;
my %Options;
my %Checked;
my($Scheme, $Authority);
my($Pages, $Links, $Broken) = (0, 0, 0);
getopt('vt', %Options);
Help();
CheckPages(@ARGV);
Summary();
Globals
我們聲明我們的全局變量。這是我們的主程序。文檔范圍也許就屬于這里。%Options保存了命令行的選項。%Checked 是一個已經檢查過了的url的哈希結構。我們用它來避免由于循環連接而帶來的無限的遞歸運算。$Authority報告當前的站點。我們用它來確定在站的連接。$Pages, $Links 和 $Broken保存Progress()和Summary()的計數。
CheckPages
當處理好了命令行的選項后,@ARGV就包含了要檢查的網頁的一個列表。
CheckPages()產生為每個網頁產生一個URI的對象,并調用 CheckPage()。
sub CheckPages
{
my @pages = @_;
my @URIs = map { new URI $_ } @pages;
for my $uri (@URIs)
{
$Scheme = $uri->scheme;
$Authority = $uri->authority;
CheckPage($uri);
}
}
CheckPage
CheckPage()檢查一個網頁。
sub CheckPage
{
my $uri = shift;
$Checked{$uri} and return;
$Checked{$uri} = 1;
$Pages++;
Twiddle();
print "PAGE $urin" if $Options{v} > 1;
my $page = new Page $uri;
my $links = $page->links;
defined $links or
die "Can't get $urin";
CheckLinks($page, $links);
}
經過一些內部管理后,它產生一個新的Page對象,得到在這個網頁上的所有的連接,然后調用CheckLinks()。
linkcheck檢查壞的連接,但是用戶所指定的那些必須存在。如果我們不能下載其中的一個,我們就停止。
CheckLinks
CheckLinks()檢查在一個頁面上的連接。
sub CheckLinks
{
my($page, $links) = @_;
my @links;
for my $link (@$links)
{
$link->scheme eq 'http' or next;
my $on_site = $link->authority eq $Authority;
$on_site or $Options{o} or next;
$Links++;
Twiddle();
print "LINK $linkn" if $Options{v} > 2;
Link->new($link)->check or do
{
Report($page, $link);
next;
};
$on_site or next;
$link->fragment(undef);
push @links, $link;
}
$Options{r} or return;
for my $link (@links)
{
CheckPage($link);
}
}
第一個循環檢查連接。我們只檢查http的連接。如果-o標志是指定的話,我們只檢查站內的連接。目前的check是:
Link->new($link)->check
如果檢查失敗,我們就調用report();
如果檢查通過,而且連接是在站點上的,我們把它加入@links數組。
如果-r標志指定的話,我們進入第二個循環,然后我們對每個在站上的連接調用checkpage()。
Output
Report()根據-a,-v標志打印出壞的連接。
Twiddle()根據-t標志打印出每個具體的進一步的報告。
Summary()打印出一個最終的檢查頁面的報告。
包的強大并不在于它們做任何常復雜和老練的事。盡管,一旦我們寫好了它們,我們可以不必管它們怎樣工作就可以使用他們。
翻譯:rj
出自:perlmonth.com issue 11
鏈接檢查
網頁增長得很快,每個網站可能有很多的(比如千個以上的連接),現在該是我們做某些事的時候了。特別是,尋找,修正,刪除他們的時候。
我當然不會點擊這么多的連接去看他們是否正常。我們需要一個程序來做這些事情。yahoo列出了這些檢查網頁的程序,但是沒有發現任何一個程序是:簡單,免費,為了我的連接。
特性
我們稱這個“linkcheck”。為了在網頁上檢查這個連接,我們可以這樣寫:
linkcheck http://my.isp.com/page.html
這個程序會給我們這樣的報告 :
Checked 1 pages, 49 links
Found 0 broken links
-r
正如所展示的,linkcheck檢查一個網頁上的所有的連接,但是我們希望檢查在一個站點上的所有的連接。我們可以通過找到到其它網頁的連接,再檢查那些網頁來做到。
linkcheck -r http://my.isp.com/page.html
Checked 144 pages, 1025 links
Found 3 broken links
如果我們檢查所有的連接,我們可能不可能結束像蜘蛛網一樣的網絡。為了避免這些,我們應該僅僅只檢查在我們自己網頁上的連接 。
-o
我們一般不想檢查那些脫機的網頁,但是我們仍有可能要檢查這些脫機的網頁。如果我們想要檢查脫機的網頁的話,我們可以設置-o這個標志位。
linkcheck -o -r http://my.isp.com/page.html
Checked 144 pages, 1131 links
Found 3 broken links
-v verbosity
如果我們找到了那些壞的連接,我們有可能想知道這些連接是什么。-v verbosity 這個標志位控制我們得到的輸出的數量。
-v 0
顯示 壞連接的數量 (默認)
-v 1
同時列出壞連接
-v 2
同時列出檢查的網頁
-v 3
同時列出檢查的連接
-t twiddle
網頁可能要花很長的時間來下載。當我們在等待的時候,我們總想看一些輸出的結果,以此來知道程序在做一些事情,也因此我們不會感到太無聊。我們可以使用 -v 標志,但是可能要把結果輸出到一個文件或是一個管道。因此,我們提供了這個標志。
-t 0
什么也沒有(默認)
-t 1
徽調控制項: | / -
-t 2
程序報告:"$Pages pages, $Links links, $Broken brokenr"
輸出是輸出到標準輸出,twiddle的是從標準錯誤輸出的。這個我們是可以重定向的。它也確保了twiddle不會從緩沖中讀取,它展示的是實時的。
運算法則
這是檢查一個網頁的大體的大鋼。
給定一個url,我們必須
從url解析主機名,
打開一個tcp連接到服務器,
發送一個http請求,
接收一個http響應,
處理重定向 ,
從http響應中解開html網頁,
解釋出這個網頁中的所有的連接,
處理相關連接,
檢查每個連接的后輟名,
為了把這個算法轉化成一個程序,我們必須
解析命令行,
確定腹鳴機連接,
為已經訪問的網頁和檢查的連接做個記號 ,
產生文件 ,
從最原始的開始做這個工作將是個很大的工作。幸運的是,我們不必這么做。大多數繁重的工作別人已經做好了,而且做成了模塊提供了。這里是linkcheck所用到的模塊,
Getopt::Std
HTML::Parser
LWP::UserAgent
Pod::Usage
URI
使用這些模塊,我們可以使用僅僅幾百行的代碼來構成這個程序。在以下的部份,我們展示怎樣編寫這個程序。
模塊
首先,我們復習一下模塊
Getopt::Std
Getopt::Std 解析命令行的選項。更詳細的說明參見GetOpt::.
URL
URL管理URL: 每個URL的對象代表一個單一的URL。URL可以有很多的方法來構建操作,分析URL,但是我們僅僅需要其中的 一些功能。
創建一個URL對象,我們這樣寫:
$uri = new URI 'http://my.isp.com/page1.html#section1';
我們可以用new_abs來分解相關連接
$uri2 = new_abs 'page2.html', $uri; # http://my.isp.com/page2.html
訪問器解開一個url的成分:
$uri->scheme; # http
$uri->authority; # my.isp.com
$uri->fragment; # section1
傳送一個參數到訪問器,設置那個成份。空的成份代表沒有定義:
$uri->fragment('section2'); # http://my.isp.com/page1.html#section2
$uri->fragment(undef); # http://my.isp.com/page1.html
as_string() 方法返回一個代表url對象的字符。我們可以使用Url對象在任何可以使用字符的地方:
print "$urin";
$Visited{$uri} = 1;
LWP::UserAgent
LWP是在perl中用來訪問www的一個庫。我們用它來得到web頁面。也許得到一個網頁的最簡單的方法是使用LWP::Simple這個模塊。
use LWP::Simple;
$content = get($uri);
get() 這個方法返回了web頁面的內容,失敗時返回undef.然而,我們需要更進一步的信息,所以我們應該使用LWP::UserAgent這個模塊。
一個user agent是任何http client類型。LWP::UserAgent在perl中執行一個http client.為了得到一個web頁面,我們產生一個LWP::UserAgent對象, 發送一個http請求,接收一個http響應。
$ua = new LWP::UserAgent;
$request = new HTTP::Request GET => $uri;
$response = $ua->request($request);
$response 包括web頁面的內容:
$content = $response->content;
如果我們只需要http頭來檢查一個頁面的修改時間,或是它的存在,我們可以使用一個head的請求:
$request = new HTTP::Request HEAD => $uri;
request()方法自動處理重定向。我們可以從最后得到的頁面覆蓋原來的url,就像這樣 :
$uri = $response->request->uri;
HTML::Parser
一旦我們有了一個web頁面,我們希望找到上面的所有的網頁。HTML::Parser解析web頁面。我們并不是直接使用HTML::Parser這個模塊。然而我們創建使用它的一個子程序:
use HTML::Parser;
package HTML::Parser::Links;
use base qw(HTML::Parser);
為了解析一個web頁面,我們創建一個我們子程序的對象,傳遞網頁的內容到parse方法。
$parser = new HTML::Parser::Links;
$parser->parse($content);
$parser->eof;
parse像回調一樣調用我們子程序中的方法:
sub start
{
my($parser, $tag, $attr, $attrseq, $origtext) = @_;
無論什么時候parse定義了一個html標簽的開始,它就調用start子程序。參數是:
$parser
HTML::Parser::Links 對象
$tag
html的名字,比如:h1,a,strong
%$attr
一個鍵值對的哈希列表
@$attrseq
在列表中的屬性列表,以它們最初的排列
$origtext
標簽的原始文本
我們只是關心一些標簽和它們的屬性。如果找到一個基本的標簽,我們捕獲這個url,以便我們可以解析相關的連接
$tag eq 'base' and
$base = $attr->{href};
我們找到一個(錨)標簽,我們捕獲href標簽
$tag eq 'a' and $attr->{href} and
$href = $attr->{href};
還有name(為了碎片)標簽
$tag eq 'a' and $attr->{name} and
$name = $attr->{name};
Pod::Usage
使用pod格式在程序中嵌入一個perl文檔是很平常的。Pod::Usage這個模塊解析它所找到的所有的源程序中的pod格式的文本,并把它打印出來 。這使得增加程序的說明和幫助很容易。
pod2usage(); # print synopsis
pod2usage(VERBOSE=>1); # print synopsis and options
pod2usage(VERBOSE=>2); # print entire man page
當命令行上有錯誤時,pod2usage這個模塊是被經常用的,它使得在打印出pod文檔以前退出程序。
Packages
模塊和包是相關的,但卻又是不同的概念。一個模塊是包含perl源代碼的文檔。一個包是包含perl子程序或者變量的名字空間。
模塊的作者一般都把他們的代碼放在模塊后面命名的包里。這樣可以把它們封裝起來,并避免名字的沖突。相反的,包的作者可能把包放在模塊里,以便其他人可以使用這個程序。
不過,我們還可以通過申明一個包,把包直接嵌入我們的程序里。
package Spinner;
我們在我們的程序里使用包是為了:
產生一個內部的界面
支持封裝
避免名字的沖突
如果我們寫模塊的話,我們應該
產生一個完整的,一般性的界面
選擇一個好的模塊的名字
提供文檔
然而,包僅僅在我們的程序里才是可見的,因此,我們不必太正式:我們可以以我們的方便來創建和使用包。這里有我們在linkcheck里使用的包
Spinner
HTML::Parser::Links
Page
Link
Spinner
-t 選項顯示一個微調控制項。這是一個1個字符的操作,由下列字符分割:
| / -
下面是這個完整的包:
package Spinner;
use vars qw($N @Spin);
@Spin = ('|', '/', '-', '');
sub Spin
{
print STDERR $Spin[$N++], "r";
$N==4 and $N=0;
}
這個包并不是太大。$N,@Spin,&Spin都在Spinner::這個包的名字空間里。為了預備這個spinner,我們這樣調用 :
Spinner::Spin();
package Spinner;
my $N;
my @Spin = ('|', '/', '-', '');
如果Spinner是一個模塊,這也許會更好 。然而,在這里這并不會真正提供任何的封裝。File-scoping并不妨礙包的聲明,所以任何的File-scoping詞匯都會分享同個名字空間,從而和在整修文檔的其它的File-scoping詞匯產生名字沖突。
HTML::Parser::Links
是HTML::Parser的一個子程序。上面展示的一些片斷說明了它的基本的界面接口。在我們的子程序中,我們有額外的實時的數據來代替解析后的html頁面。以及訪問器(accessors)返回關于這個頁面的信息。
新的方法是我們的構造器:
sub new
{
my($class, $base) = @_;
my $parser = new HTML::Parser;
$parser->{base } = $base;
$parser->{links} = [];
$parser->{fragment} = {};
bless $parser, $class
}
為了產生一個HTML::Parser::Links對象,我們
產生一個HTML::Parser對象
增加我們的即時的變量到對象
在我們的類中重新神圣引用
下面是完整的開始的方法:
sub start
{
my($parser, $tag, $attr, $attrseq, $origtext) = @_;
$tag eq 'base' and
$parser->{base} = $attr->{href};
$tag eq 'a' and $attr->{href} and do
{
my $base = $parser->{base};
my $href = $attr->{href};
my $uri = new_abs URI $href, $base;
push @{$parser->{links}}, $uri;
};
$tag eq 'a' and $attr->{name} and do
{
my $name = $attr->{name};
$parser->{fragment}{$name} = 1;
};
}
我們只是關心最基礎的以及一個標簽。如果我們發現一個基本的元素,我們保存超連接,因此,我們可以解析相關連接。當我們發現一個連接,我們創建一個新的URI對象,把它加到連接的列表中。最后,我們找到一個片斷,把它加到片斷的哈希列表中。
我們有兩個訪問器:
$parser->links()
如果$fragment存在于這個頁面,它返回true.
Page
Page包得到和解析web頁面。web頁面是交互連接的。也許可能有很多相同連接都是指向同一個頁面。然而,下載頁面花費時間,因此,我們不想下載同一個頁面多次。
Page把web頁面緩存在%Page::Content中。URL是哈希的鍵,頁面的內容是它的值。當我們第一次請求頁面的時候,Page下載它,并將它放在緩存中。任何以下的相同請求都是從緩存中讀取。
Page包還解析web頁面。解析一個web頁面,并不需要網絡的I/O,但是這仍然花費時間,如果我們對每個片斷都檢查解析的話,時間也許就會用得很多。
為了避免這個,Page包緩存了解析后的內容在%Page::Parse中。哈希的鍵是URL,值是一個HTML::Parser::Links對象。
下面是一個外部的接口:
$page = new Page $uri;
$uri = $page->uri;
$links = $page->links;
$content = get $page;
$parser = parse $page;
Link
Link包檢查一個連接的有效性。
它的外部接口非常簡單:
$link = new Link $uri;
$ok = $link->check;
和Page包一樣,Link有許多對不必要的操作的優化。
檢查連接分為兩個部份。如果連接有碎片的話,
http://my.isp.com/page.html#section
那么我們就必須下整個的頁面。事實上,我們甚至不必下載它。一個頭部請求就可以告訴我們這個頁面是否存在,而這個就是我們所關心的。
在內部,check()方法分別調用check_fragment()或者check_base(),來處理這兩個事件。check_fragment()使用Page包來下載,解析包,然后,它檢查看是否碎片存在于這個頁面。check_base()發出一個頭部請求,直接看是否存在這個頁面。
無論在哪個事件,check()緩存結果到%Link::Check。因此,我們可以不必對相同的連接檢查兩次。
Program
由于所有的基礎都由包和模塊提供了,我們可以在100行代碼內完成linkcheck這個程序。下面是主程序:
package main;
my %Options;
my %Checked;
my($Scheme, $Authority);
my($Pages, $Links, $Broken) = (0, 0, 0);
getopt('vt', %Options);
Help();
CheckPages(@ARGV);
Summary();
Globals
我們聲明我們的全局變量。這是我們的主程序。文檔范圍也許就屬于這里。%Options保存了命令行的選項。%Checked 是一個已經檢查過了的url的哈希結構。我們用它來避免由于循環連接而帶來的無限的遞歸運算。$Authority報告當前的站點。我們用它來確定在站的連接。$Pages, $Links 和 $Broken保存Progress()和Summary()的計數。
CheckPages
當處理好了命令行的選項后,@ARGV就包含了要檢查的網頁的一個列表。
CheckPages()產生為每個網頁產生一個URI的對象,并調用 CheckPage()。
sub CheckPages
{
my @pages = @_;
my @URIs = map { new URI $_ } @pages;
for my $uri (@URIs)
{
$Scheme = $uri->scheme;
$Authority = $uri->authority;
CheckPage($uri);
}
}
CheckPage
CheckPage()檢查一個網頁。
sub CheckPage
{
my $uri = shift;
$Checked{$uri} and return;
$Checked{$uri} = 1;
$Pages++;
Twiddle();
print "PAGE $urin" if $Options{v} > 1;
my $page = new Page $uri;
my $links = $page->links;
defined $links or
die "Can't get $urin";
CheckLinks($page, $links);
}
經過一些內部管理后,它產生一個新的Page對象,得到在這個網頁上的所有的連接,然后調用CheckLinks()。
linkcheck檢查壞的連接,但是用戶所指定的那些必須存在。如果我們不能下載其中的一個,我們就停止。
CheckLinks
CheckLinks()檢查在一個頁面上的連接。
sub CheckLinks
{
my($page, $links) = @_;
my @links;
for my $link (@$links)
{
$link->scheme eq 'http' or next;
my $on_site = $link->authority eq $Authority;
$on_site or $Options{o} or next;
$Links++;
Twiddle();
print "LINK $linkn" if $Options{v} > 2;
Link->new($link)->check or do
{
Report($page, $link);
next;
};
$on_site or next;
$link->fragment(undef);
push @links, $link;
}
$Options{r} or return;
for my $link (@links)
{
CheckPage($link);
}
}
第一個循環檢查連接。我們只檢查http的連接。如果-o標志是指定的話,我們只檢查站內的連接。目前的check是:
Link->new($link)->check
如果檢查失敗,我們就調用report();
如果檢查通過,而且連接是在站點上的,我們把它加入@links數組。
如果-r標志指定的話,我們進入第二個循環,然后我們對每個在站上的連接調用checkpage()。
Output
Report()根據-a,-v標志打印出壞的連接。
Twiddle()根據-t標志打印出每個具體的進一步的報告。
Summary()打印出一個最終的檢查頁面的報告。
包的強大并不在于它們做任何常復雜和老練的事。盡管,一旦我們寫好了它們,我們可以不必管它們怎樣工作就可以使用他們。
翻譯:rj
出自:perlmonth.com issue 11