启发式提取一个网页的主体内容
对于一个网页来说,一般都会有非常丰富的边框或导航条信息,但是用户往往比较专注于主题内容,边框内容可以说是没有太大价值的。尤其是对于手持设备来说,显示大量的边框信息将变得非常讨厌。在搜索引擎中,其实只要搜索引网页的主题内容,对于边框内容的索引意义不大。
上面说了一下提取一个网页主体内容的作用,下面来讨论一下方法。如果真要准确地提取一个网页的主题将非常困难,因为网页的结构多种多样,如果要做到程序自动化提取还是有一定困难的。
在网上看资料的时候,看到一篇文章http://w-shadow.com/blog/2008/01/25/extracting-the-main-content-from-a-webpage/,讲述了如何基于启发式提取网页内容,还有php实现的代码。
我们知道对于导航信息来说,一般是都是一些链接,那么网页中的链接文本的占比程度就会比较高,我们只要去除这些链接占比程度比较高的文本,就基本可以去除这些丰富的边框信息了。
下面是我用C#实现的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Sgml;
using System.IO;
namespace Soso.Youwang
{
class HtmlMainContentExtractor
{
private HashSet containerTags = new HashSet() { "div", "table", "td", "th", "tbody", "thead", "tfoot", "col", "colgroup", "ul", "ol", "html", "center", "span", "form" };
private HashSet removeTags = new HashSet() { "script", "noscript", "style", "meta", "input", "iframe", "embed", "hr", "img", "#comment", "link", "label" };
private HashSet igonoreLenTags = new HashSet() { "span" };
///
/// 链接文本的长度
///
private int totalLinkLen = 0;
///
/// 总的文本的长度
///
private int totalLen = 0;
///
/// 链接度,是平均的多少倍以上就删除
///
private double rate = 1.1;
///
/// 平均链接度
///
private double avgLinkRate;
///
/// 小于一定字符数的container将被删除
///
private int minLen = 20;
public String Extract(string html)
{
html = html.ToLower();
XmlDocument doc = ConvertHtml2Xhtml(html);
HeuristicRemove(doc.DocumentElement);
avgLinkRate = 1.0 * totalLinkLen / totalLen;
int total, link;
ContainerRemove(doc.DocumentElement, out total, out link);
return doc.OuterXml;
}
private bool ContainerRemove(XmlNode node, out int total, out int link)
{
total = 0;
link = 0;
List toRemove = new List();
foreach (XmlNode el in node.ChildNodes)
{
int t;
int l;
if (ContainerRemove(el, out t, out l))
{
toRemove.Add(el);
}
else
{
total += t;
link += l;
}
}
foreach (XmlNode el in toRemove)
{
node.RemoveChild(el);
}
if (containerTags.Contains(node.Name))
{
if ((!igonoreLenTags.Contains(node.Name) && total <= minLen) || 1.0 * link / total >= rate * avgLinkRate)
{
return true;
}
}
else if (node.NodeType == XmlNodeType.Text)
{
total += node.Value.Length;
}
else if (node.Name == "a")
{
link += node.InnerText.Length;
}
return false;
}
private bool HeuristicRemove(XmlNode node)
{
if (removeTags.Contains(node.Name))
{
return true;
}
List toRemove = new List();
foreach (XmlNode el in node.ChildNodes)
{
if (HeuristicRemove(el))
{
toRemove.Add(el);
}
}
foreach (XmlNode el in toRemove)
{
node.RemoveChild(el);
}
if (node.Name == "a")
{
totalLinkLen += node.InnerText.Length;
}
else if (node.NodeType == XmlNodeType.Text)
{
totalLen += node.Value.Length;
}
return false;
}
private XmlDocument ConvertHtml2Xhtml(string html)
{
using (SgmlReader reader = new SgmlReader())
{
reader.DocType = "HTML";
reader.InputStream = new StringReader(html);
using (StringWriter stringWriter = new StringWriter())
{
using (XmlTextWriter writer = new XmlTextWriter(stringWriter))
{
reader.WhitespaceHandling = WhitespaceHandling.None;
writer.Formatting = Formatting.Indented;
XmlDocument doc = new XmlDocument();
doc.Load(reader);
return doc;
}
}
}
}
}
}
代码中用到了一个将html正规化,然后转成xml的一个工具:SgmlReaderDll。
整个工程代码,请点这里。
本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名小橋流水(包含链接)。如您有任何疑问或者授权方面的协商,请给我发邮件。