.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

总结探讨

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

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

相关文章
|
1月前
|
数据采集 中间件 开发者
Scrapy爬虫框架-自定义中间件
Scrapy爬虫框架-自定义中间件
|
1月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
24天前
|
关系型数据库 C# 数据库
.NET 8.0 开源在线考试系统(支持移动端)
【10月更文挑战第27天】以下是适用于 .NET 8.0 的开源在线考试系统(支持移动端)的简介: 1. **基于 .NET Core**:跨平台,支持多种数据库,前后端分离,适用于多操作系统。 2. **结合 Blazor**:使用 C# 开发 Web 应用,支持响应式设计,优化移动端体验。 3. **基于 .NET MAUI**:跨平台移动应用开发,一套代码多平台运行,提高开发效率。 开发时需关注界面设计、安全性与稳定性。
|
1月前
|
数据采集 存储 XML
构建高效的Python爬虫系统
【9月更文挑战第30天】在数据驱动的时代,掌握如何快速高效地获取网络信息变得至关重要。本文将引导读者了解如何构建一个高效的Python爬虫系统,从基础概念出发,逐步深入到高级技巧和最佳实践。我们将探索如何使用Python的强大库如BeautifulSoup和Scrapy,以及如何应对反爬措施和提升爬取效率的策略。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在信息收集的海洋中航行得更远、更深。
46 6
|
2月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
93 3
|
1月前
|
Windows
.NET 隐藏/自定义windows系统光标
【10月更文挑战第20天】在.NET中,可以使用`Cursor`类来控制光标。要隐藏光标,可将光标设置为`Cursors.None`。此外,还可以通过从文件或资源加载自定义光标来更改光标的样式。例如,在表单加载时设置`this.Cursor = Cursors.None`隐藏光标,或使用`Cursor.FromFile`方法加载自定义光标文件,也可以将光标文件添加到项目资源中并通过资源管理器加载。这些方法适用于整个表单或特定控件。
|
2月前
|
JSON 安全 数据安全/隐私保护
从0到1搭建权限管理系统系列三 .net8 JWT创建Token并使用
【9月更文挑战第22天】在.NET 8中,从零开始搭建权限管理系统并使用JWT(JSON Web Tokens)创建Token是关键步骤。JWT是一种开放标准(RFC 7519),用于安全传输信息,由头部、载荷和签名三部分组成。首先需安装`Microsoft.AspNetCore.Authentication.JwtBearer`包,并在`Program.cs`中配置JWT服务。接着,创建一个静态方法`GenerateToken`生成包含用户名和角色的Token。最后,在控制器中使用`[Authorize]`属性验证和解析Token,从而实现身份验证和授权功能。
131 3
|
1月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
1月前
|
开发框架 缓存 算法
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)