歡迎光臨
每天分享高質量文章

.NET Core實踐爬蟲系統:解析網頁內容

來自:從此啟程

鏈接http://www.cnblogs.com/fancunwei/p/9581168.html

爬蟲系統的意義


爬蟲的意義在於採集大批量資料,然後基於此進行加工/分析,做更有意義的事情。谷歌,百度,今日頭條,天眼查都離不開爬蟲。

標的


我們來實踐一個最簡單的爬蟲系統。根據Url來識別網頁內容。

網頁內容識別利器:HtmlAgilityPack

GitHub地址:https://github.com/zzzprojects/html-agility-pack

HtmlAgilityPack官網:http://html-agility-pack.net/

HtmlAgilityPack的stackoverflow地址:

https://stackoverflow.com/questions/846994/how-to-use-html-agility-pack

至今Nuget已有超過900多萬的下載量,應用量十分龐大。它提供的文件教程也十分簡單易用。

Parser解析器


HtmlParse可以讓你解析HTML並傳回HtmlDocument

  • FromFile從檔案讀取

///

/// 從檔案讀取

///

public void FromFile() {          

    var path = @”test.html”;

    var doc = new HtmlDocument();

    doc.Load(path);

    var node = doc.DocumentNode.SelectSingleNode(“//body”);

    Console.WriteLine(node.OuterHtml);

}

  • 從字串加載

///

/// 從字串讀取

///

public void FromString()

{

    var html = @”

   

   

   

This is bold heading

   

This is underlined paragraph

   

This is italic heading

   

   

“;

    var htmlDoc = new HtmlDocument();

    htmlDoc.LoadHtml(html);

    var htmlBody = htmlDoc.DocumentNode.SelectSingleNode(“//body”);

    Console.WriteLine(htmlBody.OuterHtml);

}

  • 從網絡加載

///

/// 從網絡地址加載

///

public void FromWeb() {

    var html = @”https://www.cnblogs.com/”;

    HtmlWeb web = new HtmlWeb();

    var htmlDoc = web.Load(html);

    var node = htmlDoc.DocumentNode.SelectSingleNode(“//div[@id=’post_list’]”);

    Console.WriteLine(“Node Name: ” + node.Name + ”
” + node.OuterHtml);

}

Selectors選擇器


選擇器允許您從HtmlDocument中選擇HTML節點。它提供了兩個方法,可以用XPath運算式篩選節點。

XPath教程:http://www.w3school.com.cn/xpath/index.asp

  • SelectNodes() 傳回多個節點

  • SelectSingleNode(String) 傳回單個節點

簡介到此為止,更全的用法參考 http://html-agility-pack.net

查看網頁結構


我們以博客園首頁為示例。用chrome分析下網頁結構,可採集出推薦數,標題,內容Url,內容簡要,作者,評論數,閱讀數。

編碼實現


建立一個Article用來接收文章信息。

public class Article

{

    public string Id { get; set; }

    ///

    /// 標題

    ///

    public string Title { get; set; }

    ///

    /// 概要

    ///

    public string Summary { get; set; }

    ///

    /// 文章鏈接

    ///

    public string Url { get; set; }

    ///

    /// 推薦數

    ///

    public long Diggit { get; set; }

    ///

    /// 評論數

    ///

    public long Comment { get; set; }

    ///

    /// 閱讀數

    ///

    public long View { get; set; }

    ///

    ///明細

    ///

    public string Detail { get; set; }

    ///

    ///作者

    ///

    public string Author { get; set; }

    ///

    /// 作者鏈接

    ///

    public string AuthorUrl { get; set; }

}

然後根據網頁結構,查看XPath路徑,採集內容

///

/// 解析

///

///

public List

ParseCnBlogs()

{

    var url = “https://www.cnblogs.com”;

    HtmlWeb web = new HtmlWeb();

    //1.支持從web或本地path加載html

    var htmlDoc = web.Load(url);

    var post_listnode = htmlDoc.DocumentNode.SelectSingleNode(“//div[@id=’post_list’]”);

    Console.WriteLine(“Node Name: ” + post_listnode.Name + ”
” + post_listnode.OuterHtml);

    var postitemsNodes = post_listnode.SelectNodes(“//div[@class=’post_item’]”);

    var articles = new List

();

    var digitRegex = @”[^0-9]+”;

    foreach (var item in postitemsNodes)

    {

        var article = new Article();

        var diggnumnode = item.SelectSingleNode(“//span[@class=’diggnum’]”);

        //body

        var post_item_bodynode = item.SelectSingleNode(“//div[@class=’post_item_body’]”);

        var titlenode = post_item_bodynode.SelectSingleNode(“//a[@class=’titlelnk’]”);

        var summarynode post_item_bodynode.SelectSingleNode(“//p[@class=’post_item_summary’]”);

        //foot

        var footnode = item.SelectSingleNode(“//div[@class=’post_item_foot’]”);

        var authornode = footnode.ChildNodes[1];

        var commentnode = item.SelectSingleNode(“//span[@class=’article_comment’]”);

        var viewnode = item.SelectSingleNode(“//span[@class=’article_view’]”);

        article.Diggit = int.Parse(diggnumnode.InnerText);

        article.Title = titlenode.InnerText;

        article.Url = titlenode.Attributes[“href”].Value;

        article.Summary = titlenode.InnerHtml;

        article.Author = authornode.InnerText;

        article.AuthorUrl = authornode.Attributes[“href”].Value;

        article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText,  digitRegex, “”));

        article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, “”));

        articles.Add(article);

    }

    return articles;

}

查看採集結果


看到結果就驚獃了,竟然全是重覆的。難道是Xpath語法理解不對麽? 採集結果

重溫下XPath語法


XPath 使用路徑運算式在 XML 文件中選取節點。節點是通過沿著路徑或者 step 來選取的

XPath 通配符可用來選取未知的 XML 元素

我測試了幾個語法如:

//例1,會傳回20個

var titlenodes = post_item_bodynode.SelectNodes(“//a[@class=’titlelnk’]”);

//會報錯,因為這個a並不直接在bodynode下麵,而是在子級h3元素的子級。

var titlenodes = post_item_bodynode.SelectNodes(“a[@class=’titlelnk’]”);

然後又實驗了一種:

//Bingo,這個可以,但是強烈指定了下級h3,這就稍微麻煩了點。

var titlenodes = post_item_bodynode.SelectNodes(“h3//a[@class=’titlelnk’]”);

這裡就引申出了一個小問題:如何定位子級的子級?用通配符*可以麽?

//傳回1個。

var titlenodes= post_item_bodynode.SelectNodes(“*//a[@class=’titlelnk’]”)

能正確傳回1,應該是可以了,我們改下代碼看下效果。 

然後和博客園首頁資料對比,結果吻合。 所以我們可以得出結論:

  • XPath搜索以//開頭時,會匹配所有的項,並不是子項。

  • 直屬子級可以直接跟上 node名稱。

  • 只想查子級的子級,可以用*代替子級,實現模糊搜索。

改過後代碼如下:

public List

ParseCnBlogs()

{

    var url = “https://www.cnblogs.com”;

    HtmlWeb web = new HtmlWeb();

    //1.支持從web或本地path加載html

    var htmlDoc = web.Load(url);

    var post_listnode = htmlDoc.DocumentNode.SelectSingleNode(“//div[@id=’post_list’]”);

    //Console.WriteLine(“Node Name: ” + post_listnode.Name + ”
” + post_listnode.OuterHtml);

    var postitemsNodes = post_listnode.SelectNodes(“div[@class=’post_item’]”);

    var articles = new List

();

    var digitRegex = @”[^0-9]+”;

    foreach (var item in postitemsNodes)

    {

        var article = new Article();

        var diggnumnode = item.SelectSingleNode(“*//span[@class=’diggnum’]”);

        //body

        var post_item_bodynode = item.SelectSingleNode(“div[@class=’post_item_body’]”);

        var titlenode = post_item_bodynode.SelectSingleNode(“*//a[@class=’titlelnk’]”);

        var summarynode = post_item_bodynode.SelectSingleNode(“p[@class=’post_item_summary’]”);

        //foot

        var footnode = post_item_bodynode.SelectSingleNode(“div[@class=’post_item_foot’]”);

        var authornode = footnode.ChildNodes[1];

        var commentnode = footnode.SelectSingleNode(“span[@class=’article_comment’]”);

        var viewnode = footnode.SelectSingleNode(“span[@class=’article_view’]”);


        article.Diggit = int.Parse(diggnumnode.InnerText);

        article.Title = titlenode.InnerText;

        article.Url = titlenode.Attributes[“href”].Value;

        article.Summary = titlenode.InnerHtml;

        article.Author = authornode.InnerText;

        article.AuthorUrl = authornode.Attributes[“href”].Value;

        article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText, digitRegex, “”));

        article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, “”));

        articles.Add(article);

    }

    return articles;

}

原始碼

代碼已上傳至 GitHub:https://github.com/fancunwei/CsharpFanDemo

總結


Demo到此結束,下篇繼續構思如何構建自定義規則,讓用戶可以在頁面自己填寫規則去識別。



●編號151,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂