从测试300万个超链接接学到的

开发 测试
Stack Exchange上有超过三百万个不同的链接。经过很长时间,许多链接已经不能用了。最近我花时间编写了一个工具,它能判断哪些是坏链,能帮助我们来修复坏链。

Stack Exchange上有超过三百万个不同的链接。经过很长时间,许多链接已经不能用了。

最近我花时间编写了一个工具,它能判断哪些是坏链,能帮助我们来修复坏链。

我们是怎么做的?

首先,我们要对他人的网站心存敬意。

做一个好的网民

● 对每个域名限制请求

我们采用自动过期的set,来确保十秒钟内对单个域名不会请求多过一次。当我们觉得需要对某些链接进行更多的测试时,我们也做了特殊处理。

  1. public class AutoExpireSet<T>  
  2. {  
  3.    
  4.     Dictionary<T, DateTime> items = new Dictionary<T, DateTime>();  
  5.     Dictionary<T, TimeSpan> expireOverride =  
  6.          new Dictionary<T, TimeSpan>();  
  7.    
  8.     int defaultDurationSeconds;  
  9.    
  10.     public AutoExpireSet(int defaultDurationSeconds)  
  11.     {  
  12.         this.defaultDurationSeconds =  
  13.            defaultDurationSeconds;  
  14.     }  
  15.    
  16.     public bool TryReserve(T t)  
  17.     {  
  18.         bool reserved = false;  
  19.         lock (this)  
  20.         {  
  21.             DateTime dt;  
  22.             if (!items.TryGetValue(t, out dt))  
  23.             {  
  24.                 dt = DateTime.MinValue;  
  25.             }  
  26.    
  27.             if (dt < DateTime.UtcNow)  
  28.             {  
  29.                 TimeSpan span;  
  30.                 if (!expireOverride.TryGetValue(t, out span))  
  31.                 {  
  32.                     span =  
  33.                      TimeSpan.FromSeconds(defaultDurationSeconds);  
  34.                 }  
  35.                 items[t] = DateTime.UtcNow.Add(span);  
  36.                 reserved = true;  
  37.             }  
  38.    
  39.         }  
  40.         return reserved;  
  41.     }  
  42.    
  43.     public void ExpireOverride(T t, TimeSpan span)  
  44.     {  
  45.         lock (this)  
  46.         {  
  47.             expireOverride[t] = span;  
  48.         }  
  49.     }  

● 健壮的验证函数

我们的验证函数包括了许多我认为非常重要的概念。

  1. public ValidateResult Validate(  
  2.       bool useHeadMethod = true,  
  3.       bool enableKeepAlive = false,  
  4.       int timeoutSeconds = 30 )  
  5. {  
  6.     ValidateResult result = new ValidateResult();  
  7.    
  8.     HttpWebRequest request = WebRequest.Create(Uri)  
  9.                                   as HttpWebRequest;  
  10.     if (useHeadMethod)  
  11.     {  
  12.         request.Method = "HEAD";  
  13.     }  
  14.     else 
  15.     {  
  16.         request.Method = "GET";  
  17.     }  
  18.    
  19.     // always compress, if you get back a 404 from a HEAD  
  20.     //     it can be quite big.  
  21.     request.AutomaticDecompression = DecompressionMethods.GZip;  
  22.     request.AllowAutoRedirect = false;  
  23.     request.UserAgent = UserAgentString;  
  24.     request.Timeout = timeoutSeconds * 1000;  
  25.     request.KeepAlive = enableKeepAlive;  
  26.    
  27.     HttpWebResponse response = null;  
  28.     try 
  29.     {  
  30.         response = request.GetResponse() as HttpWebResponse;  
  31.    
  32.         result.StatusCode = response.StatusCode;  
  33.         if (response.StatusCode ==  
  34.                    HttpStatusCode.Redirect ||  
  35.             response.StatusCode ==  
  36.                    HttpStatusCode.MovedPermanently ||  
  37.             response.StatusCode ==  
  38.                    HttpStatusCode.SeeOther ||  
  39.             response.StatusCode ==  
  40.                    HttpStatusCode.TemporaryRedirect)  
  41.         {  
  42.             try 
  43.             {  
  44.                 Uri targetUri =  
  45.                   new Uri(Uri, response.Headers["Location"]);  
  46.                 var scheme = targetUri.Scheme.ToLower();  
  47.                 if (scheme == "http" || scheme == "https")  
  48.                 {  
  49.                     result.RedirectResult =  
  50.                         new ExternalUrl(targetUri);  
  51.                 }  
  52.                 else 
  53.                 {  
  54.                     // this little gem was born out of  
  55.                     //   http://tinyurl.com/18r  
  56.                     //   redirecting to about:blank  
  57.                     result.StatusCode =  
  58.                            HttpStatusCode.SwitchingProtocols;  
  59.                     result.WebExceptionStatus = null;  
  60.                 }  
  61.             }  
  62.             catch (UriFormatException)  
  63.             {  
  64.                 // another gem ... people sometimes redirect to  
  65.                 //    http://nonsense:port/yay  
  66.                 result.StatusCode =  
  67.                     HttpStatusCode.SwitchingProtocols;  
  68.                 result.WebExceptionStatus =  
  69.                     WebExceptionStatus.NameResolutionFailure;  
  70.             }  
  71.    
  72.         } 

● 从***天开始就设置正确的User Agent字符串

如果什么地方出错了,你希望他人能够联系到你。我们的链接爬虫的user agent字符串为: Mozilla/5.0 (compatible; stackexchangebot/1.0; +http://meta.stackoverflow.com/q/130398)。

● 处理302, 303, 307等页面跳转

尽管302和303跳转非常常见,307却不多见。它被作为一种针对浏览器的错误表现的解决方法被引入,解释见此处

307***的例子是http://www.haskell.org。我非常不赞同在首页就跳转地做法,URL重写以及其他的工具可以解决这个问题,而不需要有多余的跳转;但是,首页跳转仍旧存在。

当你跳转时,你需要继续测试。我们的链接测试机会测试最多五层。你需要设置层次上限,否则你会陷入无限循环。

跳转有时很奇怪,网站有时会把你导向到about:config或一个不存在的URL。检验跳转的页面信息很重要。

● 当你获得所需要的信息时,请及时中断请求

在TCP协议中,包收到时,特殊的状态会被标记。当客户端发送给服务器的包中标记了FIN的话,连接会早早的中止。调用request.Abort你可以避免在404时从服务器端下载大量数据。

当测试链接时,你经常需要避免HTTP keepalive。因为我们的测试机没必要给服务器造成不必要得连接负担。

中断可以减少压缩,但我非常赞成启用压缩。

● 先使用HEAD请求,再用GET请求

一些服务器不使用HEAD。例如,Amazon完全禁止了,对HEAD请求返回405。在ASP.NET MVC中,人们经常显式设置路由经过的verb属性。程序员们在规定使用HttpVerbs.Get时往往没有使用HttpVerbs.Head。所以当你失败时(没有获得200响应),你需要重新使用GET verb来测试。(译者:这一段不是很懂,如有错误请指正。)

● 忽略robots.txt

开始我打算做一个好网民,解析了所有的robots.txt文件,遵守排除和爬虫频率。但事实上许多网站如GitHub, Delicious和Facebook都有针对爬虫的白名单。所有的爬虫都被屏蔽了,除了那些著名的允许爬虫的网站(如Google, Yahoo和Bing)。因为链接测试机是不会抓取网页,关注robots.txt也不现实,所以我建议忽略robots.txt。这在Meta Stack Overflow也有讨论。

● 使用合理的超时

测试时,我们给网站30s来响应,但有些网站需要更长时间。你当然不想让一个恶意的网站让你的测试机停止。所以我们采用30s作为最长的响应时间。

● 用很多线程来测试链接

我用在悉尼的开发电脑来做链接测试,显然串行的三百万次访问不知道会占用多长时间。所以我用了30个线程。

并发当然也会带来一些技术挑战。你也不想在等待一个域名释放资源的时候让一个线程阻塞。

我采用Async类来管理队列。相对于微软的任务并行库(Microsoft Task Parallel Library),我更喜欢Async,因为使用它来限制线程池中的线程数量非常简单,而且API也简单易用。

● 一次实效不代表***失效

我仍旧在调整判断一个链接是坏链的算法。一次失效有可能是偶然事件。一个星期内的数次失效可能是服务器坏掉或者不幸的巧合。

现在隔天的两次失效看起来比较可靠 – 我们没有去寻找最***的算法,而是让用户告诉我们什么时候出错了,但我们相信出错率不高。

同样的我们仍旧需要确定在一次成功测试之后多久药重新测试。我想每隔三个月测一次就足够了。

测试链接的一些有趣发现

Kernel.org被黑了

2011年9月1日,Kernel.org被黑了。你要问,这和测试链接有什么关系呢?

事实证明有人破坏了所有的文档链接,这些链接今天仍旧不能用。例如http://www.kernel.org/pub/software/scm /git/docs/git-svn.html 在Stack Overflow的150个左右的帖子里出现过,现在它们会将你导向到404页面,而它的新地址应该在:http://git-scm.com/docs /git-svn。在所有我碰到的坏链中,git文档的坏链是最严重的。将近影响了6000个帖子。采用Apache的重写功能来处理它是非常容易的。

有的网站的URL不能给你任何信息

http://www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038displaylang=en 是个坏链,在60个左右的帖子中出现。想象下,如果这个链接类似于http://www.microsoft.com/downloads/ie- developer-toolbar-beta-3,那么就算微软打算移走这个链接,我们仍旧克一猜测它可能带我们去到什么页面。

将你的404页面做的别致和有用–从GitHub学到的

在所有的404页面中,GitHub的让我最生气。

你问为什么?

它看起来很酷,有相当不错的视觉效果。有些人就是看什么都不顺眼。

嗯,事实上是:

https://github.com/dbalatero/typhoeus 在50个左右的帖子里被引用,而它已经转移到https://github.com/typhoeus。GitHub没有使用任何的跳转,仅仅将你转到404页面。

对url采用最基本的解析以确定真正想要去的页面是非常小的开销:

对不起,我们没有找到你链接到的页面。用户经常会改变账户导致链接失效。”typhoeus”库也存在于:https://github.com/typhoeus

是的,没有任何信息告诉我我犯了个错误。GitHub应该让404页面变得更有用。对我来说GitHub 404页面最让我气愤地是我花了很多力气而找不到结果。不要给我漂亮的页面,能提供一些有用的信息吗。

你可以做多一步,跳转到他们新的首页去,我理解账号是非常有技巧的,但它看起来在GitHub上是多么不可思议的常见错误啊。

在Stack Overflow上我们花了很多时间来优化这种情况,例如“你最喜欢的程序员笑话是什么?”,讨论区认为这个问题不会持续很久,所以我们尽可能解释为什么要移除它,以及哪里可以找到它。

Oracle的问题

Oracle收购Sun对Java生态圈来说是个永远的沉重的打击。Oracle的任务是重新树立品牌,重构Java 生态圈,但这是错误的引导。大量的文档都没有被正确定向。就连最近的在dev.java.net下的所有项目都没有正确的跳转页面。Hudson这个 Java持续集成的服务器曾经使用https://hudson.dev.java.net/ (译者注:也失效了),Stack Overflow中150个帖子都引用了它。

个人的教训

href 标题的重要性

在短链的世界里,看起来在URI里使用任何合理的标题不再那么被鼓励了。事实上过去的三年里你访问的5%的链接都失效了。我相信我的博客中也有许多坏链。修复坏链是个困难的任务,尤其在没有上下文的情况下,这项任务变得更加困难。

所以我决定为我的链接都加上合理的标题。不仅因为能让搜索引擎更好地搜索结果,也能让用户知道受损的图片下是什么内容,同时在处理坏的势后能帮我修复它。

超链接是很脆弱的

当我们使用Google时,我们从来没得到404。它确保我们在杂乱无章的网络中高效的搜索。测试很多的链接告诉你现实并没有那么的好。那么意味着我要避免使用链接吗?当然不是,知道问题的存在能够帮我思考我写下的内容。我会避免写出失去意义的文章。在Stack Overflow我们经常看到如下的回复:

See this blog post over here. 看看这里的文章。

当外部资源链接失效的时候,这种答案就没有了意义。

原文链接: samsaffron.com 

编译:http://blog.jobbole.com/22288/

责任编辑:林师授 来源: 伯乐在线
相关推荐

2016-01-18 10:06:05

编程

2015-06-29 13:47:19

创业创业智慧

2010-08-23 10:30:05

CSS超链接

2012-05-22 09:52:03

jQuery

2013-08-19 12:46:27

2024-04-15 12:54:00

ReactVue列表逻辑

2014-08-06 12:29:33

腾讯开放平台市场

2022-02-22 10:40:27

漏洞网络攻击

2010-10-18 09:10:57

Google日历宕机

2010-01-15 18:12:28

VB.NET超链接

2015-06-01 06:42:50

开源公司三大教训

2022-09-13 08:05:47

AlloyDb架构数据库

2015-09-24 09:41:04

Amazon云停机云安全教训

2009-05-26 09:07:50

Windows 7微软操作系统

2014-12-22 10:09:50

工程师

2022-03-21 10:21:50

jQuery代码模式

2021-03-09 09:55:02

Vuejs前端代码

2020-07-07 10:38:11

首席信息官IT领导者经验教训

2020-07-07 10:40:45

CIO首席信息官IT

2022-12-12 11:08:07

数字化转型企业
点赞
收藏

51CTO技术栈公众号