top
Loading...
模塊和包的使用
模塊解釋

鏈接檢查

  網頁增長得很快,每個網站可能有很多的(比如千個以上的連接),現在該是我們做某些事的時候了。特別是,尋找,修正,刪除他們的時候。
  我當然不會點擊這么多的連接去看他們是否正常。我們需要一個程序來做這些事情。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 
北斗有巢氏 有巢氏北斗