引言

在上篇文章(http://www.cnblogs.com/lightluomeng/p/7212577.html)中,初步实现了一个可配置的网页信息分析组件。但是由于是奔着解决事情的目的去的,所以写的比较匆忙,很多细节方面的问题没有仔细考虑,所以存在不少问题。主要问题有:

  • 配置非常不人性化。不人性化到什么程度呢...我自己配置了一个需要抓取多重列表同时中间需要分析连接然后跳转的页面,足足写了500行的配置文件。而且每个节点的类型名称的编写这里没有做优化,导致名称空间很长,很累,而且容易出错。
  • 关于返回信息降维没有处理好,所以在实现新的功能节点的时候很容易出错。
  • 日志做的不够好,不能够通过错误信息推断哪里出了问题。
  • 配置还不够到位。仍然需要不少的代码来把整个流程串联起来。没有做到外部程序集加载。

设计上的改进

  • 不再静默处理降维(集合收敛),现在使用一个DimReduceConvertor来将二维数组降维到一位数组,或者将更高维度的数组降维到低一个维度的数组
  • 不再静默的判断是否是集合,现在使用一个ProcessedList来将数据显著的标记为数组,降维操作也会基于这个判断进行
  • 移除了ICollector,将此接口上的Key属性定义放在了基础的IValueConvertor上,这样更好的保证了整个树形结构的一致性,同时可以显著的减少嵌套结构
  • 引入了IValuePersistence,用来解决处理后的值的持久化的问题
  • 所有元件都通过构造函数注入的方式引用了ILogger
  • 大量应用了IOptions模式,从而可以以全局的方式配置一些必要的信息,减少单个处理节点的配置的复杂度
  • 引入了ITypeNameResolver从而使得单个节点在指定名称的时候可以使用简写,降低配置难度;引入了其他的ITypeResolver从而使自动化注入和配置成为可能

目前整体的类型继承关系如下(部分类型未展示):

IOptions模式

IOptions建设在.net core的ioc的基础之上。这个模式结合了.net core的配置系统之后,非常优雅。通过类型继承和配置类型的组合注入(在一个类型中同时注入自身的定制化配置和基类的配置),可以很方便的做到全局配置和个别配置。同时,由于IOptions<>支持可选依赖,这样就可以给一个类型提供默认的行为,而后通过配置在必要的时候改变其行为。例如:

        public CollectorConvertor(ILogger logger, IOptions<ConvertorOptions> options,
IOptions<CollectorOptions> collectorOptions) : base(logger, options)
{
if (collectorOptions.Value != null)
{
AutoGenerateKey = collectorOptions.Value.AutoGenerateKey;
AutoResolveComflict = collectorOptions.Value.AutoResolveComflict;
}
}

在这类型CollectorConvertor中,同时注入了两个配置。其中ConvertorOptions是基类的配置。我们可以通过CollectorOptions来覆盖基类的配置。当然,在上面的代码中,并没有这么做,出于其他原因,节点的初始化操作是通过其他方式实现的。

几个实例

现在,把一个控制台程序的代码限定为:

    class Program
{
static void Main(string[] args)
{
SwitchConfiguration();
RunCore();
} public static IServiceCollection ServiceCollection { get; set; } public static IServiceProvider ServiceProvider { get; set; } public static IConfigurationRoot ConfigurationRoot { get; set; } private static void SwitchConfiguration()
{
var allFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory);
var files = allFiles.Where(i => Regex.IsMatch(i, @".*appsettings\.?.*\.json")).ToList();
if (files.Count == 1)
{
Console.WriteLine("仅找到一个配置文件,加载中...");
BuildConfiguration(files[0]);
}
else
{
Console.WriteLine($"找到{files.Count}个配置文件,请选择加载第几个...");
var index = Console.ReadLine().Number<int>();
if (index == null)
{
Console.WriteLine("错误的输入,程序退出,回车以继续...");
Console.ReadLine();
SwitchConfiguration();
}
else
{
var configurationName = files[index.Value];
BuildConfiguration(configurationName);
}
}
} private static void BuildConfiguration(string fileName)
{
var builder = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile(fileName, true, true);
var configurationRoot = builder.Build();
var collection = new ServiceCollection();
collection.AddOptions();
collection.ConfigureDefault<EnviromentBuilderOptions>(configurationRoot); var traceSource = new TraceSource("信息提取", SourceLevels.All);
traceSource.Listeners.Add(new ConsoleTraceListener());
collection.AddSingleton<ILogger, TraceSourceLogger>(p => new TraceSourceLogger(traceSource));
collection.AddSingleton<EnviromentBuilder>();
collection.AddSingleton<ConvertorBuilder>(); var enBuilder = collection.BuildServiceProvider().GetService<EnviromentBuilder>();
var enviroment = enBuilder.Build(collection, configurationRoot);
ServiceProvider = enviroment.ServiceProvider;
ConfigurationRoot = configurationRoot;
} private static void RunCore()
{
var builder = ServiceProvider.GetService<ConvertorBuilder>();
var convertor = builder.Build();
if (convertor == null)
{
Console.WriteLine("无法初始化convertor,程序退出");
}
else
{
AsyncHelper.Synchronize(() => convertor.ProcessAsync(null));
Console.WriteLine("处理完成...");
}
}
}

通过配置来抓取不同网站的信息。比如,我们使用以下配置来抓取博客园新闻的前10页的标题:

"ConvertorBuildOptions": {
"TypeName": "Collector",
"PersistenceTypeName":"ConsoleOutputPersistence",
"Children": [
{
"Key": "博客园前10页所有的文章title",
"TypeName": "Container",
"Children": [
{
"TypeName": "NumberList",
"Properties": {
"From": 1,
"To": 10
}
},
{
"TypeName": "Formatter",
"Properties": {
"Formatter": "https://news.cnblogs.com/n/page/{0}/"
}
},
{"TypeName":"Url2Html"},
{
"TypeName": "Xpath",
"Properties": {
"Xpath": "//h2[@class=\"news_entry\"]/a",
"ValueProvider": "InnerText"
}
},
{
"TypeName": "DimReduce"
}
]
}
]
}

很显然,通过配置上的改进,这个配置文件已经缩短了不知道多少,配置起来也更加清晰明了。下面是输出的内容,这里使用了一个在控制台输出的仓储实现:



处理节点支持并行运算,基础的ConvertorOptions可以配置这个功能,但是有些实现会忽略这个配置。例如,就上述操作而言,开启并行和不开启并行的情况下的耗时分别是:500ms 和 949ms。如果是前100页的抓取任务的话,那么结果分别是:5374ms 和 9077ms。实验机器的配置是:



注意,这个性能数据可能会因为站点的安全防护措施以及网络带宽的影响变得极其不稳定。

一点心得

  • 要想复杂必须先简单。这些节点之所以能够运转起来,原因是他们的出发点非常简单,就是一个入口一个出口。
  • 要想简单必须单一。在前面的设计中,一个节点仍然考虑了太多的问题,比如如何判断是否要输出集合,在什么时候应该对集合进行降维等等。现在的做法是不做这些特殊处理,让特殊的节点来做这些处理。整个流程更加流畅了。

打包的源代码

在附件中打包了文章中描述的代码的源码,同时包含一个可运行的程序和若干配置。由于代码中使用了局域网内部署的nuget服务器,所以有些包是无法还原的,这里直接把程序集附上。可下载的链接是 :

http://pan.baidu.com/s/1eRPtxYU

编写一个可配置的网页信息提取组件 (二)—— 优雅的.net core 配置系统的更多相关文章

  1. vue项目中编写一个图片预览的公用组件

    今天产品提出了一个查看影像的功能需求. 在查看单据的列表中,有一列是影像字段,一开始根据单据号调用接口查看是否有图片附件,如果有则弹出一个全屏的弹出层,如果没有给出提示.而且,从列表进入详情之后,附件 ...

  2. 开发环境配置--Ubuntu+Qt4+OpenCV(二)

    同系列文章 1. 开发环境配置--Ubuntu+Qt4+OpenCV(一) 2. 开发环境配置--Ubuntu+Qt4+OpenCV(二) 3. 开发环境配置--Ubuntu+Qt4+OpenCV(三 ...

  3. ASP.NET Core配置Kestrel 网址Urls

    ASP.NET Core中如何配置Kestrel Urls呢,大家可能都知道使用UseUrls() 方法来配置. 今天给介绍全面的ASP.NET Core 配置 Urls,使用多种方式配置Urls.让 ...

  4. 10分钟就能学会的.NET Core配置

    .NET Core为我们提供了一套用于配置的API,它为程序提供了运行时从文件.命令行参数.环境变量等读取配置的方法.配置都是键值对的形式,并且支持嵌套,.NET Core还内建了从配置反序列化为PO ...

  5. 【转】10分钟就能学会的.NET Core配置

    .NET Core为我们提供了一套用于配置的API,它为程序提供了运行时从文件.命令行参数.环境变量等读取配置的方法.配置都是键值对的形式,并且支持嵌套,.NET Core还内建了从配置反序列化为PO ...

  6. 从头开始编写一个Orchard网上商店模块(2) - 配置您的Orchard开发环境

    原文地址:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-par ...

  7. Python 网络爬虫 005 (编程) 如何编写一个可以 下载(或叫:爬取)一个网页 的网络爬虫

    如何编写一个可以 下载(或叫:爬取)一个网页 的网络爬虫 使用的系统:Windows 10 64位 Python 语言版本:Python 2.7.10 V 使用的编程 Python 的集成开发环境:P ...

  8. Python 网络爬虫 004 (编程) 如何编写一个网络爬虫,来下载(或叫:爬取)一个站点里的所有网页

    爬取目标站点里所有的网页 使用的系统:Windows 10 64位 Python语言版本:Python 3.5.0 V 使用的编程Python的集成开发环境:PyCharm 2016 04 一 . 首 ...

  9. 编写一个简单的COM组件

    参考网站:编写一个简单的COM组件_a ray of sunshine-CSDN博客 (1) 用MIDL编写.idl文件 //将以下代码保存成 IXIYIZ.idl 文件 //在命令行上进行编译,编译 ...

随机推荐

  1. Nmap原理-01选项介绍

    Nmap原理-01选项介绍 1.Nmap原理图 Nmap包含四项基本功能:主机发现/端口扫描/版本探测/操作系统探测.这四项功能之间存在大致的依赖关系,比如图片中的先后关系,除此之外,Nmap还提供规 ...

  2. Elasticsearch 与 Kafka 整合剖析

    1.概述 目前,随着大数据的浪潮,Kafka 被越来越多的企业所认可,如今的Kafka已发展到0.10.x,其优秀的特性也带给我们解决实际业务的方案.对于数据分流来说,既可以分流到离线存储平台(HDF ...

  3. c++ 库函数返回的字符串指针是否需要手动释放

    #include <stdio.h> char * tmpnam(char *s); tmpnam函数返回一个不与任何已存在文件同名的有效文件名,如果字符串s不为空,文件名也会写入它.对t ...

  4. Java之IO流详解

    IO流 Input/Output 完成输入/输出 应用程序运行时——数据在内存中  ←→ 把数据写入硬盘(磁带)  内存中的数据不可持久保存的  输入:从外部存储器(硬盘.磁带.U盘)把数据读入内存. ...

  5. JavaScript数组知识点

    强类型语言数组特点:连续的,指定好长度, 还要规定好数据类型弱类型语言数组特点:不一定是连续的 可以不用指定长度 不限定数据类型(可以存储任意类型的数据)数组定义方式:1.var arr=new Ar ...

  6. Linux上的防病毒软件ClamAV

    Clam AntiVirus(ClamAV)是免费而且开放源代码的防毒软件,软件与病毒码的更新皆由社群免费发布.目前ClamAV主要是使用在由Linux.FreeBSD等Unix-like系统架设的邮 ...

  7. springmvc 之 SpringMVC视图解析器

    当我们对SpringMVC控制的资源发起请求时,这些请求都会被SpringMVC的DispatcherServlet处理,接着Spring会分析看哪一个HandlerMapping定义的所有请求映射中 ...

  8. js实现htmlToWordDemo

    之前由于工作需要,需要实现将html内的一部分内容直接转为word和pdf的功能.就研究了一下方法并且实现了两个demo.今天先说一下html to word(才疏学浅,仅供交流,如有错误,请指出). ...

  9. 当mysql遇上PHP

    博客提纲 利用PHP连接mySQL数据库 两套接口:面向对象和面向过程 实现写改删查(CUBD)实例 通过prepare语句处理相同类型的不同SQL语句 通过bind_param()绑定参数,及相关注 ...

  10. js的双等号类型转换

    var undefined; undefined == null; // true 1 == true; // true 2 == true; // false 0 == false; // true ...