用mod_rewrite解决网站旧链接404错误

前两天收到谷歌搜索团队发来的网站监控报告,称其智能手机爬虫识别出我网站上不可访问的链接数量正逐渐增加[1],建议我为了移动用户的网站访问体验优化网站配置、根据实际情况进行修正。虽然我不是一个手机控(甚至恰相反,我并不喜欢在手机等移动端完成工作),但一来考虑到网站目标用户中有不少是手机重度使用者,二来对技术的偏好让我不希望在网站运营上留下瑕疵,我还是决定查看一番,究竟有哪些链接不可访问,以及为何数量突然骤增。

谷歌搜索报告

打开详细报告,发现桌面端的访问并无死链,而移动端自二月中旬始突然产生大量的失效链接,根据统计到2月20号已达799起!仔细查看了一下具体的爬虫记录,发现有个别的确是过期文件,无必要保存而删除的,但大多数却是过去多年被谷歌搜索引擎收录的实际存在过的网页,只不过它们在日常维护中由于网站升级、内容更新等原因发生了变化,比如论坛程序变更导致的动态链接变化、课件更新导致的网站目录变化、项目终结导致的页面移除等等。

谷歌搜索控制台站点错误信息

尚未弄清为何谷歌智能手机爬虫会在近期突然高频度访问我的网站,但既然提示有这些失效的链接,说明网上存在着对这些失效链接所对应资源的需求,那么还是尽力修整一番为好。

由于位于Buffalo机房原主机上Nginx Web Server的启动问题未能彻底修复,网站就迁移到了洛杉矶机房一台配置相对较高的主机上。我在这台服务器上安装的是Apache HTTP Server,在应付网址重写方面相当出色,于是就顺势祭出mod_rewrite模块来尝试完成死链变活链的任务。

除一些静态网页,这数百个失效链接还涉及到许多动态网页,综合归纳起来无非就几种模式,我挑几个例子集中说明。

先看第一个例子。

麻省理工中文化项目页面移除

麻省理工中文化项目是多年前我参与的分别由中国大陆教育部、CORE基金会和台湾OOPS协助美国麻省理工学院进行的一个志愿服务项目,该项目约十年前随着CORE基金会的解散和OOPS的朱学恒先生隐退而渐淡出人们的视野,我随后也不再维护运营网站上的这个栏目,仅在论坛保留着相应的版块和旧时的贴子,服务器上的mitocw目录后来随着网站整体改版而移除。从谷歌搜索引擎的报告来看,需要将对该目录及目录下文件的访问引流至论坛相应的版块上去。

https://bizedu.net/mitocw/mitocw_local.html这个过期页面为例,当访问者访问该页面时,Apache HTTP Server应当作出回应,将https://bbs.bizedu.net/viewforum.php?f=6这个论坛版块的URL发送至该访问者的浏览器端。在Apache配置文件的VirtualHost block,可如下添加重写规则。

1
2
3
4
RewriteEngine On
# 以上一行用于激活rewrite,此外Apache的主配置文件中必须事先加载rewrite_module,否则以下重写规则不生效
RewriteCond "%{HTTP_HOST}" "(|www\.)bizedu.net"
RewriteRule "^/mitocw/?(.*)" "https://bbs.bizedu.net/viewforum.php?f=6" [L,R=301,NE]

^/mitocw/?(.*)中的caret(^)意味着mitocw目录存放在服务器网站根目录下,/?(.*)中的问号意味着访问路径可能带斜杠或不带,即mitocw/mitocw,其后的括号及括号中的句点和星号意味着其它可能的字符。这样一来,无论是对https://bizedu.net/mitocwhttps://bizedu.net/mitocw/mitocw_local.html还是更深一级目录下的文件如https://bizedu.net/mitocw/MitOcwPartyDemo/absent.ppt的访问都会被引流至https://bbs.bizedu.net/viewforum.php?f=6

第二个例子是有关原页面移除后内容更新到其它目录位置的。十年前为公共管理专业学生讲授人力资源专业英语课程时,该课程的在线课程资源首页地址是https://bizedu.net/courses/hrm-eng/default.html,几个学期后课程名称变更为专业英语,与财务专业和营销专业的商务英语整合,现在的课程地址为https://bizedu.net/bizeng,因而就要将失效的原地址导至如今的地址上来。如前例,重写规则可如下添加。

人力资源管理英语课程页面转移

1
2
RewriteCond "%{HTTP_HOST}" "(|www\.)bizedu.net"
RewriteRule "/hrm-eng(|/.*)" "https://bizedu.net/bizeng/" [L,R=301,NE]

与前一个例子不同的是,在RewriteRule后的规则中,/hrm-eng(|/.*)前方并未增加 ^ 符号,原因是hrm-eng的目录在网站改版过程中曾由根目录转移至courses目录下,若照先例添加了caret符号,那么对https://bizedu.net/courses/hrm-eng/default.html的访问将不能导至https://bizedu.net/bizeng来。

论坛贴子地址的转移

第三个例子显然比前两个要麻烦,我要处理的是动态页面的重写。

十五年前在国内刚为自己的网站注册域名时,国内无良恶心的域名注册商几乎把持着一切权限,甚至包括二级域名的指向,因而那会儿建立论坛时没有二级域名(subdomain)如bbs.bizedu.net,只能放在主域名的子目录(subdirectory)下,如https://bizedu.net/forums。后来,又经历了服务商变更[2]、论坛程序更换[3]等糟心事儿。过去的历史遗留问题带来的结果就是我要把诸如https://bizedu.net/forums/index.php?showtopic=8这样的地址变更为https://bbs.bizedu.net/viewtopic.php?t=8这样的地址,也就是先进行从subdirectory到subdomain的重写,再把“小尾巴”访问路径及查询字符串升级到新的形式。完成这部分任务需要了解一些比如正则表达Apache算式解析式参数Rewrite标记符以及Apache mod_rewrite的知识。这方面可利用的参考资源也不少,比如:

不过,我仍旧花了两三天才大体完成这项工作中的主要部分,因时间与能力的限制以及专业知识的局限,无法做更深层面的探索了。多次尝试和测试、调整后,我使用了以下几行代码:

1
2
3
RewriteCond "%{HTTP_HOST}" "(|www\.)bizedu.net"
RewriteCond "%{QUERY_STRING}" "showtopic=([0-9]*)"
RewriteRule "/forums/index.php" "https://bbs.bizedu.net/viewtopic.php?t=%1" [L,R=301,NE]

测试期间发现,以下代码也有同样效果:

1
2
3
4
RewriteCond "%{HTTP_HOST}" "(|www\.)bizedu.net"
RewriteCond "%{REQUEST_URI}" "/forums/index.php"
RewriteCond "%{QUERY_STRING}" "showtopic=([0-9]*)"
RewriteRule "" "https://bbs.bizedu.net/viewtopic.php?t=%1" [L,R=301,NE]

不过,第三个例子的问题仍未完全解决,因为当访问者直接访问https://bizedu.net/forums这个地址,也就是不带查询符时,就需要做另外的设定。因此,在上面代码后,继续添加两条重写规则。

论坛子目录到二级域名的转移

1
2
RewriteCond "%{HTTP_HOST}" "(|www\.)bizedu.net"
RewriteRule "/forums(|/.*)" "https://bbs.bizedu.net/$1" [L,R=301,NE]

其它的失效链接大体都能根据以上三个例子做相应的重写设置。这样,Apache HTTP Server是忙了些,但我就无须头疼于那近八百条的失效链接记录的逐一处理工作了。

Update@Feb 27, 2019

Buffalo机房原主机上Nginx Web Server的启动故障似乎与几条Header代码有关,但又不想贸然comment掉,只得尝试用Debian代替了CentOS。Debian的表现出乎意料,新编译好的Nginx不但没有了启动故障,就连PID文件找不到的老问题也消失了,更难能可贵的是,它的资源消耗表现似强于CentOS。

迁回网站的一个副作用就是无法在Nginx上全面部署Apache的重写规则,因而以上文章中涉及到的个别实例就失效了。


  1. Googlebot for smartphones identified a significant increase in the number of URLs on https://bizedu.net/ that return a 404 (not found) error ↩︎

  2. 维护论坛那些年中,因资源和技术,特别是财力精力有限,曾将论坛放到一家免费论坛提供商处托管,后因其强制转换政策造成中文贴子乱码灾难而无法挽回损失,在升级无望的情况下只能更换论坛程序。 ↩︎

  3. 论坛程序更换本身并不算难,特别是主流的PHP论坛程序如Xenforo、phpBB、vBulletin、IPB、Vanilla、Discuz!如同Windows下的工具软件一般,几乎是点数下鼠标就能完成安装、升级、转换等操作,但难点在于各家论坛程序其动态部分也即%{QUERY_STRING}的部分不一样,导致原来的贴子和版块地址与新论坛的不同,这就会导致访问链接失效。 ↩︎