转载请标明出处。
《网页解析器设计》这个题目是我本科毕业设计的题目。时间真快哈,转眼又是一年过去了。去年的这个时候,我正忙着毕设以及考研的复试。那个时候的,未经历过社会的洗礼,对科研,都研究生生活充满了天真而或是白痴般的憧憬。
真是不吃一堑不长一智哈。经过了考研,复试的洗礼。我那幼稚单纯的心,终于开化了,作为一个具有社会属性的人,我也越发市侩和成熟啦。相信我会越走越好的,因为我不在单纯地生活在了我自己杜撰的社会的中了。。。
虽然我信念 不要选择平庸,平庸即有被潜规则的信条,但是在现实生活中,我仍然是一个As plain as Jane一样的女孩。学术上没有 novel idea;工程上也没有或将成为大牛的魄力。混乱的编码习惯,尽管大师兄一再强调要改掉,可是至今没有改好,尽管我每次都会思考这个变量该起什么名字好呢?可是写完程序,还是觉得可读性很差,大多数人看了头痛,看不懂。就犹如我写的字一样。。。。
废话少说,切入正题。本来打算以这个题目为切机发一篇论文了,可是我的方法太老了,而且有诸多参照,如果发PAPER也是在哪种不入流的刊物上发表,而且日后可能会因为这篇文章坏了名声。所以就写成工程型的文字和网友分享了。感谢 same name 的师兄对我论文草稿的修改,再发论文,我会发一个其他版面的。
我的这篇文字,理论基础在于 孙承杰,关毅 基于统计的网页正文信息抽取方法的研究 《中文信息学报》2004年 第18卷 5期
当然一年前,我还是个小P孩,还没有本事将人家论文上的算法直接实现 。当时老师让我用C++/MFC进行实现,可是我安装boost库的时候就没安好,又见 VS2005写C++语言程序的时候没有提示,又见了网络上对VS2005的一些负面提示,就打消这个念头了,换成了C#。
这篇文章我的代码实现是参考的咱们园子里的蛙蛙王子的蛙蛙推荐:基于标记窗的网页正文提取算法的一些细节问题 蛙蛙牌正文提取算
在这里要对蛙蛙的刻苦学习和钻研精神进行一下赞扬。已经是工作的人了还要这么大的劲头进行学习钻研,确实是值得我们小辈学习的。
注:蛙蛙的算法,不应该是基于标记窗的正文提取算法,应该属于孙承杰的统计方法。
前面一部分是老太太裹脚布般的引言,下面 let's begin.
系统实现
语言 C#
调用外来库Winistahtmlparser.
程序的总体印象图请见新闻采阅系统效果图。上半学期《现代信息检索》的大作业就是在本科毕设的基础上进行的修改。
提取过程中提供了几个新闻语料库可供大家下载。
我的系统分为两个解析模块: 目录页(参照中科院软件所于满全的叫法叫做索引页)解析模块和新闻正文页(参照于满全的叫法叫做正文页)解析模块。与以往网友版解析系统不同之处在于,如果是多页新闻,那么我的系统1.可以将新闻的每一页都提出来;2.能够自动识别文本编码。当然这两个特点是和那个金油条新闻提取器来比较的。
目录页提取算法。
目前大多数,二级目录页面都是div/table/li型的,即提取的URL放在这三种元素中的一种。另外,几乎所有的新闻入口目录页上面都标记有新闻题目和发布时间(精确到小时和分钟),那么为了简便,我们就以这个\d\d:\d\d为特征模式提取新闻的URL。
代码如下:
/// 函数名称:GetPattern
/// 功能说明:用于判定索引页正文是储存在Li中还是table中
/// 参数:string rawtext 去掉style 等无关标签之后的网页源码
/// 返回值 bool true表明是table型;false表明是li型
/// </summary>
/// <param name="rawtext"></param>
/// <returns></returns>
public static bool GetPattern(string rawtext)
{
Lexer lexer = new Lexer(rawtext);
Parser parser = new Parser(lexer);
NodeFilter filter = new TagNameFilter("li");//解析出其中的li元素
NodeList htmlNodes = parser.Parse(filter);
if (htmlNodes.Count == 0)
{
return true;//如果源码中不含有li元素则该索引页属于table型。
}
else
{
//去掉其中不含有时间的条目
Regex f2 = new Regex(@"\d\d:\d\d");
for (int i = htmlNodes.Count - 1; i >= 0; i--)
{
if (!f2.IsMatch(htmlNodes[i].ToHtml()))
htmlNodes.Remove(i);
}
if (htmlNodes.Count == 0)//如果网页源码中含有li元素,但是li元素中不含有带发布时间的连接,则该索引页属于table型
return true;
else//否则为li型
return false;
}
}
/// 函数名称:ItemRetrival_1
/// 功能说明:用于提取帖子列表页面的url,帖子标题,帖子时间
/// 参数:string url表示帖子列表url
/// 参数 ref Encoding encode 用于获取网页字符集编码
/// 参数: ref List<string> listUrl,listTitle,listTime用于存放提取出的各项信息
///
/// </summary>
/// <param name="url"></param>
/// <param name="encode"></param>
/// <param name="listurl"></param>
/// <param name="listtitle"></param>
/// <param name="listtime"></param>
public static void ItemRetrival_1(string url, ref Encoding encode, ref List<string> listUrl, ref List<string> listTitle,
ref List<string> listTime)
{
//获取网页源码;
string rawtext = GetDataFromUrl(url, ref encode);
//将无关的style,script等标签去掉;
string reg1 = @"<style[\s\S]+?/style>|<select[\s\S]+?/select>|<script[\s\S]+?/script>|<\!\-\-[\s\S]*?\-\->";
rawtext = new Regex(reg1, RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(rawtext, "");
//以下用htmlparser提取源码中的目标table;
Lexer lexer = new Lexer(rawtext);
//解析出其中的table元素
Parser parser = new Parser(lexer);
NodeFilter filter = new TagNameFilter("table");
NodeList htmlNodes = parser.Parse(filter);
//去除嵌套式table
Regex f1 = new Regex(@"<table.*?>");
for (int i = htmlNodes.Count - 1; i >= 0; i--)
{
MatchCollection myCollection = f1.Matches(htmlNodes[i].ToHtml());
if (myCollection.Count > 1)
htmlNodes.Remove(i);
}
//去除没有时间的table,认为这种table是无效table
Regex f2 = new Regex(@"\d\d:\d\d");
for (int i = htmlNodes.Count - 1; i >= 0; i--)
{
if (!f2.IsMatch(htmlNodes[i].ToHtml()))
htmlNodes.Remove(i);
}
//以下程序解析出以上三种目标信息
string final = htmlNodes.ToHtml();
Lexer lex2 = new Lexer(final);
Parser par2 = new Parser(lex2);
NodeFilter filter2 = new TagNameFilter("tr");
NodeList finalNodes = par2.Parse(filter2);
//提取发帖时间信息
RegexFilter rf = new RegexFilter(@"\d\d:\d\d");
for (int i = 0; i < finalNodes.Count; i++)
{
Lexer lexerTmp = new Lexer(finalNodes[i].ToHtml());
Parser parserTmp = new Parser(lexerTmp);
NodeList tmp = parserTmp.Parse(rf);
if (tmp.Count > 0)
for (int j = 0; j < tmp.Count; j++)
{
string temp = tmp[j].ToHtml();
ModifyRawText(ref temp);
listTime.Add(temp);
}
}
//提取帖子URL以及帖子标题
string atagAssist = finalNodes.ToHtml();
Lexer lex3 = new Lexer(atagAssist);
Parser par3 = new Parser(lex3);
NodeFilter filter3 = new TagNameFilter("a");
NodeList atagNodes = par3.Parse(filter3);
string urlpart = new Regex(@"http://.*?(?=/)").Match(url).Value;
for (int i = 0; i < atagNodes.Count; i++)
{
ATag link = (ATag)atagNodes.ElementAt(i);
string temp1 = link.GetAttribute("href");
string temp2 = link.StringText;
if (!new Regex("http").IsMatch(temp1))//如果提取出的url为相对url,则加上域名补全为绝对url
{
temp1 = urlpart + temp1;//将提取出的url构造完整,形成完整的url
}
ModifyRawText(ref temp2);
listUrl.Add(temp1);
listTitle.Add(temp2);
}
}
/// 函数名称:ItemRetrival_2
/// 功能说明:用于提取帖子列表页面的url,帖子标题,帖子时间
/// 参数:string url表示帖子列表url
/// 参数 ref Encoding encode 用于获取网页字符集编码
/// 参数: ref List<string> listUrl,listTitle,listTime用于存放提取出的各项信息
///
/// </summary>
/// <param name="url"></param>
/// <param name="encode"></param>
/// <param name="listurl"></param>
/// <param name="listtitle"></param>
/// <param name="listtime"></param>
public static void ItemRetrival_2(string url, ref Encoding encode, ref List<string> listUrl, ref List<string> listTitle,
ref List<string> listTime)
{
//获取网页源码;
string rawtext = GetDataFromUrl(url, ref encode);
string reg1 = @"<style[\s\S]+?/style>|<select[\s\S]+?/select>|<script[\s\S]+?/script>|<\!\-\-[\s\S]*?\-\->";
rawtext = new Regex(reg1, RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(rawtext, "");
//将无关的style,script等标签去掉;
//以下操作用于提取帖子页面的发帖时间、帖子URL,帖子标题等信息
//用htmlparser获取目标li元素
Lexer lexer = new Lexer(rawtext);
Parser parser = new Parser(lexer);
NodeFilter filter = new TagNameFilter("li");//解析出其中的li元素
NodeList htmlNodes = parser.Parse(filter);
//去掉其中不含有时间的条目
Regex f2 = new Regex(@"\d\d:\d\d");
for (int i = htmlNodes.Count - 1; i >= 0; i--)
{
if (!f2.IsMatch(htmlNodes[i].ToHtml()))
htmlNodes.Remove(i);
}
RegexFilter rf = new RegexFilter(@"\d\d:\d\d");
string final = htmlNodes.ToHtml();
for (int i = 0; i < htmlNodes.Count; i++)
{
Lexer lexerTmp = new Lexer(htmlNodes[i].ToHtml());
Parser parserTmp = new Parser(lexerTmp);
NodeList tmp = parserTmp.Parse(rf);
if (tmp.Count > 0)
for (int j = 0; j < tmp.Count; j++)
{
string temp = tmp[j].ToHtml();
ModifyRawText(ref temp);
listTime.Add(temp);
}
}
//提取帖子url和标题
string atagAssist = htmlNodes.ToHtml();
Lexer lex3 = new Lexer(atagAssist);
Parser par3 = new Parser(lex3);
NodeFilter filter3 = new TagNameFilter("a");
NodeList atagNodes = par3.Parse(filter3);
for (int i = 0; i < atagNodes.Count; i++)
{
string urlpart = new Regex(@"http://.*?(?=/)").Match(url).Value;
ATag link = (ATag)atagNodes.ElementAt(i);
string temp1 = link.GetAttribute("href");
string temp2 = link.StringText;
if (!new Regex("http").IsMatch(temp1))//如果提取出的url为相对url,则加上域名补全为绝对url
{
temp1 = urlpart + temp1;//将提取出的url构造完整,形成完整的url
}
ModifyRawText(ref temp2);
listUrl.Add(temp1);
listTitle.Add(temp2);
}
}