[ 来源: | 作者: | 时间:2007-07-16 | 收藏 | 推荐 ] 【大 中 小】
模块解释 www.it55.com
链接检查
sflj www.it55.com kg^&fgd
网页增长得很快,每个网站可能有很多的(比如千个以上的连接),现在该是我们做某些事的时候了。特别是,寻找,修正,删除他们的时候。
我当然不会点击这么多的连接去看他们是否正常。我们需要一个程序来做这些事情。yahoo列出了这些检查网页的程序,但是没有发现任何一个程序是:简单,免费,为了我的连接。 www.it55.com
特性 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
我们称这个“linkcheck”。为了在网页上检查这个连接,我们可以这样写:
linkcheck http://my.isp.com/page.html
这个程序会给我们这样的报告 : sflj www.it55.com kg^&fgd
Checked 1 pages, 49 links
Found 0 broken links www.it55.com在线教程
-r
正如所展示的,linkcheck检查一个网页上的所有的连接,但是我们希望检查在一个站点上的所有的连接。我们可以通过找到到其它网页的连接,再检查那些网页来做到。
linkcheck -r http://my.isp.com/page.html
Checked 144 pages, 1025 links
Found 3 broken links vd;k;l www.it55.com rdfg
如果我们检查所有的连接,我们可能不可能结束像蜘蛛网一样的网络。为了避免这些,我们应该仅仅只检查在我们自己网页上的连接 。
sflj www.it55.com kg^&fgd
-o it55.com
我们一般不想检查那些脱机的网页,但是我们仍有可能要检查这些脱机的网页。如果我们想要检查脱机的网页的话,我们可以设置-o这个标志位。
linkcheck -o -r http://my.isp.com/page.html
Checked 144 pages, 1131 links
Found 3 broken links www.it55.com在线教程
-v verbosity it55.com
如果我们找到了那些坏的连接,我们有可能想知道这些连接是什么。-v verbosity 这个标志位控莆颐堑玫降氖涑龅氖俊?
-v 0
显示 坏连接的数量 (默认)
-v 1
同时列出坏连接
-v 2
同时列出检查的网页
-v 3
同时列出检查的连接 www.it55.com
-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 免费资源www.it55.com
Pod::Usage
URI vd;k;l www.it55.com rdfg
使用这些模块,我们可以使用仅仅几百行的代码来构成这个程序。在以下的部份,我们展示怎样编写这个程序。 免费资源www.it55.com
模块 http://www.it55.com/
首先,我们复习一下模块
Getopt::Std
Getopt::Std 解析命令行的选项。更详细的说明参见GetOpt::. 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
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
IT资讯之家 www.it55.com
访问器解开一个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 http://www.it55.com/
as_string() 方法返回一个代表url对象的字符。我们可以使用Url对象在任何可以使用字符的地方:
print "$urin";
$Visited{$uri} = 1;
IT资讯之家 www.it55.com
LWP::UserAgent vd;k;l www.it55.com rdfg
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响应。 it55.com
$ua = new LWP::UserAgent;
$request = new HTTP::Request GET => $uri;
$response = $ua->request($request); www.it55.com在线教程
$response 包括web页面的内容:
$content = $response->content;
如果我们只需要http头来检查一个页面的修改时间,或是它的存在,我们可以使用一个head的请求:
$request = new HTTP::Request HEAD => $uri;
request()方法自动处理重定向。我们可以从最后得到的页面覆盖原来的url,就像这样 :
$uri = $response->request->uri;
vd;k;l www.it55.com rdfg
HTML::Parser
一旦我们有了一个web页面,我们希望找到上面的所有的网页。HTML::Parser解析web页面。我们并不是直接使用HTML::Parser这个模块。然而我们创建使用它的一个子程序:
use HTML::Parser; http://www.it55.com/
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标签 vd;k;l www.it55.com rdfg
$tag eq 'a' and $attr->{href} and
$href = $attr->{href};
还有name(为了碎片)标签
IT资讯之家 www.it55.com
$tag eq 'a' and $attr->{name} and
$name = $attr->{name}; 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
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文档以前退出程序。 IT资讯之家 www.it55.com
Packages www.it55.com
模块和包是相关的,但却又是不同的概念。一个模块是包含perl源代码的文档。一个包是包含perl子程序或者变量的名字空间。
模块的作者一般都把他们的代码放在模块后面命名的包里。这样可以把它们封装起来,并避免名字的冲突。相反的,包的作者可能把包放在模块里,以便其他人可以使用这个程序。
不过,我们还可以通过申明一个包,把包直接嵌入我们的程序里。
package Spinner;
我们在我们的程序里使用包是为了:
产生一个内部的界面
支持封装
避免名字的冲突
如果我们写模块的话,我们应该
产生一个完整的,一般性的界面
选择一个好的模块的名字
提供文档
然而,包仅仅在我们的程序里才是可见的,因此,我们不必太正式:我们可以以我们的方便来创建和使用包。这里有我们在linkcheck里使用的包
Spinner
HTML::Parser::Links
Page
Link
Spinner
-t 选项显示一个微调控制项。这是一个1个字符的操作,由下列字符分割:
| / -
下面是这个完整的包:
package Spinner; vd;k;l www.it55.com rdfg
use vars qw($N @Spin); it55.com
@Spin = ('|', '/', '-', ''); www.it55.com
sub Spin
{
print STDERR $Spin[$N++], "r";
$N==4 and $N=0;
}
www.it55.com
这个包并不是太大。$N,@Spin,&Spin都在Spinner::这个包的名字空间里。为了预备这个spinner,我们这样调用 :
Spinner::Spin();
package Spinner;
my $N;
my @Spin = ('|', '/', '-', '');
如果Spinner是一个模块,这也许会更好 。然而,在这里这并不会真正提供任何的封装。File-scoping并不妨碍包的声明,所以任何的File-scoping词汇都会分享同个名字空间,从而和在整修文档的其它的File-scoping词汇产生名字冲突。 免费资源www.it55.com
HTML::Parser::Links
是HTML::Parser的一个子程序。上面展示的一些片断说明了它的基本的界面接口。在我们的子程序中,我们有额外的实时的数据来代替解析后的html页面。以及访问器(accessors)返回关于这个页面的信息。 免费资源www.it55.com
新的方法是我们的构造器:
sub new
{
my($class, $base) = @_; vd;k;l www.it55.com rdfg
my $parser = new HTML::Parser;
$parser->{base } = $base;
$parser->{links} = [];
$parser->{fragment} = {}; www.it55.com
bless $parser, $class
}
为了产生一个HTML::Parser::Links对象,我们
产生一个HTML::Parser对象
增加我们的即时的变量到对象
在我们的类中重新神圣引用
下面是完整的开始的方法:
sub start
{
my($parser, $tag, $attr, $attrseq, $origtext) = @_; 免费资源www.it55.com
$tag eq 'base' and
$parser->{base} = $attr->{href}; www.it55.com在线教程
$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. sflj www.it55.com kg^&fgd
Page
Page包得到和解析web页面。web页面是交互连接的。也许可能有很多相同连接都是指向同一个页面。然而,下载页面花费时间,因此,我们不想下载同一个页面多次。 sflj www.it55.com kg^&fgd
Page把web页面缓存在%Page::Content中。URL是哈希的键,页面的内容是它的值。当我们第一次请求页面的时候,Page下载它,并将它放在缓存中。任何以下的相同请求都是从缓存中读取。
Page包还解析web页面。解析一个web页面,并不需要网络的I/O,但是这仍然花费时间,如果我们对每个片断都检查解析的话,时间也许就会用得很多。
为了避免这个,Page包缓存了解析后的内容在%Page::Parse中。哈希的键是URL,值是一个HTML::Parser::Links对象。 it55.com
下面是一个外部的接口:
$page = new Page $uri;
$uri = $page->uri;
上一篇 目录 下一篇s = $page->links;
$content = get $page;
$parser = parse $page; vd;k;l www.it55.com rdfg
Link
Link包检查一个连接的有效性。
它的外部接口非常简单:
上一篇 目录 下一篇 = new Link $uri;
$ok = 上一篇 目录 下一篇->check;
和Page包一样,Link有许多对不必要的操作的优化。
检查连接分为两个部份。如果连接有碎片的话,
http://my.isp.com/page.html#section
那么我们就必须下整个的页面。事实上,我们甚至不必下载它。一个头部请求就可以告诉我们这个页面是否存在,而这个就是我们所关心的。
在内部,check()方法分别调用check_fragment()或者check_base(),来处理这两个事件。check_fragment()使用Page包来下载,解析包,然后,它检查看是否碎片存在于这个页面。check_base()发出一个头部请求,直接看是否存在这个页面。
www.it55.com
www.it55.com在线教程
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(); vd;k;l www.it55.com rdfg
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);
}
}
www.it55.com
CheckPage
CheckPage()检查一个网页。
sub CheckPage
{
my $uri = shift; www.it55.com在线教程
$Checked{$uri} and return;
$Checked{$uri} = 1;
$Pages++;
Twiddle();
print "PAGE $urin" if $Options{v} > 1;
my $page = new Page $uri;
my 上一篇 目录 下一篇s = $page->links;
defined 上一篇 目录 下一篇s or
die "Can't get $urin"; http://www.it55.com/
CheckLinks($page, 上一篇 目录 下一篇s);
}
经过一些内部管理后,它产生一个新的Page对象,得到在这个网页上的所有的连接,然后调用CheckLinks()。
linkcheck检查坏的连接,但是用户所指定的那些必须存在。如果我们不能下载其中的一个,我们就停止。 vd;k;l www.it55.com rdfg
CheckLinks
CheckLinks()检查在一个页面上的连接。
sub CheckLinks
{
my($page, 上一篇 目录 下一篇s) = @_;
my @links; IT资讯之家 www.it55.com
for my 上一篇 目录 下一篇 (@上一篇 目录 下一篇s)
{
上一篇 目录 下一篇->scheme eq 'http' or next;
my $on_site = 上一篇 目录 下一篇->authority eq $Authority;
$on_site or $Options{o} or next;
$Links++;
Twiddle();
print "LINK 上一篇 目录 下一篇n" if $Options{v} > 2;
Link->new(上一篇 目录 下一篇)->check or do
{
Report($page, 上一篇 目录 下一篇);
next;
};
$on_site or next;
上一篇 目录 下一篇->fragment(undef);
push @links, 上一篇 目录 下一篇;
}
45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
$Options{r} or return; it55.com
for my 上一篇 目录 下一篇 (@links)
{
CheckPage(上一篇 目录 下一篇);
}
} www.it55.com在线教程
第一个循环检查连接。我们只检查http的连接。如果-o标志是指定的话,我们只检查站内的连接。目前的check是:
Link->new(上一篇 目录 下一篇)->check
如果检查失败,我们就调用report(); vd;k;l www.it55.com rdfg
如果检查通过,而且连接是在站点上的,我们把它加入@links数组。
如果-r标志指定的话,我们进入第二个循环,然后我们对每个在站上的连接调用checkpage()。
sflj www.it55.com kg^&fgd
Output
Report()根据-a,-v标志打印出坏的连接。
Twiddle()根据-t标志打印出每个具体的进一步的报告。
Summary()打印出一个最终的检查页面的报告。
vd;k;l www.it55.com rdfg
包的强大并不在于它们做任何常复杂和老练的事。尽管,一旦我们写好了它们,我们可以不必管它们怎样工作就可以使用他们。
www.it55.com在线教程
(编辑:IT资讯之家 www.it55.com)