使用 Perl 检查网络链接

作者:Jim Weirich

当我第一次拥有互联网账户时,我做的第一件事之一就是创建我自己的网页。我收到最多评论的网页叫做“Weirichs on the Web”,我在其中链接到我在网上找到的其他 Weirichs。虽然这很有趣,但保持链接更新可能非常乏味。当我引用的网页被移动或删除时,指向它们的链接就会失效。如果不经常检查,就很难保持我的链接是最新的。

因此,我开始思考,有没有一种方法可以自动查找网页中过时的链接?我需要的是一个脚本,它可以扫描我的所有网页,并报告每个无效的 HTML 链接以及它所在的网页。

这个问题有几个部分。我们的脚本必须能够

  • 从 Web 获取网页文档

  • 从网页文档中提取 URL 列表

  • 测试 URL 以查看其是否有效

LWP 库

我们可以手动编写代码来提取 URL 并验证它们,但有一种更简单的方法。LWP 是一个 Perl 库(可以从任何 CPAN 存档站点获得),旨在使在 Perl 中访问万维网变得非常容易。LWP 使用 Perl 对象为客户端提供与 Web 相关的服务。Perl 对象是 Perl 语言的最新添加,许多人可能不熟悉它们。

Perl 对象是对“事物”的引用,这些“事物”知道它们属于哪个类。这些“事物”通常是匿名哈希,但 您不需要知道这一点即可使用对象。 类是提供对象用来实现其行为的方法的包。最后,方法是一个函数(在类包中),它期望一个对象引用(或有时是一个包名称)作为其第一个参数。

如果这听起来令人困惑,请不要担心。使用对象非常容易。LWP 定义了一个名为 HTTP::Request 的类,它表示要在 Web 上发送的请求。可以使用以下语句创建 GET 位于 URL http://w3.one.net/~jweirich 的文档的请求

$req = new HTTP::Request GET,
 'http://w3.one.net/~jweirich';

new 创建一个新的 Request 对象,该对象使用 GET 和 http://w3.one.net/~jweirich 参数初始化。这个新对象被分配给 $req 变量。

调用对象的成员函数同样简单明了。例如,如果您想检查此请求的 URL,您可以在此对象上调用 url 方法。

print "The URL of this request is:
", $req->url, ",\n";

请注意,方法是使用 -> 语法调用的。C++ 程序员应该对此感到熟悉。

获取文档

关于通过 Web 获取文档的所有知识都存储在 UserAgent 对象中。UserAgent 对象知道等待响应的时间、如何处理错误以及文档到达时如何处理文档。它完成了所有繁重的工作——我们只需要给它正确的信息,以便它可以完成它的工作。

use LWP::UserAgent;
use HTTP::Request;
$agent = new LWP::UserAgent;
$req = new HTTP::Request ('GET',
 'http://w3.one.net/~jweirich/');
$agent->request ($req, \&callback);

这段 Perl 代码片段创建了一个 UserAgent 和一个 Request 对象。UserAgent 的 Request 方法发出请求,并使用来自到达文档的数据块调用名为 callback 的子例程。callback 子例程可能会被多次调用,直到收到完整文档。

解析文档

我们可以使用正则表达式来解析传入的文档,以确定所有链接的位置,但是当您开始考虑 HTML 标签可能跨越多行以及涉及的所有细微变化时,这会变得更加困难。幸运的是,LWP 库中有一个 HTML 解析对象,称为 HTML::LinkExtor,它可以从 HTML 文档中提取所有链接。

创建解析器,然后将文档片段馈送给它,直到到达文档末尾。每当解析器检测到嵌入在 HTML 标签中的链接时,它都会调用我们提供的另一个回调子例程。这是一个提取和打印文档中所有链接的示例。

use HTML::LinkExtor
$parser = new HTML::LinkExtor (\&LinkCallback);
$parser->parse ($chunk);
$parser->parse ($chunk);
$parser->parse ($chunk);
$parser->eof;
sub LinkCallback {
    my ($tag, %links) = @_;
    print join ("\n", values %links), "\n";
}
整合

现在我们拥有构建 checklinks 脚本所需的所有工具。我们将为 URL 定义两个操作。当我们扫描 URL 时,我们将获取文档(使用 UserAgent)并扫描它以查找内部 HTML 链接。我们找到的每个新链接都将添加到要检查的 URL 列表中。

接下来,检查链接以查看它是否指向有效的网页文档。我们可以尝试检索整个文档以查看文档是否存在,但 HTTP 协议定义了一个 HEAD 请求,该请求仅获取文档的日期、长度和一些其他属性。由于对于大型文档,HEAD 请求可能比完整的 GET 快得多,并且由于它告诉我们我们需要知道的内容,我们将使用 LWP::Simple 包的 head() 函数来检查 URL。如果 head() 返回未定义的值,则无法获取 URL 指定的文档,我们将 URL 添加到坏 URL 列表中。如果 head() 返回列表,则 URL 有效,并将其添加到好 URL 列表中。最后,如果有效的 URL 指向我们本地 Web 空间中的页面并以“.html”或“.htm”结尾,我们将 URL 添加到要扫描的 URL 列表中。

扫描过程会产生更多要检查的 URL。检查这些 URL 会产生更多需要扫描的 URL。在检查 URL 时,它们会被移动到好列表或坏列表。由于我们将扫描限制在我们本地 Web 空间中的 URL,因此最终我们将扫描从我们的起始文档可访问的所有本地 URL。

当没有更多 URL 要扫描并且所有 URL 都已检查完毕时,我们可以打印坏 URL 列表和包含它们的文件的列表。

结果

checklinks 的完整代码可以在 列表 1 中找到。您需要 Perl 5 才能运行 checklinks 例程。您还需要 LWP 库的最新副本。当我安装 LWP 时,我还必须更新 IO 和 Net 模块。您可以在 https://perldotcom.perl5.cn/perl 找到 Perl 以及 LWP、IO 和 Net 模块。

您可以使用以下命令在单个 URL 上调用 checklinks

checklinks url

如果您希望扫描从主 URL 可访问的所有本地 URL,请添加 -r 选项。

在我的家庭系统上针对我的整套网页运行 checklinks 大约需要 13 分钟才能完成。大部分时间都花在等待坏 URL 超时上。它扫描了 76 个页面,检查了 289 个 URL,并发现了 31 个不再有效的链接。现在我所要做的就是找到时间来清理我的网页!

Jim Weirich 是 Compuware 的软件顾问,专门从事 Unix 和 C++。当他不忙于他的网页时,您可以发现他在弹吉他、陪孩子玩耍或玩 Linux。欢迎在 jweirich@one.net 提出意见或访问 http://w3.one.net/~jweirich。

加载 Disqus 评论