引言

在上篇文章(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. 关于XAMPP环境配置

     关于XAMPP软件 * Apache - 软件服务器(运行PHP) * 启动失败 * 原因 - 端口号被占用 * 错误信息 - Error: Apache shutdown unexpectedly ...

  2. Vue 非父子组件通信

    组件是Vue核心的一块内容,组件之间的通信也是很基本的开发需求.组件通信又包括父组件向子组件传数据,子组件向父组件传数据,非父子组件间的通信.前两种通信Vue的文档都说的很清楚,但是第三种文档上确只有 ...

  3. phantomjs-prebuilt@2.1.14 install: `node install.js`

    在用vue-cli构建项目时,npm install 安装包的时候报错了. 错误信息: npm ERR! Failed at the phantomjs-prebuilt@2.1.14 install ...

  4. lvs之 lvs原理架构介绍

    一. 概念 lvs的术语: Router:GWIP vs:virtual server,director rs:real server CIP:client IP VIP:virtual server ...

  5. golang 浮点数 取精度的效率对比

    需求 浮点数取2位精度输出 实现 代码 package main import ( "time" "log" "strconv" " ...

  6. php 面向对象的三大特性

    <?phpheader("Content-type:text/html;charset=utf-8");/*封装目的:为了使类更加安全做法:1.将成员变量变成私有2.做一个成 ...

  7. js获取页面宽高

    网页可见区域宽:document.body.clientWidth网页可见区域高:document.body.clientHeight网页可见区域宽:document.body.offsetWidth ...

  8. grid表格选择模式

    selModel: { // type: 'checkboxmodel', type: 'cellmodel', // mode: 'SIMPLE', mode: 'SINGLE', checkOnl ...

  9. ICG_System之全自动代码生成器V2.0版本

    大家好! 早在2014年本人就已经利用业余时间开发自己的ICG之代码生成器系统.依靠bootstrap的崛起本人也在不断完善此应用.目的是为了减少开发人员的工作量. 减少不必要的复制粘贴操作,该系统已 ...

  10. 2016-12-30 PHP JS

    1:Js 控制图片样式 2:PHP WEB