热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【转载】C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子)

原文地址:http:www.cnblogs.comasxinyupCSharp_HtmlAgilityPack_XPath_Weather_Data.html阅读目录1.HtmlAgili

 原文地址:http://www.cnblogs.com/asxinyu/p/CSharp_HtmlAgilityPack_XPath_Weather_Data.html

阅读目录
  • 1.HtmlAgilityPack简介
  • 2.XPath技术介绍与使用
  • 3.采集天气网站案例
  • 4.资源

  第一次接触HtmlAgilityPack是在5年前,一些意外,让我从技术部门临时调到销售部门,负责建立一些流程和寻找潜在客户,最后在阿里巴巴找到了很多客户信息,非常全面,刚开始是手动复制到Excel,是真尼玛的累,虽然那个时候C#还很菜,也想能不能通过程序来批量获取(所以平时想法要多才好)。几经周折,终于发现了HtmlAgilityPack神器,这几年也用HtmlAgilityPack采集了很多类型数据,特别是足球赛事资料库的数据采集以及天气数据采集,都是使用HtmlAgilityPack,所以把自己的使用过程总结下来,分享给大家,让更多人接触和学会使用,给自己的工作带来遍历。

  今天的主要内容是HtmlAgilityPack的基本介绍、使用,实际代码。最后我们以采集天气数据为例子,来介绍实际的采集分析过程和简单的代码。我们将在下一篇文章中开源该天气数据库和C#操作代码。采集核心就只是在这里介绍,其实核心代码都有了,自己加工下就可以了,同时也免费对有需要的人开放。至于具体详情,请关注下一篇文章。 

.NET开源目录:【目录】本博客其他.NET开源项目文章目录 

本文原文地址:C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子)  

1.HtmlAgilityPack简介

  HtmlAgilityPack是一个开源的解析HTML元素的类库,最大的特点是可以通过XPath来解析HMTL,如果您以前用C#操作过XML,那么使用起HtmlAgilityPack也会得心应手。目前最新版本为1.4.6,下载地址如下:http://htmlagilitypack.codeplex.com/ 目前稳定的版本是1.4.6,上一次更新还是2012年,所以很稳定,基本功能全面,也没必要更新了。 

  提到HtmlAgilityPack,就必须要介绍一个辅助工具,不知道其他人在使用的时候,是如何分析页面结构的。反正我是使用官方提供的一个叫做HAPExplorer的工具。非常有用。下面我们在使用的时候会介绍如何使用。   

2.XPath技术介绍与使用

2.1 XPath介绍

  XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言。XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。起初 XPath 的提出的初衷是将其作为一个通用的、介于XPointer与XSL间的语法模型。但是 XPath 很快的被开发者采用来当作小型查询语言。

  XPath是W3C的一个标准。它最主要的目的是为了在XML1.0或XML1.1文档节点树中定位节点所设计。目前有XPath1.0和XPath2.0两个版本。其中Xpath1.0是1999年成为W3C标准,而XPath2.0标准的确立是在2007年。W3C关于XPath的英文详细文档请见:http://www.w3.org/TR/xpath20/ 。

2.2 XPath的路径表达

  XPath是XML的查询语言,和SQL的角色很类似。以下面XML为例,介绍XPath的语法。下面的一些资料是几年前学习这个的时候,从网络以及博客园获取的一些资料,暂时找不到出处,例子和文字基本都是借鉴,再次谢过。如果大家发现类似的一起文章,告诉我链接,我加上引用。下面的Xpath的相关表达也很基础,基本足够用了。

xml version="1.0"encoding="ISO-8859-1"?>
<catalog>
<cd country="USA">
<title>Empire Burlesquetitle>
<artist>Bob Dylanartist>
<price>10.90price>
cd>
catalog>

 

定位节点:XML是树状结构,类似档案系统内数据夹的结构,XPath也类似档案系统的路径命名方式。不过XPath是一种模式(Pattern),可以选出XML档案中,路径符合某个模式的所有节点出来。例如要选catalog底下的cd中所有price元素可以用:

/catalog/cd/price

  如果XPath的开头是一个斜线(/)代表这是绝对路径。如果开头是两个斜线(//)表示文件中所有符合模式的元素都会被选出来,即使是处于树中不同的层级也会被选出来。以下的语法会选出文件中所有叫做cd的元素(在树中的任何层级都会被选出来)://cd

选择未知的元素:使用星号(*)可以选择未知的元素。下面这个语法会选出/catalog/cd的所有子元素:  

/catalog/cd/*

  以下的语法会选出所有catalog的子元素中,包含有price作为子元素的元素。

/catalog/*/price

  以下的语法会选出有两层父节点,叫做price的所有元素。

/*/*/price

  要注意的是,想要存取不分层级的元素,XPath语法必须以两个斜线开头(//),想要存取未知元素才用星号(*),星号只能代表未知名称的元素,不能代表未知层级的元素。

选择分支:使用中括号可以选择分支。以下的语法从catalog的子元素中取出第一个叫做cd的元素。XPath的定义中没有第0元素这种东西。

/catalog/cd[1]

以下语法选择catalog中的最后一个cd元素:(XPathj并没有定义first()这种函式喔,用上例的[1]就可以取出第一个元素。

/catalog/cd[last()]

以下语法选出price元素的值等于10.90的所有/catalog/cd元素

/catalog/cd[price=10.90]

选择属性:在XPath中,除了选择元素以外,也可以选择属性。属性都是以@开头。例如选择文件中所有叫做country的属性:

//@country

以下语法选择出country属性值为UK的cd元素

//cd[@country='UK']

3.采集天气网站案例

3.1 需求分析

  我们要采集的是全国各地城市的天气信息,网站为:http://www.tianqihoubao.com/,该网站数据分为2种类型,1个是历史数据,覆盖范围为2011年至今,1个是天气预报的数据,历史数据是天气后报,也就是实际的天气数据。采集的范围必须覆盖全国主要城市,最好是所有的城市。通过分析该网站的页面,的确是满足要求。天气信息,包括实际的天气状况,风力状况以及气温状况情况,包括最低和最高区间。

  结合基本要求,我们进入网站,分析一些大概特点,以及主要页面的结构。

3.2 网站页面结构分析

  要采集大量的信息,必须对网站页面进行详细的分析和总结。因为机器采集不是人工,需要动态构造URL,请求或者页面html,然后进行解析。所以分析网站页面结构是第一步,也是很关键的一步。我们首先进入到总的历史页面:http://www.tianqihoubao.com/lishi/,如下图:

  很明显,这个总的页面按省份进行了分开,可以看到每个省份、地级市名称的链接中,都是固定格式,只不过拼音缩写不同而已。而且每个省份的第一个城市为省会城市。这一点要注意,程序中要区分省会城市和其他地级城市。当然省会城市也可以省略,毕竟只有30多个,手动标记也很快的事情。这个页面我们将主要采集省份的缩写信息,然后我们选择一个省份,点击进去,看每个省份具体的城市信息,如我们选择辽宁省:http://www.tianqihoubao.com/lishi/ln.htm如下图:

  同样,每个省份下面的地区也有单独的链接,格式和上面的类似,按照城市拼音。我们看到每个省份下面,有大的地级行政区,每个地级市区后面细分了小的县市区。我们随意点击大连市的链接,进去看看具体的天气历史信息:

   该页面包括了城市2011年1月到2015年至今的历史数据,按月分开。链接的特点也很固定,包括了城市名称的拼音和年份月份信息。所以构造这个链接就很容易了。下面看看每个月份的情况:

   广告我屏蔽了一些,手动给抹掉吧。每个城市的每个月的天气信息比较简单,直接表格填充了数据,日期,天气状况,气温和风力。这几步都是按照页面的链接一步一步引导过来的,所以上述流程清楚了,要采集的信息也清楚了,有了大概的思路:

  先采集整个省份的拼音代码,然后依次获取每个省份每个地级市,以及对应县级市的名称和拼音代码,最后循环每个县级市,按照月份获取所有历史数据。下面将重点分析几个页面的节点情况,就是如何用HtmlAgilityPack和Xpath来获取你要的数据信息,至于保存到数据库,八仙过海各显神通吧,我用的是XCode组件。

3.3 分析省-县市结构页面

  还是以辽宁省为例:http://www.tianqihoubao.com/lishi/ln.htm ,打开页面,右键获取网页源代码后,粘贴到 HAPExplorer 中,也可以直接在HAPExplorer 中打开链接,如下面的动画演示:

  我们可以看到,右侧的XPath地址,div结束后,下面都是dl标签,就是我们要采集的行了。下面我们用代码来获取上述结构。先看看获取页面源代码的代码:

public static string GetWebClient(string url)
{
string strHTML = "";
WebClient myWebClient
= new WebClient();
Stream myStream
= myWebClient.OpenRead(url);
StreamReader sr
= new StreamReader(myStream, Encoding.Default);//注意编码
strHTML = sr.ReadToEnd();
myStream.Close();
return strHTML;
}

 

   下面是分析每个省份下属县市区的程序,限于篇幅我们省掉了数据库部分,只采集城市和拼音代码,并输出:

/// 添加省级-地区-县市 的城市信息,注意 省会城市 标记5
/// 省份代码
public static void ParsePageByArea(String cityCode)
{
//更加链接格式和省份代码构造URL
String url = String.Format("http://www.tianqihoubao.com/lishi/{0}.htm", cityCode);
//下载网页源代码
var docText = HtmlHelper.GetWebClient(url);
//加载源代码,获取文档对象
var doc = new HtmlDocument(); doc.LoadHtml(docText);
//更加xpath获取总的对象,如果不为空,就继续选择dl标签
var res = doc.DocumentNode.SelectSingleNode(@"/html[1]/body[1]/div[1]/div[6]/div[1]/div[1]/div[3]");
if (res != null)
{
var list = res.SelectNodes(@"dl");//选择标签数组
if (list.Count <1) return;
foreach (var item in list)
{
var dd = item.SelectSingleNode(@"dd").SelectNodes("a");
foreach (var node in dd)
{
var text = node.InnerText.Trim();
//拼音代码要从href属性中进行分割提取
var herf = node.Attributes["href"].Value.Trim().Split('/', '.');
Console.WriteLine(
"{0}:{1}", text, herf[herf.Length - 2]);
}
}
}
}

 

            //自己项目中用到的
var res = doc.DocumentNode.SelectSingleNode(@"/html[1]/body[1]//div[@id='trends1_cont_2']");

if (res != null)
{
res.Descendants(
"a").Each(x =>
{
list.Add(
new OptionItem()
{
Text
= x.InnerText,
Value
= url + x.Attributes["href"].Value.TrimStart('.'),
});
});

res.Descendants("span").Where(c => c.Attributes["class"].Value == "date").Each((i, c) =>
{
list[i].Id
= int.Parse(c.InnerText.TrimStart('[').TrimEnd(']').Replace("-", ""));
list[i].Tag
= c.InnerText;
});

}

 

我们以辽宁为例,调用代码:ParsePageByArea("ln");结果如下:

3.4 分析城市单月的历史天气页面

  这也是最重要核心的一个要分析的页面。我们以大连市2011年8月份为例:http://www.tianqihoubao.com/lishi/dalian/month/201108.html,我们要找到我们需要采集的信息节点,如下图所示的动画演示,其实这个过程习惯几次就好了,每一次点击节点后,要观察右边的内容是不是我们想要的,还可以通过滚动条的长度判断大概的长度。 

   这里不是直接从URL加载,由于编码原因,URL加载会有乱码,所以我是手动辅助源代码到HAPExplorer中的,效果一样,所以直接在获取页面源代码的时候,要注意编码问题。总的过程比较简单,还是查找到Table标签的位置,因为那里保存了所需要的数据,每一行每一列都非常标准。过程类似,我们直接更加XPath找到Table,然后一次获取每行,每列,进行对应即可,看代码,都进行了详细的注释:

/// 采集单个城市单个月的历史天气数据
/// 城市拼音代码
/// 年份
/// 月份
public static void ParsePageByCityMonth(String cityCode, Int32 year, Int32 month)
{
//更加拼音代码,月份信息构造URL
String url = String.Format("http://www.tianqihoubao.com/lishi/{0}/month/{1}{2:D2}.html", cityCode, year, month);
//获取该链接的源代码
var docText = HtmlHelper.GetWebClient(url);
//加载源代码,获取页面结构对象
var doc = new HtmlDocument(); doc.LoadHtml(docText);
//更加Xpath获取表格对象
var res = doc.DocumentNode.SelectSingleNode(@"/html[1]/body[1]/div[2]/div[6]/div[1]/div[1]/table[1]");
if (res != null)
{
//获取所有行
var list = res.SelectNodes(@"tr");
list.RemoveAt(
0);//移除第一行,是表头
// 遍历每一行,获取日期,以及天气状况等信息
foreach (var item in list)
{
var dd = item.SelectNodes(@"td");
//日期 - - 气温 - 风力风向
if (dd.Count != 4) continue;
//获取当前行日期
var date1 = dd[0].InnerText.Replace("\r\n", "").Replace(" ", "").Trim();
//获取当前行天气状况
var tq = dd[1].InnerText.Replace("\r\n", "").Replace(" ", "").Trim();
//获取当前行气温
var qw = dd[2].InnerText.Replace("\r\n", "").Replace(" ", "").Trim();
//获取当前行风力风向
var fx = dd[3].InnerText.Replace("\r\n", "").Replace(" ", "").Trim();
//输出
Console.WriteLine("{0}:{1},{2},{3}", date1, tq, qw, fx);
}
}
}

 

我们调用大连市2011年8月的记录:ParsePageByCityMonth("dalian",2011,8);  结果如下:

  至于其他页面都是这个思路,先分析xpath,再获取对应的信息。熟悉几次后应该会快很多的。HtmlAgilityPack里面的方法用多了,自己用对象浏览器查看一些,会一些基本的就可以解决很多问题。

  另外,很多网页都是直接输出json数据,对json数据的处理我写过一篇文章,可以参考下,纯手工打造的解析json:用原始方法解析复杂字符串,json一定要用JsonMapper么? 


推荐阅读
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
author-avatar
RaymondKit
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有