之前我在《改善自己的阅读体验》推荐使用Readability这个小工具。它是一段JavaScript脚本,通过在浏览器的页面上运行,提取出文章正文部分,并通过一种干净清爽的形式展示给用户。那么,如果我们在服务器端得到了一个HTML字符串,又该如何得到它的可读部分?直接在服务器端执行JavaScript不太可行,因为Readability依赖浏览器的DOM结构及相关API。如果调用WebKit和IE的浏览器内核又需要大动干戈,也很难跨平台。因此,我基于HtmlAgilityPack将Readability的部分算法移植到了C#上。
我们知道,把一个页面的HTML解析为DOM树并不是件容易的事情,因为HTML几乎不会是标准的XML,各种容错必不可少,否则寸步难行。HtmlAgilityPack是一个非常有用的类库,提供了近乎浏览器的解析功能以及各种DOM操作,只需简单补充几行代码便可以对应几乎浏览器上所有的DOM操作。更重要的是,它只是依赖了.NET 2.0中的XPath实现,完全不涉及MSHTML.dll,ActiveX或是COM等非托管代码,为我们移植Readability提供了良好的基础。
Readability的算法并不复杂,我主要移植了它的getArticleTitle及grabArticle两个方法,分别用于获取文章标题及页面内容。不过值得一提的是,我发现它的某些算法还是为英文内容服务的。例如在获取文章标题时,它会按照空格分割文档的标题,查看其中有多少个单词,并进行一些处理,而对于中文内容很显然是不合适的(因此对于中文页面,捕获到的标题往往就是页面标题)。同样,在计算某个节点是否是文章正文的时候,其中“英文逗号”的数量也是分值的一部分,我在移植的时候也添加了“中文逗号”。
目前我的移植成果放在了github上,称为NReadability,可以从一个HTML字符串中获得标题和正文内容。包括阅读Readability的代码在内,移植工作大约花了我三个小时的时间,其中的算法几乎和JavaScript代码完全对应,不过并不完整,还有些细微之处没有移植(甚至还没完全去除样式),有时间我会慢慢补上。从效果上看执行结果还是比较令人满意的,使用起来也非常简单,例如:
vardocumentHtml = newWebClient().DownloadString("http://...");
varreadability = Readability.Create(documentHtml);
Console.WriteLine(readability.Title);
Console.WriteLine(readability.Content);
我写了个最最简单的示例程序部署在我的博客上(Mono 2.8,ASP.NET 4),您可以尝试一下。目前这个页面在下载URL内容时直接使用了UTF-8编码,因此对于某些腌臜泼皮的中文页面会得到乱码。您可以使用英文页面,或是比较正常的中文页面(例如InfoQ,博客园,CSDN等等)进行试验。如果结果与您的预期不符,也请在评论或是在推特上回复一下,谢谢。