【轉載】C#+HtmlAgilityPack+XPath帶你採集數據(以採集天氣數據爲例子)

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

閱讀目錄

  第一次接觸HtmlAgilityPack是在5年前,一些意外,讓我從技術部門臨時調到銷售部門,負責創建一些流程和尋找潛在客戶,最後在阿里巴巴找到了不少客戶信息,很是全面,剛開始是手動複製到Excel,是真尼瑪的累,雖然那個時候C#還很菜,也想能不能經過程序來批量獲取(因此平時想法要多才好)。幾經周折,終於發現了HtmlAgilityPack神器,這幾年也用HtmlAgilityPack採集了不少類型數據,特別是足球賽事資料庫的數據採集以及天氣數據採集,都是使用HtmlAgilityPack,因此把本身的使用過程總結下來,分享給你們,讓更多人接觸和學會使用,給本身的工做帶來遍歷。node

  今天的主要內容是HtmlAgilityPack的基本介紹、使用,實際代碼。最後咱們以採集天氣數據爲例子,來介紹實際的採集分析過程和簡單的代碼。咱們將在下一篇文章中開源該天氣數據庫和C#操做代碼。採集核心就只是在這裏介紹,其實核心代碼都有了,本身加工下就能夠了,同時也免費對有須要的人開放。至於具體詳情,請關注下一篇文章。 數據庫

.NET開源目錄:【目錄】本博客其餘.NET開源項目文章目錄 json

本文原文地址: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/ 。app

2.2 XPath的路徑表達

  XPath是XML的查詢語言,和SQL的角色很相似。如下面XML爲例,介紹XPath的語法。下面的一些資料是幾年前學習這個的時候,從網絡以及博客園獲取的一些資料,暫時找不到出處,例子和文字基本都是借鑑,再次謝過。若是你們發現相似的一塊兒文章,告訴我連接,我加上引用。下面的Xpath的相關表達也很基礎,基本足夠用了。工具

<?xml version="1.0"encoding="ISO-8859-1"?>
<catalog>
<cd country="USA">
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<price>10.90</price>
</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;
}

 

   下面是分析每一個省份下屬縣市區的程序,限於篇幅咱們省掉了數據庫部分,只採集城市和拼音代碼,並輸出:

/// <summary>添加省級-地區-縣市 的城市信息,注意 省會城市 標記5</summary>
/// <param name="cityCode">省份代碼</param>
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,而後一次獲取每行,每列,進行對應便可,看代碼,都進行了詳細的註釋:

/// <summary>採集單個城市單個月的歷史天氣數據</summary>
/// <param name="cityCode">城市拼音代碼</param>
/// <param name="year">年份</param>
/// <param name="month">月份</param>
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麼? 

相關文章
相關標籤/搜索