.NetCore实践爬虫系统(二)自定义规则

简介: 回顾上篇文章NetCore实践爬虫系统(一)解析网页内容 我们讲了利用HtmlAgilityPack,输入XPath路径,识别网页节点,获取我们需要的内容。评论中也得到了大家的一些支持与建议。下面继续我们的爬虫系统实践之路。

回顾

上篇文章NetCore实践爬虫系统(一)解析网页内容 我们讲了利用HtmlAgilityPack,输入XPath路径,识别网页节点,获取我们需要的内容。评论中也得到了大家的一些支持与建议。下面继续我们的爬虫系统实践之路。本篇文章不包含依赖注入/数据访问/UI界面等,只包含核心的爬虫相关知识,只能作为Demo使用,抛砖引玉,共同交流。

抽象规则

爬虫系统之所以重要,正是他能支持各种各样的数据。要支持识别数据,第一步就是要将规则剥离出来,支持用户自定义。

爬虫规则,实际上是跟商品有点类似,如动态属性,但也有它特殊的地方,如规则可以循环嵌套,递归,相互引用,链接可以无限下去抓取。更复杂的,就需要自然语言识别,语义分析等领域了。

我用PPT画了个演示图。用于演示支持分析文章,活动,天气等各种类型的规则。

PPT示意图

编码实现

先来定义个采集规则接口,根据规则获取单个或一批内容。

    /// <summary>
    /// 采集规则接口
    /// </summary>
    public interface IDataSplider
    {
        /// <summary>
        /// 得到内容
        /// </summary>
        /// <param name="rule"></param>
        /// <returns></returns>
        List<SpliderContent> GetByRule(SpliderRule rule);

        /// <summary>
        /// 得到属性信息
        /// </summary>
        /// <param name="node"></param>
        /// <param name="rule"></param>
        /// <returns></returns>
        List<Field> GetFields(HtmlNode node, SpliderRule rule);
    }

必不可少的规则类,用来配置XPath根路径。

 /// <summary>
    /// 采集规则-能满足列表页/详情页。
    /// </summary>
    public class SpliderRule
    {
        public string Id { get; set; }

        public string Url { get; set; }
        /// <summary>
        /// 网页块
        /// </summary>
        public string ContentXPath { get; set; }
        /// <summary>
        /// 支持列表式
        /// </summary>
        public string EachXPath { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<RuleField> RuleFields { get; set; }
    }
    

然后就是属性字段的自定义设置,这里根据内容特性,加入了正则支持。例如评论数是数字,可用正则筛选出数字。还有Attribute字段,用来获取node的Attribute信息。

/// <summary>
    /// 自定义属性字段
    /// </summary>
    public class RuleField
    {
        public string Id { get; set; }

        public string DisplayName { get; set; }
        /// <summary>
        /// 用于存储的别名
        /// </summary>
        public string FieldName { get; set; }
        public string XPath { get; set; }
        public string Attribute { get; set; }
        /// <summary>
        /// 针对获取的HTml正则过滤
        /// </summary>
        public string InnerHtmlRegex { get; set; }
        /// <summary>
        /// 针对获取的Text正则过滤
        /// </summary>
        public string InnerTextRegex { get; set; }
        /// <summary>
        /// 是否优先取InnerText
        /// </summary>
        public bool IsFirstInnerText { get; set; }

    }

下面是根据文章爬虫规则的解析步骤,实现接口IDataSplider

/// <summary>
    /// 支持列表和详情页
    /// </summary>
    public class ArticleSplider : IDataSplider
    {
        /// <summary>
        /// 根据Rule
        /// </summary>
        /// <param name="rule"></param>
        /// <returns></returns>
        public List<SpliderContent> GetByRule(SpliderRule rule)
        {
            var url = rule.Url;
            HtmlWeb web = new HtmlWeb();
            //1.支持从web或本地path加载html
            var htmlDoc = web.Load(url);
            var contentnode = htmlDoc.DocumentNode.SelectSingleNode(rule.ContentXPath);

            var list = new List<SpliderContent>();
            //列表页
            if (!string.IsNullOrWhiteSpace(rule.EachXPath))
            {
                var itemsNodes = contentnode.SelectNodes(rule.EachXPath);
                foreach (var item in itemsNodes)
                {
                    var fields = GetFields(item, rule);
                    list.Add(new SpliderContent()
                    {
                        Fields = fields,
                        SpliderRuleId = rule.Id
                    });
                }
                return list;
            }
            //详情页
            var cfields = GetFields(contentnode, rule);
            list.Add(new SpliderContent()
            {
                Fields = cfields,
                SpliderRuleId = rule.Id
            });
            return list;
        }

        public List<Field> GetFields(HtmlNode item, SpliderRule rule)
        {
            var fields = new List<Field>();

            foreach (var rulefield in rule.RuleFields)
            {
                var field = new Field() { DisplayName = rulefield.DisplayName, FieldName = "" };

                var fieldnode = item.SelectSingleNode(rulefield.XPath);
                if (fieldnode != null)
                {

                    field.InnerHtml = fieldnode.InnerHtml;
                    field.InnerText = fieldnode.InnerText;
                    field.AfterRegexHtml = !string.IsNullOrWhiteSpace(rulefield.InnerHtmlRegex) ? Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, "") : fieldnode.InnerHtml;
                    field.AfterRegexText = !string.IsNullOrWhiteSpace(rulefield.InnerTextRegex) ? Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, "") : fieldnode.InnerText;

                    //field.AfterRegexHtml = Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, "");
                    //field.AfterRegexText = Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, "");
                    if (!string.IsNullOrWhiteSpace(rulefield.Attribute))
                    {
                        field.Value = fieldnode.Attributes[rulefield.Attribute].Value;
                    }
                    else
                    {
                        field.Value = rulefield.IsFirstInnerText ? field.AfterRegexText : field.AfterRegexHtml;
                    }
                    }
                fields.Add(field);
            }
            return fields;
        }
    }

还是以博客园为例,配置内容和属性的自定义规则

        /// <summary>
        /// 
        /// </summary>
        public void RunArticleRule()
        {
            var postitembodyXPath = "div[@class='post_item_body']//";
            var postitembodyFootXPath = postitembodyXPath+ "div[@class='post_item_foot']//";
            var rule = new SpliderRule()
            {
                ContentXPath = "//div[@id='post_list']",
                EachXPath = "div[@class='post_item']",
                Url = "https://www.cnblogs.com",
                RuleFields = new List<RuleField>() {
                         new RuleField(){ DisplayName="推荐", XPath="*//span[@class='diggnum']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="标题",XPath=postitembodyXPath+"a[@class='titlelnk']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="URL",XPath=postitembodyXPath+"a[@class='titlelnk']",Attribute="href", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="简要",XPath=postitembodyXPath+"p[@class='post_item_summary']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="作者",XPath=postitembodyFootXPath+"a[@class='lightblue']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="作者URL",XPath=postitembodyFootXPath+"a[@class='lightblue']",Attribute="href", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="讨论数", XPath="span[@class='article_comment']",IsFirstInnerText=true, InnerTextRegex=@"[^0-9]+"  },
                         new RuleField(){ DisplayName="阅读数", XPath=postitembodyFootXPath+"span[@class='article_view']",IsFirstInnerText=true, InnerTextRegex=@"[^0-9]+"  },
                    }
            };
            var splider = new ArticleSplider();
            var list = splider.GetByRule(rule);
            foreach (var item in list)
            {
                var msg = string.Empty;
                item.Fields.ForEach(M =>
                {
                    if (M.DisplayName != "简要" && !M.DisplayName.Contains("URL"))
                    {
                        msg += $"{M.DisplayName}:{M.Value}";
                    }
                });
                Console.WriteLine(msg);
            }
        }
        

运行效果

效果完美!

运行效果图

经过简单的重构,我们已经达到了上篇的效果。

常用规则模型和自定义规则模型

写到这里,我想到了一般UML图工具或Axsure原型等,都会内置各种常用组件,那么文章爬虫模型也是我们内置的一种常用组件了。后续我们完全可以按照上面的套路支持其他模型。除了常用模型之外,在网页或客户端上,高级的爬虫工具会支持用户自定义配置,根据配置来获取内容。

上面的SpliderRule已经能支持大部分内容管理系统单页面抓取。但无法支持规则相互引用,然后根据抓取的内容引用配置规则继续抓取。(这里也许有什么专门的名词来描述:递归爬虫?)。

今天主要是在上篇文章的基础上重构而来,支持了规则配置。为了有点新意,就多提供两个配置例子吧。

例子1:文章详情

我们以上篇文章为例,获取文章详情。 主要结点是标题,内容。其他额外属性暂不处理。

文章详情结构图

编码实现

        /// <summary>
        /// 详情
        /// </summary>
        public void RunArticleDetail() {
           

            var rule = new SpliderRule()
            {
                ContentXPath = "//div[@id='post_detail']",
                EachXPath = "",
                Url = " https://www.cnblogs.com/fancunwei/p/9581168.html",
                RuleFields = new List<RuleField>() {
                         new RuleField(){ DisplayName="标题",XPath="*//div[@class='post']//a[@id='cb_post_title_url']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="详情",XPath="*//div[@class='postBody']//div[@class='blogpost-body']",Attribute="", IsFirstInnerText=false }
                           }
            };
            var splider = new ArticleSplider();
            var list = splider.GetByRule(rule);
            foreach (var item in list)
            {
                var msg = string.Empty;
                item.Fields.ForEach(M =>
                {
                    Console.WriteLine($"{M.DisplayName}:{M.Value}");
                });
                Console.WriteLine(msg);
            }
        }

运行效果

效果同样完美!

文章详情

例子2:天气预报

天气预报的例子,我们就以上海8-15天预报为例

分析结构

点击链接,我们发现 今天/7天/8-15天/40天分别是不同的路由页面,那就简单了,我们只考虑当前页面就行。还有个问题,那个晴天雨天的图片,是按样式显示的。我们虽然能抓到html,但样式还未考虑,,HtmlAgilityPack应该有个从WebBrowser获取网页的,似乎能支持样式。本篇文章先跳过这个问题,以后再细究。

上海8-15预报网页结构分析图

配置规则

根据网页结构,配置对应规则。

 public void RunWeather() {

            var rule = new SpliderRule()
            {
                ContentXPath = "//div[@id='15d']",
                EachXPath = "*//li",
                Url = "http://www.weather.com.cn/weather15d/101020100.shtml",
                RuleFields = new List<RuleField>() {
                         new RuleField(){ DisplayName="日期",XPath="span[@class='time']", IsFirstInnerText=true },
                         new RuleField(){ DisplayName="天气",XPath="span[@class='wea']",Attribute="", IsFirstInnerText=false },
                         new RuleField(){ DisplayName="区间",XPath="span[@class='tem']",Attribute="", IsFirstInnerText=false },
                         new RuleField(){ DisplayName="风向",XPath="span[@class='wind']",Attribute="", IsFirstInnerText=false },
                         new RuleField(){ DisplayName="风力",XPath="span[@class='wind1']",Attribute="", IsFirstInnerText=false },
                           }
            };
            var splider = new ArticleSplider();
            var list = splider.GetByRule(rule);
            foreach (var item in list)
            {
                var msg = string.Empty;
                item.Fields.ForEach(M =>
                {
                        msg += $"{M.DisplayName}:{M.Value} ";
                });
                Console.WriteLine(msg);
            }

        }

运行效果

效果再次完美!

天气预报获取图

源码

上述代码已提交到GitHub

总结探讨

综上所述,我们实现单页面的自定义规则,但也遗留了一个小问题。天气预报晴天阴天效果图,原文是用样式展示的。针对这种不规则问题,如果代码定制当然很容易,但如果做成通用,有什么好办法呢?请提出你的建议!心情好的,顺便点个推荐...

下篇文章,继续探讨多页面/递归爬虫自定义规则的实现。

作者:从此启程/范存威

出处:http://www.cnblogs.com/fancunwei/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!

相关文章
|
27天前
|
人工智能 开发框架 .NET
如何掌握.NET技术,引领开发前沿:.NET技术的核心能力、在现代开发中的应用实践、以及如何通过.NET技术实现持续创新。
.NET技术已成为软件开发不可或缺的部分,本文分三部分探讨:核心能力如多语言支持、统一运行时环境、丰富的类库及跨平台能力;现代开发实践包括企业级应用、Web与移动开发、云服务及游戏开发;并通过性能优化、容器化、AI集成等方面实现持续创新,使开发者站在技术前沿。
42 3
|
1月前
|
机器学习/深度学习 数据采集 数据可视化
基于爬虫和机器学习的招聘数据分析与可视化系统,python django框架,前端bootstrap,机器学习有八种带有可视化大屏和后台
本文介绍了一个基于Python Django框架和Bootstrap前端技术,集成了机器学习算法和数据可视化的招聘数据分析与可视化系统,该系统通过爬虫技术获取职位信息,并使用多种机器学习模型进行薪资预测、职位匹配和趋势分析,提供了一个直观的可视化大屏和后台管理系统,以优化招聘策略并提升决策质量。
|
7天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
14天前
|
jenkins 测试技术 持续交付
解锁.NET项目高效秘籍:从理论迷雾到实践巅峰,持续集成与自动化测试如何悄然改变游戏规则?
【8月更文挑战第28天】在软件开发领域,持续集成(CI)与自动化测试已成为提升效率和质量的关键工具。尤其在.NET项目中,二者的结合能显著提高开发速度并保证软件稳定性。本文将从理论到实践,详细介绍CI与自动化测试的重要性,并以ASP.NET Core Web API项目为例,演示如何使用Jenkins和NUnit实现自动化构建与测试。每次代码提交后,Jenkins自动触发构建流程,通过编译和运行NUnit测试确保代码质量。这种方式不仅节省了时间,还能快速发现并解决问题,推动.NET项目开发迈向更高水平。
32 8
|
12天前
|
数据采集 Rust 安全
Rust在网络爬虫中的应用与实践:探索内存安全与并发处理的奥秘
【8月更文挑战第31天】网络爬虫是自动化程序,用于从互联网抓取数据。随着互联网的发展,构建高效、安全的爬虫成为热点。Rust语言凭借内存安全和高性能特点,在此领域展现出巨大潜力。本文探讨Rust如何通过所有权、借用及生命周期机制保障内存安全;利用`async/await`模型和`tokio`运行时处理并发请求;借助WebAssembly技术处理动态内容;并使用`reqwest`和`js-sys`库解析CSS和JavaScript,确保代码的安全性和可维护性。未来,Rust将在网络爬虫领域扮演更重要角色。
27 1
|
15天前
|
数据采集 存储 JavaScript
构建你的第一个Python爬虫:从理论到实践
【8月更文挑战第27天】本文旨在为初学者提供一个关于如何构建简单Python网络爬虫的指南。我们将从网络爬虫的基础概念讲起,然后通过一个实际的项目案例来展示如何抓取网页数据,并将其存储在本地文件中。文章将介绍必要的工具和库,并逐步引导读者完成一个简单的爬虫项目,以加深对网络数据抓取过程的理解。
|
11天前
|
数据采集 存储 数据库
构建你的第一个Python爬虫:从入门到实践
【8月更文挑战第31天】在数字时代的浪潮中,数据如同新时代的石油,而网络爬虫则是开采这些数据的钻头。本文将引导初学者了解并实现一个基础的网络爬虫,使用Python语言,通过实际代码示例,展示如何收集和解析网页信息。我们将一起探索HTTP请求、HTML解析以及数据存储等核心概念,让你能够快速上手并运行你的首个爬虫项目。
|
14天前
|
Kubernetes 监控 Devops
【独家揭秘】.NET项目中的DevOps实践:从代码提交到生产部署,你不知道的那些事!
【8月更文挑战第28天】.NET 项目中的 DevOps 实践贯穿代码提交到生产部署全流程,涵盖健壮的源代码管理、GitFlow 工作流、持续集成与部署、容器化及监控日志记录。通过 Git、CI/CD 工具、Kubernetes 及日志框架的最佳实践应用,显著提升软件开发效率与质量。本文通过具体示例,助力开发者构建高效可靠的 DevOps 流程,确保项目成功交付。
41 0
|
18天前
|
开发框架 .NET Docker
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
|
25天前
|
数据采集 存储 中间件
Python进行网络爬虫:Scrapy框架的实践
【8月更文挑战第17天】网络爬虫是自动化程序,用于从互联网收集信息。Python凭借其丰富的库和框架成为构建爬虫的首选语言。Scrapy作为一款流行的开源框架,简化了爬虫开发过程。本文介绍如何使用Python和Scrapy构建简单爬虫:首先安装Scrapy,接着创建新项目并定义爬虫,指定起始URL和解析逻辑。运行爬虫可将数据保存为JSON文件或存储到数据库。此外,Scrapy支持高级功能如中间件定制、分布式爬取、动态页面渲染等。在实践中需遵循最佳规范,如尊重robots.txt协议、合理设置爬取速度等。通过本文,读者将掌握Scrapy基础并了解如何高效地进行网络数据采集。
99 6

热门文章

最新文章