引言

在上篇文章(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. javaSE_07Java中类和对象-封装特性--练习

    1.编写封装一个学生类,有姓名,有年龄,有性别,有英语成绩,数学成绩,语文成绩,一个学生类,我们关注姓名,年龄,学历等信息,要求年龄必须在19-40岁之间,默认为19,学历必须是大专,本科,研究生这几 ...

  2. 安装JDK,配置环境变量有感

    前天无事,心血来潮给公司新配的笔记本(win10系统64位)装开发工具,然后不可避免的就装了JDK,顺理成章的需要配置环境变量,结果就出问题了. 配置完成,测试时,在dos命令窗口输入java命令执行 ...

  3. Linux命令 查看及修改文件属性

    chmod [功能说明] 改变文件的访问权限  #Linux中访问权限分为:文件属主(文件的创建者)文件组属主(创建者所处的组)和其他(其他用户) [语法格式] Chmod[参数]mode[文件名或目 ...

  4. octomap中3d-rrt路径规划

    路径规划 碰撞冲突检测 在octomap中制定起止点,目标点,使用rrt规划一条路径出来,没有运动学,动力学的限制,只要能避开障碍物. 效果如下: #include "ros/ros.h&q ...

  5. 教你轻轻松松入门PHP

    入门PHP不用愁,跟我来学一学. 1.我们想学习PHP,首先就要了解PHP,那PHP是什么呢? PHP("PHP: Hypertext Preprocessor",超文本预处理器的 ...

  6. Bash On Windows的学习

    Bash On Windows的学习 Bash On Windows的卸载 删除软件和设置:在 cmd 运行lxrun /uninstall 删除所有文件:在cmd中运行lxrun /uninstal ...

  7. unity3d项目导入android studio

    第一步 拿到unity3d项目,里面包含以下文件 第二步 在android studio下新建一个project 第三步 将unity3d项目目录下的libs下的jar文件复制黏贴到android s ...

  8. dubbo 请求调用过程分析

    服务消费方发起请求 当服务的消费方引用了某远程服务,服务的应用方在spring的配置实例如下: <dubbo:referenceid="demoService"interfa ...

  9. asp.net使用qq邮箱发送邮件

    using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Ne ...

  10. 【Android Developers Training】 103. 查询当前地点

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...