65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 XHtmlKit 抓取网页

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2018年2月20日

CPOL

2分钟阅读

viewsIcon

11243

downloadIcon

223

使用 XHtmlKit Nuget 包,使 C# 中的网页抓取变得有趣

引言

所以你要开始网页抓取了!?也许你会构建一个竞争情报数据库,创建下一个热门搜索引擎,或者用有用的数据填充你的应用程序……这里有一个让工作变得有趣的工具:一个名为 XHtmlKit 的 Nuget 包。让我们开始吧!

步骤 1:创建一个项目

首先,在 Visual Studio 中创建一个新项目。对于这个示例,我们将使用经典的桌面控制台应用程序。

步骤 2:添加对 XHtmlKit 的引用

接下来,在解决方案资源管理器中,右键单击 引用,然后选择 管理 Nuget 包

然后,在选择 浏览 选项卡的情况下,输入:XHtmlKit 并按 Enter。从列表中选择 XHtmlKit,然后单击 安装

现在,你会在项目的引用中看到 XHtmlKit

步骤 3:编写一个抓取器

首先,创建一个 POCO 类来保存抓取结果。在这种情况下,将其命名为 Article.cs

namespace SampleScraper
{
    /// <summary>
    /// POCO class for the results we want
    /// </summary>
    public class Article
    {
        public string Category;
        public string Title;
        public string Rating;
        public string Date;
        public string Author;
        public string Description;
        public string Tags;
    }
}

然后,创建一个执行抓取的类。在这种情况下:MyScraper.cs

using System.Collections.Generic;
using System.Xml;
using XHtmlKit;
using System.Text;
using System.Threading.Tasks;

namespace SampleScraper
{
    public static class MyScraper
    {
        /// <summary>
        /// Sample scraper
        /// </summary>
        public static async Task<Article[]> GetCodeProjectArticlesAsync(int pageNum = 1)
        {
            List<Article> results = new List<Article>();

            // Get web page as an XHtml document using XHtmlKit
            string url = 
            "https://codeproject.org.cn/script/Articles/Latest.aspx?pgnum=" + pageNum; 
            XmlDocument page = await XHtmlLoader.LoadWebPageAsync(url);

            // Select all articles using an anchor node containing a robust 
            // @class attribute
            var articles = page.SelectNodes
                           ("//table[contains(@class,'article-list')]/tr[@valign]");

            // Get each article
            foreach (XmlNode a in articles)
            {
                // Extract article data - we need to be aware that 
                // sometimes there are no results 
                // for certain fields
                var category = a.SelectSingleNode("./td[1]//a/text()");
                var title = a.SelectSingleNode(".//div[@class='title']/a/text()");
                var date = a.SelectSingleNode
                           (".//div[contains(@class,'modified')]/text()");
                var rating = a.SelectSingleNode
                             (".//div[contains(@class,'rating-stars')]/@title");
                var desc = a.SelectSingleNode(".//div[@class='description']/text()");
                var author = a.SelectSingleNode
                             (".//div[contains(@class,'author')]/text()");
                XmlNodeList tagNodes = a.SelectNodes(".//div[@class='t']/a/text()");
                StringBuilder tags = new StringBuilder();
                foreach (XmlNode tagNode in tagNodes)
                    tags.Append((tags.Length > 0 ? "," : "") + tagNode.Value);

                // Create the data structure we want
                Article article = new Article
                {
                    Category = category != null ? category.Value : string.Empty,
                    Title = title != null ? title.Value : string.Empty,
                    Author = author != null ? author.Value : string.Empty,
                    Description = desc != null ? desc.Value : string.Empty,
                    Rating = rating != null ? rating.Value : string.Empty,
                    Date = date != null ? date.Value : string.Empty,
                    Tags = tags.ToString()
                };

                // Add to results
                results.Add(article);
            }
            return results.ToArray();
        }
    }
}

然后,使用 MyScraper 类获取一些数据。

using System;

namespace SampleScraper
{
    class Program
    {
        static void Main(string[] args)
        {
            // Get data
            Article[] articles = MyScraper.GetCodeProjectArticlesAsync().Result;

            // Do something with data
            foreach (Article a in articles)
            {
                Console.WriteLine(a.Date + ", " + a.Title + ", " + a.Rating);
            }
        }
    }
}

现在,按 F5,并观看结果!

关注点

这个示例中有几个关键要素:首先,这一行

XmlDocument page = await XHtmlLoader.LoadWebPageAsync(url);

是魔法发生的地方。在底层,XHtmlKit 使用 HttpClient 获取给定的网页,并将原始流解析为 XmlDocument。一旦加载到 XmlDocument 中,使用 XPath 获取所需的数据就变得非常简单。请注意,LoadWebPageAsync() 是一个异步方法,因此你的方法也应该是 async

我们的锚定 XPath 语句获取所有具有 valign 属性的 tr 行,这些行直接位于具有包含术语 'article-list' 的 class 属性的 table 节点下方。

var articles = page.SelectNodes("//table[contains(@class,'article-list')]/tr[@valign]");

这是一个相对健壮的 XPath 语句,因为术语 'article-list' 在本质上是语义化的。虽然网页的基础 CSS 格式可能会发生变化,但文章不太可能在没有进行重大页面重新设计的情况下移动到不同的标记位置。

最后,剩下的就是循环遍历文章节点,并从每个 XHtml 代码块中提取单个文章元素。在这里,我们使用以 './' 为前缀的 XPath 语句。这告诉 XPath 评估器查找相对于当前上下文节点的节点,这效率很高。我们需要注意,给定的 SelectSingleNode() 语句可能返回数据,也可能不返回数据。

var category = a.SelectSingleNode("./td[1]//a/text()");
var title = a.SelectSingleNode(".//div[@class='title']/a/text()");
var date = a.SelectSingleNode(".//div[contains(@class,'modified')]/text()");
var rating = a.SelectSingleNode(".//div[contains(@class,'rating-stars')]/@title");
var desc = a.SelectSingleNode(".//div[@class='description']/text()");
var author = a.SelectSingleNode(".//div[contains(@class,'author')]/text()");

另请注意,单个字段选择使用尽可能语义化的 XPath 语句,例如 'title'、'rating-stars' 和 'author'!

就这样了。祝你抓取愉快!

修订历史

  • 2018 年 2 月 20 日:添加了项目示例下载,修复了图像链接。
© . All rights reserved.