0. 序言

在开发我最优惠网的过程中,遇到一些问题和技术点,写出来和大家分享,也是我自己对近期工作的整理和记录,预计会有解析HTML类库、本地缓存、链接跳转和C#中执行js代码技巧等方面。

1. HtmlAgilityPack简介

网站中首先遇到的问题是爬虫和解析HTML的问题,一般情况在获取页面少量信息的情况下,我们可以使用正则来精确匹配目标。不过本身正则表达式就比较复杂,同时正则表达式的精确程度很难拿捏,太精确和原网页耦合太严重,页面代码稍改动就会使正则无效;太宽泛的正则由可能会匹配目标过多。所以我们今天介绍的是通过解析HTML结构来获取目标的方式——HtmlAgilityPack。

HtmlAgilityPack是一个解析HTML的类库,支持用XPath来解析HTML,可以像XML一样来解析HTML。

HtmlAgilityPack的代码托管在codeplex上:http://htmlagilitypack.codeplex.com/,不过建议通过Nuget来获取最新版本。

2. XPath简介

XPath即为XML路径语言,它是一种用来确定XML文档中某部分位置的语言。XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。下图列举了XPath主要的路径表达式:

这种针对XML的路径能在解析HTML中用的原因是HtmlAgilityPack将下载下来的HTML页面进行规格化处理,让原本对语义支持并不好的HTML文档格式变为更严谨的Xhtml格式,甚至可以转换为XML格式;并使用XPath来选择、处理dom中的element。下图表示HTML格式化之后的节点示意图:

3. HtmlAgilityPack中常用的API

在HtmlAgilityPack中常用到的类有HtmlDocument、HtmlNodeCollection、HtmlNode和HtmlWeb。

首先是加载HTML,如果是已经存在的静态HTML代码,可以用HtmlDocument的Load()或LoadHtml()来加载,如果是网络上的URL则需要用HtmlWeb的Get()或Load()方法来加载。

不管是哪种加载方式,我们得到的都是HtmlDocument的实例。此时我们需要得到的是HtmlNode或者HtmlNodeCollection对象,使用HtmlDocument的DocumentNode属性,它整个HTML文档的根节点,它本身也是一个HtmlNode。

得到文档根节点后即可使用前一节介绍的XPath得到你想要的文档中任意一个节点的信息。

下面是一个典型的获得有效内容的例子:

HtmlWeb htmlWeb = new HtmlWeb();
HtmlDocument htmlDoc = htmlWeb.Load("http://www.baidu.com");
HtmlNode htmlNode = htmlDoc.DocumentNode.SelectSingleNode("//title");
string title = htmlNode.InnerText;

  

由于使用最多的类是HtmlNode,这里将它常用的属性和方法列在下面,方便各位查阅。

属性:

Attributes              获取节点的属性集合
ChildNodes             获取子节点集合(包括文本节点)
FirstChild              获取第一个子节点
HasAttributes            判断该节点是否含有属性
HasChildNodes          判断该节点是否含有子节点
Id                 获取该节点的Id属性
InnerHtml             获取该节点的Html代码
InnerText             获取该节点的内容,与InnerHtml不同的地方在于它会过滤掉Html代码,而InnerHtml是连Html代码一起输出
LastChild              获取最后一个子节点
Name                Html元素名
NextSibling            获取下一个兄弟节点
ParentNode            获取该节点的父节点
PreviousSibling          获取前一个兄弟节点
XPath                根据节点返回该节点的XPath

方法:

HtmlNode AppendChild(HtmlNode newChild);              将参数元素追加到为调用元素的子元素(追加在最后)
void AppendChildren(HtmlNodeCollection newChildren);       将参数集合中的元素追加为调用元素的子元素(追加在最后)
HtmlNode PrependChild(HtmlNode newChild);              将参数中的元素作为子元素,放在调用元素的最前面
void PrependChildren(HtmlNodeCollection newChildren);       将参数集合中的所有元素作为子元素,放在调用元素前面
HtmlNode Clone();                           本节点克隆到一个新的节点
HtmlNode CloneNode(bool deep);                   节点克隆到一个新的几点,参数确定是否连子元素一起克隆
HtmlNode CloneNode(string newName);                克隆的同时更改元素名
HtmlNode CloneNode(string newName, bool deep);           克隆的同时更改元素名。参数确定是否连子元素一起克隆
void CopyFrom(HtmlNode node);                    创建重复的节点和其下的子树。
void CopyFrom(HtmlNode node, bool deep);               创建节点的副本。
static HtmlNode CreateNode(string html);                静态方法,允许用字符串创建一个新节点
IEnumerable<HtmlNode> DescendantNodes();             获取所有子代节点
IEnumerable<HtmlNode> DescendantNodesAndSelf();         获取所有的子代节点以及自身
IEnumerable<HtmlNode> Descendants();               获取枚举列表中的所有子代节点
IEnumerable<HtmlNode> Descendants(string name);         获取枚举列表中的所有子代节点,注意元素名要与参数匹配
IEnumerable<HtmlNode> DescendantsAndSelf();           获取枚举列表中的所有子代节点以及自身
IEnumerable<HtmlNode> DescendantsAndSelf(string name);     获取枚举列表中的所有子代节点以及自身,注意元素名要与参数匹配
HtmlNode Element(string name);                   根据参数名获取一个元素
IEnumerable<HtmlNode> Elements(string name);          根据参数名获取匹配的元素集合
bool GetAttributeValue(string name, bool def);             帮助方法,用来获取此节点的属性的值(布尔类型)。如果未找到该属性,则将返回默认值。
int GetAttributeValue(string name, int def);              帮助方法,用来获取此节点的属性的值(整型)。如果未找到该属性,则将返回默认值。
string GetAttributeValue(string name, string def);          帮助方法,用来获取此节点的属性的值(字符串类型)。如果未找到该属性,则将返回默认值。
HtmlNode InsertAfter(HtmlNode newChild, HtmlNode refChild);     将一个节点插入到第二个参数节点的后面,与第二个参数是兄弟关系
HtmlNode InsertBefore(HtmlNode newChild, HtmlNode refChild);   讲一个节点插入到第二个参数节点的后面,与第二个参数是兄弟关系
static bool IsCDataElement(string name);                确定是否一个元素节点是一个 CDATA 元素节点。
static bool IsClosedElement(string name);               确定是否封闭的元素节点
static bool IsEmptyElement(string name);                 确定是否一个空的元素节点。
static bool IsOverlappedClosingElement(string text);            确定是否文本对应于一个节点可以保留重叠的结束标记。
void Remove();                              从父集合中移除调用节点
void RemoveAll();                            移除调用节点的所有子节点以及属性
void RemoveAllChildren();                        移除调用节点的所有子节点
HtmlNode RemoveChild(HtmlNode oldChild);              移除调用节点的指定名字的子节点
HtmlNode RemoveChild(HtmlNode oldChild, bool keepGrandChildren);移除调用节点调用名字的子节点,第二个参数确定是否连孙子节点一起移除
HtmlNode ReplaceChild(HtmlNode newChild, HtmlNode oldChild);   将调用节点原有的一个子节点替换为一个新的节点,第二个参数是旧节点
HtmlNodeCollection SelectNodes(string xpath);             根据XPath获取一个节点集合
HtmlNode SelectSingleNode(string xpath);                根据XPath获取唯一的一个节点
HtmlAttribute SetAttributeValue(string name, string value);       设置调用节点的属性
string WriteContentTo();                           将该节点的所有子级都保存到一个字符串中。
void WriteContentTo(TextWriter outText);                将该节点的所有子级都保存到指定的 TextWriter。
string WriteTo();                            将当前节点保存到一个字符串中。
void WriteTo(TextWriter outText);                    将当前节点保存到指定的 TextWriter。
void WriteTo(XmlWriter writer);                     将当前节点保存到指定的则 XmlWriter。

4. 实战

基础的都熟悉了,我们来做练习。例子就是在开发我最优惠网中实际使用的代码,获取什么值得买发现频道的商品名称、价格和详情页网址。代码片段如下:

        static void Main(string[] args)
{
HtmlWeb htmlWeb = new HtmlWeb();
HtmlDocument htmlDoc = htmlWeb.Load("http://faxian.smzdm.com");
HtmlNode htmlNode = htmlDoc.DocumentNode.SelectSingleNode("//ul[@class='leftWrap discovery_list']"); foreach (var li in htmlNode.SelectNodes("child::li"))
{
Console.WriteLine("名称:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a/span[1]").InnerText);
Console.WriteLine("价格:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a/span[2]").InnerText);
Console.WriteLine("网址:" + li.SelectSingleNode("child::div[@class='listItem']/h2/a").GetAttributeValue("href", ""));
Console.WriteLine("---------------------------------------------------------------------------");
Thread.Sleep(1000);
}
Console.ReadLine();
}

  

代码下载:http://pan.baidu.com/s/1pLkq6E3

5. 后记

HtmlAgilityPack 的确是一个功能强大的HTML解析类库,我目前仅仅使用了它的一小部分功能,但是已经能完全满足我的需求。如果童鞋们有类似需求,可以试试。

最后再次打个广告:网购前记得去我最优惠网查查最低价哦^_^

参考文档:

http://www.cnblogs.com/oec2003/p/3322956.html

http://zhoufoxcn.blog.51cto.com/792419/595344/

http://www.cnblogs.com/kissdodog/archive/2013/02/28/2936950.html

http://www.tuicool.com/articles/YZ3uau

我最优惠网系列(1)——HTML 解析类库HtmlAgilityPack的更多相关文章

  1. HTML 解析类库HtmlAgilityPack

    1. HtmlAgilityPack简介 网站中首先遇到的问题是爬虫和解析HTML的问题,一般情况在获取页面少量信息的情况下,我们可以使用正则来精确匹配目标.不过本身正则表达式就比较复杂,同时正则表达 ...

  2. HTML解析利器HtmlAgilityPack

    一个.NET下的HTML解析类库HtmlAgilityPack.HtmlAgilityPack是一个支持用XPath来解析HTML的类库,在花了一点时间学习了解HtmlAgilityPack的API和 ...

  3. Sharepoint学习笔记—习题系列--70-576习题解析 --索引目录

        Sharepoint学习笔记—习题系列--70-576习题解析  为便于查阅,这里整理并列出了70-576习题解析系列的所有问题,有些内容可能会在以后更新. 需要事先申明的是:     1. ...

  4. Sharepoint学习笔记—习题系列--70-573习题解析 --索引目录

                  Sharepoint学习笔记—习题系列--70-573习题解析 为便于查阅,这里整理并列出了我前面播客中的关于70-573习题解析系列的所有问题,有些内容可能会在以后更新, ...

  5. [置顶] Android学习系列-Android中解析xml(7)

    Android学习系列-Android中解析xml(7) 一,概述 1,一个是DOM,它是生成一个树,有了树以后你搜索.查找都可以做. 2,另一种是基于流的,就是解析器从头到尾解析一遍xml文件.   ...

  6. 程序员收藏必看系列:深度解析MySQL优化(二)

    程序员收藏必看系列:深度解析MySQL优化(一) 性能优化建议 下面会从3个不同方面给出一些优化建议.但请等等,还有一句忠告要先送给你:不要听信你看到的关于优化的“绝对真理”,包括本文所讨论的内容,而 ...

  7. 牛客网数据库SQL实战解析(51-61题)

    牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...

  8. 牛客网数据库SQL实战解析(41-50题)

    牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...

  9. 牛客网数据库SQL实战解析(31-40题)

    牛客网SQL刷题地址: https://www.nowcoder.com/ta/sql?page=0 牛客网数据库SQL实战解析(01-10题): https://blog.csdn.net/u010 ...

随机推荐

  1. Oracle instr函数与SqlServer charindex的区别

    INSTR(C1,C2[,I[,J]]) [功能]在一个字符串中搜索指定的字符,返回发现指定的字符的位置; [说明]多字节符(汉字.全角符等),按1个字符计算 [参数] C1 被搜索的字符串      ...

  2. MFC Grid control 2.27

    原文链接地址:http://www.codeproject.com/Articles/8/MFC-Grid-control MFCGridCtrl是个强大的类,用于数据的表格显示. 1.类特征 Cel ...

  3. Swing应用开发实战系列之四:组件内容实时刷新问题

    窗口组件动态刷新问题,在dotnet中根本不算什么问题,用几句代码很轻松就能搞定,但是在Swing中,实现动态刷新组件内容却是一件颇为吃力的事情.譬如针对我们经常用到的刷新JLable.JTextFi ...

  4. Set a static file on django

    1. In setting file: ROOT_PATH='/home/ronglian/project/taskschedule' STATIC_URL = '/static/' STATICFI ...

  5. 标准C IO函数和 内核IO函数 效率(时间)比较

    前言 标准C提供的文件相关的IO函数,除标准错误输出是不带缓冲的(可以尽快的将错误消息显示出来)之外,所有与终端相关的都是行缓冲,其余都是全缓冲的. 我们可以使用setbuf,setvbuf改变指定流 ...

  6. Centos7 搭建hadoop2.6 HA

    用户配置: User :root Password:toor 2.创建新用户 student Pwd: student 3.安装virtualbox的增强工具软件 4.系统默认安装的是openjdk ...

  7. selenium验证车贷计算器算法

    1.验证页面上的车贷计算器算的结果是否与需求中给的公式结果一致. 页面图片: 代码如下(简单实现,需要优化): package com.test; import java.math.BigDecima ...

  8. 【MVC 4】1.第一个 MVC 应用程序

    作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> ASP.NET MVC 是微软的一个 Web开发框架,它整合了“模型—视图—控制器(MVC)”架构 ...

  9. C#委托与C语言函数指针及函数指针数组

    C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用 ...

  10. TDD in Expert Python Programmin

    Test-Driven Development PrinciplesTDD consists of writing test cases that cover a desired feature, t ...