原文地址:http://bbs.chinaunix.net/viewthread.php?tid=1316204
前天研究使用HTML::TreeBuilder模块分析网页,看到了一篇文章,顺便就翻译了一下,发上来分享。本人文笔不好,e文水平有限,大家撮合看吧。
原文地址:http://www.perl.com/pub/a/2006/01/19/analyzing_html.html?page=1
文章的背景是,作者在教授网页编辑的课程,他会给学生做一些使用nvu做网页作业,每个作业中有些特定的要求,作者苦于给学生的作业评分和做注释,所以就想到使用perl程序对学生的作品进行分析。
perl的正则表达式在文本处理方面的能力已经非常卓越,并且还有分解网页的专用模组HTML::TreeBuilder。它提供了一个html的分解器,这个分解器可以从一个网页构建出一个元素的树形结构。并且,从一个网页中建立一棵树和构建它的内容是非常容易的:
#新建一棵树
$tree = HTML::TreeBuilder->new;
#由一个网页文件构建树的内容
$tree->parse_file($file_name);
#当然也可以由一个变量的内容中分解出树的内容
$tree->parse($value);
树的节点是一个HTML::Element对象。这有很多方法可以存取和操作树中的这些节点。当你使用完成了这棵树的时候,可以使用下面的方法销毁它并且释放它占用的内存:
$tree->delete;
在HTML::TreeBuilder建立的树形结构中,一个模组HTML::Element代表一个html元素。它有大量的方法存取和操作这些元素和搜寻树中的子孙节点和祖先节点。例如:方法find()使用一个或更多的标签名作为参数来寻找所有的下行的相关节点:
@elements = $element->find('a', 'img');
上面这条语句将把所有$element节点以下的<a>节点和<img>节点存储在@elements数组里。方法look_down()是比find()更强大的搜索方法。它以三种类型的方法来查找下行节点:1,严格指定标签的名称或属性值。2,使用正则表达式匹配。3,通过一个返回真的子函数来确定想要的节点。下面是一些例子:
@anchors = $element->look_down('_tag' => 'a');
找到所有的$element下的<a>节点,并存储到@anchors数组中。
@colors = $element->look_down('style' => qr/color/);
找到所有的$element下的含有style属性并且该属性包括color的节点,并存储到@anchors数组中。
@largeimages = $element->look_down(
sub {
$_[0]->tag() eq 'img' and
($_[0]->attr('width') > 100 or
$_[0]->attr('height') > 100)
}
);
找到所有的$element下的<img>节点并且节点中的width和height属性必须大于100像素,并存储到@largeimages数组中。注意:这样的语句在检测到没有width和height属性的<img>节点时会产生一个警告信息。
我们还可以混用三种方法,例如:
@largeimages = $element->look_down(
'_tag' => 'img',
'width' => qr//,
'height' => qr//,
sub { $_[0]->attr('width') > 100 or
$_[0]->attr('height') > 100 }
);
这样就可以过滤那些没有width和height属性的<img>节点了,'width' => qr// 和 'height' => qr// 保证只有还有这两个属性的节点才被搜索到。
look_up()方法和look_down()方法一样,只不过它搜索$element节点的祖先节点(上行查找)。
2 多个文件分析:
下面讲一个实际的例子:一个学生的网页作业的自动评分程序。这个程序首先由一些html文件来构建一些树形结构,然后把他们存储在一个@trees数组中:
my @trees; foreach (@files) { print " building tree for $_ .../n" if $options{v}; my $tree = HTML::TreeBuilder->new; $tree->parse_file($_); push( @trees, $tree ); } |
doitem()子函数遍历@trees数组,并且请求一个代码块来在每棵树中寻找特定的html元素,并且存贮代码块返回的结果。它调用printd()函数显示发现的html元素和这些元素所在的文件名(当使用-v选项被设置的话):
sub doitem { my $func = shift; my $num = 0; foreach my $i ( 0 .. $#files ) { my @elements = $func->( $files[$i], $trees[$i] ); printd $files[$i], @elements; $num += @elements; } return $num; } |
上面提到的代码块是一个需要两个参数的子函数,两个参数一个是文件名另一个是该文件对应的html树,它返回书中找到元素的数组。下面的例子代码将返回所有网页中的斜体字,包括<i>标签元素(例如:<i>text</i>)和font-style属性为斜体的元素(例如:<spanSTYLE="font-style: italic">text</span>).
$n = doitem sub { my ( $file, $tree ) = @_; return ( $tree->find("i"), $tree->look_down( "style" => qr/font-style *: *italic/ ) ); }; marking "Italicized text (2 points): " . ( ( $n > 0 ) ? "good. 2" : "no italic text. 0" ); |
这样,在页面中的斜体字的两种方式都被检测了。marking函数报告检测的结果,并且在程序的最后进行测试,帮助计算总节点数。
其他的需求可以使用相同的方法,下面的代码看上去更加难理解,它使用了一个正则帮助我们选出网页中没定义颜色的元素。
my $pattern = qr/(^|[^-])color *: *rgb/( *[0-9]*, *[0-9]*, *[0-9]*/)/; return $tree->look_down( "style" => $pattern, sub { $_[0]->as_trimmed_text ne "" } ); |
nvu(一个可视化网页编辑器)使用rgb(R,G,B)形式的style属性设置文本的颜色(例如:<span STYLE="color: rgb(0, 0,255);">text</span>)。上面这段代码比斜体字的代码稍微严格一些,因为它还过滤那些没有包含任何文字的元素。代码中的as_trimmed_text()方法返回cut掉开头和结尾部空白字符的文本内容。
下面这段代码使用了嵌套的look_down()函数来定位有边框并且有连接的图表。它可以找到任何被<a>包裹的有边框的<img>。
return $tree->look_down( "_tag" => "a", sub { $_[0]->look_down( "_tag" => "img", sub { hasBorder( $_[0] ) } ); } ); |
检查没有连接的图表更加有趣,它需要look_down()和look_up()两个函数,下面这段代码只找出没有被<a>包裹的<img>元素。
return $tree->look_down( "_tag" => "img", sub { !$_[0]->look_up( "_tag" => "a" ) and hasBorder( $_[0] ); } ); |
检测正当的内部连接需要使用look_down()函数里的代码块排除一般的外部连接,通过检查href的值是否有协议名。还要验证连接到的文件是否真正存在。
use File::Basename; $n = doitem sub { my ( $file, $tree ) = @_; return $tree->look_down( "_tag" => "a", "href" => qr//, sub { !( $_[0]->attr("href") =~ /^ *(http:|https:|ftp:|mailto:)/) and -e dirname($file) . "/" . decodeURL( $_[0]->attr("href") ); } ); }; |
nvu通过指定body标签中的style的颜色属性来改变页面的字体颜色,就像:<body style="color: rgb(0, 0,255);">。下面代码使用一个正则去匹配style属性并且取回三个颜色的值,任何不为零的颜色值就表示页面不是默认的颜色。
my $pattern = qr/(?:^|[^-])color *: *rgb/(( *[0-9]*),( *[0-9]*),( *[0-9]*)/)/; return $tree->look_down( "_tag" => "body", "style" => qr//, sub { $_[0]->attr("style") =~ $pattern and ( $1 != 0 or $2 != 0 or $3 != 0 ); } ); |
合理的使用look_down(),look_up(),as_trimmed_text()等函数,我们的代码可以定位和标记出很html元素(图片,内部链接,背景图片等等)的存在。
3,完成
制作网页的最后的要求是页面外观的美观,遗憾的是,HTML::TreeBuilder或任何相关的模组都不能帮助完成对这个要求的评分,所以,本文的作者只能自己手动给最后一个要求评分。但是他有希望用perl在这项工作上帮点忙,所以就有了下面的一段代码:
my $input = ""; do { print "$str1 [$str2]: "; $input = <STDIN>; $input =~ s/(^/s+|/s+$)//g; } until ( $input =~ /(.*/./s+/d+$|^/s*$|^/d+$)/ ); $input = $str2 if $input eq ""; if ( $input =~ /^/d+$/ ) { $n = $input; if ( $n == 10 ) { $input = "good looking, nice content. $n"; } else { ( $input = $str2 ) =~ s/(/./s*)/d+/s*$/$1$n/; } } marking "$str1 $input"; |
最后,perl代码为每一个要求标记出包含老师的注释和评分的文本并且计算出整个作业的总成绩。
my ( $total, $score ) = ( 0, 0 );
while ( $marktext =~ /.*?/((/d+)/s+points/).*?/./s+(/d+)/g )
{
$total += $1;
$score += $2;
}
marking "Total ($total points): $score";