回顾

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

抽象规则

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

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

我用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获取网页的,似乎能支持样式。本篇文章先跳过这个问题,以后再细究。

配置规则

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

 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

总结探讨

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

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

.NetCore实践爬虫系统(二)自定义规则的更多相关文章

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

    爬虫系统的意义 爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情.谷歌,百度,今日头条,天眼查都离不开爬虫. 今日目标 今天我们来实践一个最简单的爬虫系统.根据Url来识别网页 ...

  2. 《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX

    <CMake实践>笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE <CMake实践>笔记二:INSTALL/CMAKE_INSTALL_PREFIX &l ...

  3. 《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX【转】

    本文转载自:http://www.cnblogs.com/52php/p/5681751.html 四.更好一点的Hello World 没有最好,只有更好 从本小节开始,后面所有的构建我们都将采用  ...

  4. 基于golang分布式爬虫系统的架构体系v1.0

    基于golang分布式爬虫系统的架构体系v1.0 一.什么是分布式系统 分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统.简单来说就是一群独立计算机 ...

  5. 【转】RHadoop实践系列之二:RHadoop安装与使用

    RHadoop实践系列之二:RHadoop安装与使用 RHadoop实践系列文章,包含了R语言与Hadoop结合进行海量数据分析.Hadoop主要用来存储海量数据,R语言完成MapReduce 算法, ...

  6. 机器学习算法与Python实践之(二)支持向量机(SVM)初级

    机器学习算法与Python实践之(二)支持向量机(SVM)初级 机器学习算法与Python实践之(二)支持向量机(SVM)初级 zouxy09@qq.com http://blog.csdn.net/ ...

  7. scrapy分布式爬虫scrapy_redis二篇

    =============================================================== Scrapy-Redis分布式爬虫框架 ================ ...

  8. PySpider 爬虫系统

    PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI.采用Python语言编写,分布式架构,支持多种数据库后端,强大的WebUI支持脚本编辑器,任务监视器,项目管理器以及结果查看器 ...

  9. java编程排序之内置引用类型的排序规则实现,和自定义规则实现+冒泡排序运用

    第一种排序:[冒泡排序]基本数据类型的排序. [1]最简易的冒泡排序.效率低.因为比较的次数和趟数最多. /** * 最原始的冒泡排序. * 效率低. * 因为趟数和次数最多.都是按最大化的循环次数进 ...

随机推荐

  1. windows下搭建Consul分布式系统和集群

    随着大数据时代的到来,分布式是解决大数据问题的一个主要手段,随着越来越多的分布式的服务,如何在分布式的系统中对这些服务做协调变成了一个很棘手的问题.我们在一个项目上注册了很多服务,在进行运维时,需要时 ...

  2. 如何监视 Azure 中的虚拟机

    通过收集.查看和分析诊断与日志数据,可以利用很多机会来监视 VM. 若要执行简单的 VM 监视,可以在 Azure 门户中使用 VM 的“概述”屏幕. 可以使用扩展在 VM 上配置诊断以收集更多指标数 ...

  3. jbosscache

    JBossCache 讲解说明 是什么? 一个树形结构.支持集群.支持事务的缓存技术. 有什么作用? JBoss Cache是针对Java应用的企业级集群解决方案,其目的是通过缓存需要频繁访问的Jav ...

  4. 减少MySQL的Sleep进程有效方法

    经常遇到很多朋友问到,他的MySQL中有很多Sleep进程,严重占用MySQL的资源,现在分析一下出现这种现象的原因和解决办法: 1,通常来说,MySQL出现大量Sleep进程是因为采用的PHP的My ...

  5. 《MySQL技术内幕:InnoDB存储引擎(第2版)》书摘

    MySQL技术内幕:InnoDB存储引擎(第2版) 姜承尧 第1章 MySQL体系结构和存储引擎 >> 在上述例子中使用了mysqld_safe命令来启动数据库,当然启动MySQL实例的方 ...

  6. MSCRM2016 取消邮箱强制SSL

    在新建电子邮件服务器配置文件时Advanced中的Use SSL for Incoming/Outgoing Connection默认都是启用的而且无法编辑,启用SSL当然是为了安全的考虑,但当客户的 ...

  7. fedora27安装后的配置工作(持续更新)

    换源 没什么可说的,安装后更换国内软件源是必须做的事,推荐更换阿里的镜像源.换源教程 添加epel源 EPEL (Extra Packages for Enterprise Linux)是基于Fedo ...

  8. 自己模拟写C++中的String类型

    下面是模拟实现字符串的相关功能,它包括一下功能:    String(const char * s);//利用字符串来初始化对象    String(); //默认构造函数    String(con ...

  9. ArcGIS Earth1.9最新版安装和使用教程

    1.下载ArcGIS Earth 官网下载地址:https://www.esri.com/en-us/arcgis/products/arcgis-earth 在这个网页的最下面填上信息,就可以下载了 ...

  10. 阿里八八Alpha阶段Scrum(3/12)

    今日进度 叶文滔: 实现了悬浮按钮的拖动. 问题困难:第三方库调入不成功,多级悬浮按钮的实现仍未完成. 刘晓: 完成注册.修改密码的UI部分,创建了注册Activity,修改密码Activity. 问 ...