编写一个可配置的网页信息提取组件 (二)—— 优雅的.net core 配置系统
引言
在上篇文章(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服务器,所以有些包是无法还原的,这里直接把程序集附上。可下载的链接是 :
编写一个可配置的网页信息提取组件 (二)—— 优雅的.net core 配置系统的更多相关文章
- vue项目中编写一个图片预览的公用组件
今天产品提出了一个查看影像的功能需求. 在查看单据的列表中,有一列是影像字段,一开始根据单据号调用接口查看是否有图片附件,如果有则弹出一个全屏的弹出层,如果没有给出提示.而且,从列表进入详情之后,附件 ...
- 开发环境配置--Ubuntu+Qt4+OpenCV(二)
同系列文章 1. 开发环境配置--Ubuntu+Qt4+OpenCV(一) 2. 开发环境配置--Ubuntu+Qt4+OpenCV(二) 3. 开发环境配置--Ubuntu+Qt4+OpenCV(三 ...
- ASP.NET Core配置Kestrel 网址Urls
ASP.NET Core中如何配置Kestrel Urls呢,大家可能都知道使用UseUrls() 方法来配置. 今天给介绍全面的ASP.NET Core 配置 Urls,使用多种方式配置Urls.让 ...
- 10分钟就能学会的.NET Core配置
.NET Core为我们提供了一套用于配置的API,它为程序提供了运行时从文件.命令行参数.环境变量等读取配置的方法.配置都是键值对的形式,并且支持嵌套,.NET Core还内建了从配置反序列化为PO ...
- 【转】10分钟就能学会的.NET Core配置
.NET Core为我们提供了一套用于配置的API,它为程序提供了运行时从文件.命令行参数.环境变量等读取配置的方法.配置都是键值对的形式,并且支持嵌套,.NET Core还内建了从配置反序列化为PO ...
- 从头开始编写一个Orchard网上商店模块(2) - 配置您的Orchard开发环境
原文地址:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-par ...
- Python 网络爬虫 005 (编程) 如何编写一个可以 下载(或叫:爬取)一个网页 的网络爬虫
如何编写一个可以 下载(或叫:爬取)一个网页 的网络爬虫 使用的系统:Windows 10 64位 Python 语言版本:Python 2.7.10 V 使用的编程 Python 的集成开发环境:P ...
- Python 网络爬虫 004 (编程) 如何编写一个网络爬虫,来下载(或叫:爬取)一个站点里的所有网页
爬取目标站点里所有的网页 使用的系统:Windows 10 64位 Python语言版本:Python 3.5.0 V 使用的编程Python的集成开发环境:PyCharm 2016 04 一 . 首 ...
- 编写一个简单的COM组件
参考网站:编写一个简单的COM组件_a ray of sunshine-CSDN博客 (1) 用MIDL编写.idl文件 //将以下代码保存成 IXIYIZ.idl 文件 //在命令行上进行编译,编译 ...
随机推荐
- C#程序遍历数组A中所有元素
] { "a1","a2","a3","a4","a5"}; //第一种方法 ; i < A. ...
- javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG、javaCV-openCV)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- NodeMCU入门(4):搭建Web服务器,配置网络连接
准备工作 1.NodeMCU模块 2.ESPlorer v0.2.0-rc6 3.NodeMCU-HTTP-Server 搭建web服务器 下载https://github.com/wangzexi/ ...
- (转)addEventListener()与removeEventListener()详解
转自:http://www.111cn.net/wy/js-ajax/48004.htm addEventListener()与removeEventListener()用于处理指定和删除事件处理程序 ...
- js判断是否是ie浏览器且给出ie版本
之前懒得写判断ie版本js,因为网上关于这方面的代码太多了,所以从网上拷贝了一个,放到项目上才发现由于时效性的问题,代码不生效.就自己写一个吧. 怎么去看浏览器的内核等信息 ---- js的全局对象w ...
- javascript中event.clientX和event.clientY用法的注意事项
今天做项目用到了event.clientX和event.clientY,给元素定位,用定位的时候,让top和left等于事件元素的的坐标 <!DOCTYPE html> <html& ...
- GCTF2017部分write up
Misc stage1 拿到题看到是一张图片隐写 用神器Stegsolve看看发现左上角藏着一张二维码 截下来,不能直接扫,需要反色处理一下 扫出来是一串十六进制 看头几个十六进制数发现是pyc文件 ...
- MySql数据库基础操作——数据库、用户的创建,表的制作、修改等
MySql 是一款使用便捷.轻量级的数据库.因为他体积小.速度快.安装使用简单.开源等优点,目前是使用最广泛的数据库.目前位于Oracle甲骨文公司旗下.那今天我们就来介绍一下数据库的基本操作.具体介 ...
- rest api get 查询接口 多个参数
查询时,使用get,传递参数至服务器. angular js中,$http可以直接传递object,在get中,params:data 在服务端, query(x=x,y=y)可写成 query(** ...
- 集成python双版本详解
最近要准备学习Python,由于版本上的差异,不知道要学哪个,现在好多东西都是基于python2基础的,但是python2在2020年左右就可能停止了,所以干脆决定两个都装上吧! 首先上官网上下载 ...