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么?