.NetCore实践爬虫系统(一)解析网页内容

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

爬虫系统的意义

爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情。谷歌,百度,今日头条,天眼查都离不开爬虫。

今日目标

今天我们来实践一个最简单的爬虫系统。根据Url来识别网页内容。

网页内容识别利器:HtmlAgilityPack

GitHub地址

HtmlAgilityPack官网

HtmlAgilityPack的stackoverflow地址

至今Nuget已有超过900多万的下载量,应用量十分庞大。它提供的文档教程也十分简单易用。

Parser解析器

HtmlParse可以让你解析HTML并返回HtmlDocument

  • FromFile从文件读取
/// <summary>
/// 从文件读取
/// </summary>
public void FromFile() {          
    var path = @"test.html";
    var doc = new HtmlDocument();
    doc.Load(path);
    var node = doc.DocumentNode.SelectSingleNode("//body");
    Console.WriteLine(node.OuterHtml);
}
  • 从字符串加载
/// <summary>
/// 从字符串读取
/// </summary>
public void FromString()
{
    var html = @"<!DOCTYPE html>
    <html>
    <body>
        <h1>This is <b>bold</b> heading</h1>
        <p>This is <u>underlined</u> paragraph</p>
        <h2>This is <i>italic</i> heading</h2>
    </body>
    </html> ";

    var htmlDoc = new HtmlDocument();
    htmlDoc.LoadHtml(html);

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

    Console.WriteLine(htmlBody.OuterHtml);
}
  • 从网络加载
/// <summary>
/// 从网络地址加载
/// </summary>
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 + "\n" + node.OuterHtml);
}

Selectors选择器

选择器允许您从HtmlDocument中选择HTML节点。它提供了两个方法,可以用XPath表达式筛选节点。XPath教程

SelectNodes() 返回多个节点

SelectSingleNode(String) 返回单个节点

简介到此为止,更全的用法参考 http://html-agility-pack.net

查看网页结构

我们以博客园首页为示例。用chrome分析下网页结构,可采集出推荐数,标题,内容Url,内容简要,作者,评论数,阅读数。

博客园主页内容结构图

编码实现

建立一个Article用来接收文章信息。


public class Article
    {
        /// <summary>
        /// 
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 概要
        /// </summary>
        public string Summary { get; set; }
        /// <summary>
        /// 文章链接
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 推荐数
        /// </summary>
        public long Diggit { get; set; }
        /// <summary>
        /// 评论数
        /// </summary>
        public long Comment { get; set; }
        /// <summary>
        /// 阅读数
        /// </summary>
        public long View { get; set; }
        /// <summary>
        ///明细
        /// </summary>
        public string Detail { get; set; }
        /// <summary>
        ///作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 作者链接
        /// </summary>
        public string AuthorUrl { get; set; }
    }

然后根据网页结构,查看XPath路径,采集内容

/// <summary>
        /// 解析
        /// </summary>
        /// <returns></returns>
        public List<Article> 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 + "\n" + post_listnode.OuterHtml);

            var postitemsNodes = post_listnode.SelectNodes("//div[@class='post_item']");
            var articles = new List<Article>();
            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 来选取的

表达式        描述
nodename    选取此节点的所有子节点。
/            从根节点选取。
//            从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
.            选取当前节点。
..            选取当前节点的父节点。
@            选取属性。

XPath 通配符可用来选取未知的 XML 元素

通配符       描述
*            匹配任何元素节点。
@*            匹配任何属性节点。
node()        匹配任何类型的节点。

我测试了几个语法如:

//例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<Article> 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 + "\n" + post_listnode.OuterHtml);

            var postitemsNodes = post_listnode.SelectNodes("div[@class='post_item']");
            var articles = new List<Article>();
            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

总结

demo到此结束。谢谢观看!

下篇继续构思如何构建自定义规则,让用户可以在页面自己填写规则去识别。

相关文章
|
1月前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
51 5
|
6天前
|
存储 分布式计算 Hadoop
基于Java的Hadoop文件处理系统:高效分布式数据解析与存储
本文介绍了如何借鉴Hadoop的设计思想,使用Java实现其核心功能MapReduce,解决海量数据处理问题。通过类比图书馆管理系统,详细解释了Hadoop的两大组件:HDFS(分布式文件系统)和MapReduce(分布式计算模型)。具体实现了单词统计任务,并扩展支持CSV和JSON格式的数据解析。为了提升性能,引入了Combiner减少中间数据传输,以及自定义Partitioner解决数据倾斜问题。最后总结了Hadoop在大数据处理中的重要性,鼓励Java开发者学习Hadoop以拓展技术边界。
31 7
|
24天前
|
存储 监控 算法
企业内网监控系统中基于哈希表的 C# 算法解析
在企业内网监控系统中,哈希表作为一种高效的数据结构,能够快速处理大量网络连接和用户操作记录,确保网络安全与效率。通过C#代码示例展示了如何使用哈希表存储和管理用户的登录时间、访问IP及操作行为等信息,实现快速的查找、插入和删除操作。哈希表的应用显著提升了系统的实时性和准确性,尽管存在哈希冲突等问题,但通过合理设计哈希函数和冲突解决策略,可以确保系统稳定运行,为企业提供有力的安全保障。
|
1月前
|
安全 前端开发 Android开发
探索移动应用与系统:从开发到操作系统的深度解析
在数字化时代的浪潮中,移动应用和操作系统成为了我们日常生活的重要组成部分。本文将深入探讨移动应用的开发流程、关键技术和最佳实践,同时分析移动操作系统的核心功能、架构和安全性。通过实际案例和代码示例,我们将揭示如何构建高效、安全且用户友好的移动应用,并理解不同操作系统之间的差异及其对应用开发的影响。无论你是开发者还是对移动技术感兴趣的读者,这篇文章都将为你提供宝贵的见解和知识。
|
1月前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
37 3
|
24天前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
46 0
|
1月前
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
101 3
|
28天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
2月前
|
机器学习/深度学习 人工智能 数据处理
【AI系统】NV Switch 深度解析
英伟达的NVSwitch技术是高性能计算领域的重大突破,旨在解决多GPU系统中数据传输的瓶颈问题。通过提供比PCIe高10倍的带宽,NVLink实现了GPU间的直接数据交换,减少了延迟,提高了吞吐量。NVSwitch则进一步推动了这一技术的发展,支持更多NVLink接口,实现无阻塞的全互联GPU系统,极大提升了数据交换效率和系统灵活性,为构建强大的计算集群奠定了基础。
123 3
|
2月前
|
前端开发 Android开发 UED
移动应用与系统:从开发到优化的全面解析####
本文深入探讨了移动应用开发的全过程,从最初的构思到最终的发布,并详细阐述了移动操作系统对应用性能和用户体验的影响。通过分析当前主流移动操作系统的特性及差异,本文旨在为开发者提供一套全面的开发与优化指南,确保应用在不同平台上均能实现最佳表现。 ####
44 0

推荐镜像

更多