PHP匹配变音使404页面更加智能化


创建您自己的 404 错识消息处理程序,为站点内容提供有用的链接和重定向。使用变音匹配(metaphone matching)和一个简单的加权记分文件为输入错误、拼写错误和无效链接生成重定向建议。根据 Web 站点的内容和首选重定向位置定制建议。捕获传入 URL 请求中的各种错误,并通过处理纠正其中的目录、脚本和 HTML 页面名称错误。

关于如何为 404 页面创建有效格式的教程比比皆是。这类教程大多建议在 404 页面中包含静态的建议链接,并将这些链接指向站点的公共区域,比如说首页、下载页面和站点的搜索引擎,前提是要有这些页面。404 页面普遍存在的问题是它们无法反映用户访问该站点的目的。本文将介绍如何构建一个建议生成器和一个方法,用于根据 Web 站点的内容提供更加有用的重定向链接。

现行的 404 处理程序允许我们为各种错误提供一些建议链接,比如说将用户指向站点目录。一些拼写校正程序(比如说 mod_speling ——— 没错,它只有一个 “l”)可用于纠正词典单词中的错误,从而将用户定向到正确的页面。本文中的代码将帮助您构建一个建议生成引擎,它可以根据 Web 站点的内容来处理在词典中无法找到的单词和目录链接。

我们考虑这样一个场景:您在电话会议中听到了一个 Web 页面名称,因此便尝试打开 blegs/DavSmath.html 链接。现行的拼写校正模块无法为此情况提供一个有用的链接。使用本文中的代码,您将能够生成一个 404 页面,并在其中显示建议的有效页面 /blogs/DaveSmith.html。

需求

本世纪生产的任何现代 PC 应该都足以编写和运行本文中的代码。如果您的 Web 页面含有超过 10,000 个不同的页面,那么可能需要大容量的内存、高性能的硬件或足够的耐心。

所提供的 Perl 和 CGI 脚本可以在多种 UNIX® 和 Windows® 平台上运行(请参阅 下载部分。虽然本文将使用 Apache 和一个 CGI 脚本作为建议引擎,但是所构建的工具应该能够在大多数 Web 服务器上正常运行。对于变音匹配,本文将引用 Michael Schwern 编写的 Text::Metaphone 模块。在开始之前,先通过喜好的 CPAN 镜像安装 Text::Metaphone 模块。请参阅 参考资料 获得下载信息。

Web 服务器页面和变音代码

针对输入和拼写错误提供替代建议的主要方法为变音匹配。与 Soundex 语音算法和一些其他算法类似,Metaphone 使用字母数字代码表示单词的发音。但是,与 Soundex 语音算法有所不同,构建语音代码的目的是匹配英文发音的语言可变性。因此,变音代码通常能够更加准确地表示特定的单词,并且为建议库的构建提供了理论基础。

考虑示例 Web 服务器目录中的下列文件。


清单 1. Web 服务器文件
               

以下为引用的内容:
./index.html
./survey.html
./search_tips.html
./about.html
./how.html
./why.html
./who.html
./NathanHarrington.html
./blogs/NathanHarrington.html
./blogs/DaveSmith.html
./blogs/MarkCappel.html

针对这些静态 HTML 文件,我们将使用 buildMetaphoneList.pl 程序为所有扩展名为 .html 的文件创建变音。


清单 2. buildMetaphoneList.pl
               

以下为引用的内容:
#!/usr/bin/perl -w
# buildMetaphoneList.pl - / split filename, 0 score, metaphones

use strict;
use File::Find;
use Text::Metaphone;

find(\&htmlOnly,".");

sub htmlOnly
{
  if( $File::Find::name =~ /\.html/ )
  {
    my $clipFname = $File::Find::name;
    $clipFname =~ s/\.html//g;

    my @slParts = split '/', $clipFname;
    shift(@slParts);

    print "$File::Find::name ### 0 ### ";
    for( @slParts ){ print Metaphone($_) . " " }
    print "\n";

  }#if a matching .html file

}#htmlOnly sub

buildMetaphoneList.pl 程序只能处理扩展名为 .html 的文件,它将移除文件名中的 .html,然后为完整路径名称的各个部分生成变音。将 buildMetaPhoneList.pl 程序复制到 Web 服务器的根目录下,然后运行命令 perl buildMetaphoneList.pl > metaphonesScore.txt。对于清单 1 中的文件,相应的 metaphonesScore.txt 文件内容如清单 3 所示。

清单 3. metaphonesScore.txt
               

以下为引用的内容:
./index.html ### 0 ### INTKS
./survey.html ### 0 ### SRF
./search_tips.html ### 0 ### SRXTPS
./about.html ### 0 ### ABT
./how.html ### 0 ### H
./why.html ### 0 ### H
./who.html ### 0 ### H
./NathanHarrington.html ### 0 ### N0NHRNKTN
./blogs/NathanHarrington.html ### 0 ### BLKS N0NHRNKTN
./blogs/DaveSmith.html ### 0 ### BLKS TFSM0
./blogs/MarkCappel.html ### 0 ### BLKS MRKKPL

清单 3 中的每一行文字都显示了 Web 服务器根目录下的实际链接、默认作用域和变音代码。注意,how.html、 why.html 和 who.html 都解析为了相同的变音代码。要解决这个不明确的地方,需要修改作用域字段,让链接建议程序以指定的顺序向页面提供链接。比如说,将 “H” 变音条目修改为:

以下为引用的内容:
./how.html ### 100 ### H
./why.html ### 50 ### H
./who.html ### 0 ### H

这样将创建一个直观的链接重排序,并留下空间用于作用域的进一步修改。作用域的数字越大,插入同一变音文件(不过是不同的作用域)的顺序就越靠后。比如说添加一个作用域为 25 的 hoo.html 文件列表,那么它将位于 who.html 条目之上和 why.html 条目之下。

您还可以使用作用域字段区分目录不同而名称相同的文件。比如说,将 ./NathanHarrington.html 一行的的作用域修改为 100,那么类似 nathenHorrington.html 这样的请求会将 ./NathanHarrington.html 链接列在 ./blogs/NathanHarrington.html 页面之前。

选择文件的作用域时,务必要考虑 Web 站点的统计和逻辑访问组件。从日志文件可以看出,用户对 why.html 页面的请求比较频繁,但是如果您认为 how.html 对于用户更为重要,那么只需修改相应的作用域值对排序做出纠正。

构建 CGI 404 处理程序

我们已经生成了适当的变音并为它们指定了相关的作用域值,下一步将构建实际的建议生成器。通常,404 错误消息的原因为链接输入错误或链接本身的问题。以下代码生成的建议将通过以下三个主要测试创建:根据目录结构匹配、使用变音组合匹配,以及当其他方法失败时使用 “包含” 匹配。这三种测试的设计目的是处理大多数 404 错误。MetaphoneSuggest CGI Perl 脚本的开始部分如下所示。

清单 4. MetaphoneSuggest CGI 第 1 部分
               

以下为引用的内容:
#!/usr/bin/perl -w
# MetaphoneSuggest - suggest links for typographical and other errors from 404s
use strict;
use CGI::Pretty ':standard';  #standard cgi stuff
use Text::Metaphone;
 
my @suggestLinks = (); # suggested link list
my %mt = ();           # filename, score, metaphone code hash

my $origLink = substr($ENV{REDIRECT_URL},1); # remove leading /
$origLink  =~ s/\.html//g;                   # remove trailing .html

open(MPH,'metaphonesScore.txt') or die "can't open metaphones";
  while(my @slPart = split '###', <MPH>)
  {
    $slPart[0] =~ s/ //g; #remove trailing space
    $mt{$slPart[0]}{ score } = $slPart[1];
    $mt{$slPart[0]}{ metaphones } = $slPart[2];
  }
close(MPH);

代码首先引入了一些常用库并声明了一些变量,然后将加载 404 报告文本和通过 buildMetaphoneList.pl 程序创建的变音。这时,我们可以开始编写主要的程序逻辑了,如下所示。

清单 5. 主要程序逻辑
               

以下为引用的内容:
push @suggestLinks, sortResults( directorySplitTest( $origLink ) );
push @suggestLinks, sortResults( combinedTest( $origLink ) );
push @suggestLinks, sortResults( containsTest( $origLink ) );

# from the book - unique-ify the array
my %seen = ();
@suggestLinks = grep{ ! $seen{$_}++ } @suggestLinks ;

print header;
print qq{Error 404: The file requested [$ENV{REDIRECT_URL}] is unavailable.<BR >};
next if( @suggestLinks == 0 );

print qq{Please try one of the following pages:<BR >};
for my $link( @suggestLinks ){
  $link = substr($link,index($link,'./')+1);
  print qq{<a href="$link">$link</a><BR >};
}

首先,对匹配测试各部分的输出进行排序,然后将其添加到总建议链接列表。对链接列表进行排序和惟一化(unique-ifying)之后,将建议链接直接打印输出。

三个排序命令将结果保存在同一个数组中,目的是创建一个有序的建议列表。发生 404 错误时,目录树中(至少第一级目录)极有可能会出现目录分隔符(用于表示 Web 页面)。比如说,以 bloggs/nathenherringtoon.html 页面请求为例。上述代码中所调用的 directorySplitTest 方法将创建一个排序的页面列表,BLKS 和子目录 N0NHRNKTN 的变音匹配都将包含在该列表中。这一策略可用于区分根目录中的文件(如 blogs.html 和 nathanharrington.html)和完整路径名匹配的页面(如 blogs/nathanharrington.html)。下面的清单显示了 directorySplitTest 子例程的内容。

清单 6. directorySplitTest subroutine
               

以下为引用的内容:
sub directorySplitTest
{
  my @matchRes = ();
  my $inLink = $_[0];
  for my $fileName ( keys %mt )
  {
    my @inLinkMetas = ();
    # process each metaphone chunk as a directory
    for my $inP ( split '\/', $inLink ){ push @inLinkMetas, Metaphone($inP) }

    my @metaList = split ' ', $mt{$fileName}{metaphones};
    next if( @metaList != @inLinkMetas );

    my $pos = 0;
    my $totalMatch = 0;
    for( @metaList )
    {
      $totalMatch++ if( $metaList[$pos] =~ /(\b$inLinkMetas[$pos]\b)/i );
      $pos++;
    }#for meatlist

    # make sure there is a match in each metaphone chunk
    next if( $totalMatch != @metaList );
    push @matchRes, "$mt{$fileName}{score} ## $fileName";

  }#for keys in metaphone hash

  return( @matchRes );

}#directorySplitTest

组合测试位于 directorySplitTest 之后,用于检查变音混和在一起时的匹配情况 — 忽略任何目录结构。该测试用于纠正 404 类错误,即文件名中含有空格、斜杠、反斜杠、冒号和其他一些无发音的字符。比如说,如果针对 blogs_nathanherrington.html 发出一个 404 请求,那么 directorySplitTest 将返回零结果,但是 combinedTest 将发现该 404 产生的变音组合在一起是 blogs/NathanHarrington.html 页面的准确匹配。同样,这些建议的优先级低于目录匹配,因此这些分类结果将在 directorySplitTest 之后存入 suggestLinks 数组。以下清单显示了 combinedTest 子例程。

清单 7. combinedTest 子例程
               

以下为引用的内容:
sub combinedTest
{
  my @matchRes = ();
  my $inLink = $_[0];
  for my $fileName ( keys %mt )
  {
    my $inLinkMeta = Metaphone($inLink);

    # smoosh all of the keys together, removing spaces and trailing newline
    my $metaList =  $mt{$fileName}{metaphones};
    $metaList =~ s/( |\n)//g;

    next if( $metaList !~ /(\b$inLinkMeta\b)/i );
    push @matchRes, "$mt{$fileName}{score} ## $fileName";
  }#for filename keys in metaphone hash

  return(@matchRes);

}#combinedTest

在 combinedTest 之后是最后一个匹配测试,该测试基于一个广度包含搜索。如果当前的 404 链接的变音是 metaphoneScores.txt 文件中可用变音的一部分,我们将把它添加到建议列表。包含搜索的设计目的是寻找内容极度不完整的 URL。nathan.html 页面在任何位置都无法找到,但是一个良好的建议应该是 /NathanHarrington.html 和 /blogs/NathanHarrington.html,并且它们根据作用域值排序并添加到 suggestLinks 数组中。注意,此方法还将为单字母变音 404(如 whoo.html)生成 NathanHarrington.html 建议。由于 NathanHarrington.html 变音中含有一个 “H”,故将其添加到建议列表。考虑创建一个最小长度的匹配变音,或提供一个包含总数受限的匹配,以修改这一行为。清单 8 显示了 containsTest 和 sortResults 子例程。

清单 8. sortResults 和 containsTest 子例程
               

以下为引用的内容:
sub sortResults
{
  # simply procedue to sort an array of 'score ## filename' entries
  my @scored = @_;
  my @idx = (); #temporary index for sorting
  for my $entry( @scored ){
    # create an index of scores
    my $item =  substr($entry,0,index($entry,'##'));
    push @idx, $item;
  }
 
  # sort the index of scores
  my @sorted = @scored[ sort { $idx[$b] <=> $idx[$a] } 0 .. $#idx ];
 
  return( @sorted );
 
}#sortResults

sub containsTest
{
  my @matchRes = ();
  my $inLink = $_[0];
  for my $fileName ( keys %mt )
  {
    my $inLinkMeta = Metaphone($inLink);
    my $metaList =  $mt{$fileName}{metaphones};
    next if( $metaList !~ /$inLinkMeta/i );
    push @matchRes, "$mt{$fileName}{score} ## $fileName";
  }#for filename keys in metaphone hash
  return(@matchRes);
}#containsTest

修改 Apache httpd.conf 文件

上面所设计的 MetaphoneSuggest 脚本是一个将从 Apache 中直接调用的 cgi-bin 脚本。要运行 MetaphoneSuggestscript 脚本,我们需要对 httpd.conf 文件进行适当修改,否则将显示 404 错误页面。比如说,如果默认的 httpd.conf 文件含有以下部分:

清单 9. 默认 httpd.conf 部分
               

以下为引用的内容:
# Customizable error responses come in three flavors:
# 1) plain text 2) local redirects 3) external redirects
#
# Some examples:
#ErrorDocument 500 "The server made a boo boo."
#ErrorDocument 404 /missing.html
#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
#ErrorDocument 402 http://www.example.com/subscription_info.html

在注释掉的 ErrorDocument 代码行之后插入如下代码:ErrorDocument 404 "/cgi-bin/MetaphoneSuggest"。确保 MetaphoneSuggest 和 metaphonesScore.txt 文件位于 Web 服务器的 <document_root</cgi-bin/ 目录下。以根用户身份发起服务器重启命令:例如 /usr/local/apache2/bin/apachectl restart,至此灵活的建议机制将彻底结束笨拙的 404 错误。

结束语
 
记住,使用 MetaphoneSuggest 程序中所描述的工具时,错误条件必须为 404 页面。试着提供少量建议的选择并保持设计的简单性。请教 Web 设计方面的知名人士,了解他们为什么没有提供自动链接建议,或者开展各种可用性研究,了解何种情况下最适合在站点中实现链接建议工具。

本文提供了各种工具和代码,用于在 404 页面中创建实用的链接建议。但是,这些示例都已经实现,您可以通过它们提供一些复杂的功能,而不仅仅是简单的目录链接或拼写建议。通过对特定站点和内容进行调整,笨拙的 404 错误将不复存在。


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3