編寫一個可配置的網頁信息提取組件 (二)—— 優雅的.net core 配置系統

引言

在上篇文章(http://www.cnblogs.com/lightluomeng/p/7212577.html)中,初步實現了一個可配置的網頁信息分析組件。可是因爲是奔着解決事情的目的去的,因此寫的比較匆忙,不少細節方面的問題沒有仔細考慮,因此存在很多問題。主要問題有:html

  • 配置很是不人性化。不人性化到什麼程度呢...我本身配置了一個須要抓取多重列表同時中間須要分析鏈接而後跳轉的頁面,足足寫了500行的配置文件。並且每一個節點的類型名稱的編寫這裏沒有作優化,致使名稱空間很長,很累,並且容易出錯。
  • 關於返回信息降維沒有處理好,因此在實現新的功能節點的時候很容易出錯。
  • 日誌作的不夠好,不可以經過錯誤信息推斷哪裏出了問題。
  • 配置還不夠到位。仍然須要很多的代碼來把整個流程串聯起來。沒有作到外部程序集加載。

設計上的改進

  • 再也不靜默處理降維(集合收斂),如今使用一個DimReduceConvertor來將二維數組降維到一位數組,或者將更高維度的數組降維到低一個維度的數組
  • 再也不靜默的判斷是不是集合,如今使用一個ProcessedList來將數據顯著的標記爲數組,降維操做也會基於這個判斷進行
  • 移除了ICollector,將此接口上的Key屬性定義放在了基礎的IValueConvertor上,這樣更好的保證了整個樹形結構的一致性,同時能夠顯著的減小嵌套結構
  • 引入了IValuePersistence,用來解決處理後的值的持久化的問題
  • 全部元件都經過構造函數注入的方式引用了ILogger
  • 大量應用了IOptions模式,從而能夠以全局的方式配置一些必要的信息,減小單個處理節點的配置的複雜度
  • 引入了ITypeNameResolver從而使得單個節點在指定名稱的時候可使用簡寫,下降配置難度;引入了其餘的ITypeResolver從而使自動化注入和配置成爲可能

目前總體的類型繼承關係以下(部分類型未展現):json

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"
          }
        ]
      }
    ]
  }

很顯然,經過配置上的改進,這個配置文件已經縮短了不知道多少,配置起來也更加清晰明瞭。下面是輸出的內容,這裏使用了一個在控制檯輸出的倉儲實現:app


處理節點支持並行運算,基礎的ConvertorOptions能夠配置這個功能,可是有些實現會忽略這個配置。例如,就上述操做而言,開啓並行和不開啓並行的狀況下的耗時分別是:500ms 和 949ms。若是是前100頁的抓取任務的話,那麼結果分別是:5374ms 和 9077ms。實驗機器的配置是:ide


注意,這個性能數據可能會由於站點的安全防禦措施以及網絡帶寬的影響變得極其不穩定。函數

一點心得

  • 要想複雜必須先簡單。這些節點之因此可以運轉起來,緣由是他們的出發點很是簡單,就是一個入口一個出口。
  • 要想簡單必須單一。在前面的設計中,一個節點仍然考慮了太多的問題,好比如何判斷是否要輸出集合,在何時應該對集合進行降維等等。如今的作法是不作這些特殊處理,讓特殊的節點來作這些處理。整個流程更加流暢了。

打包的源代碼

在附件中打包了文章中描述的代碼的源碼,同時包含一個可運行的程序和若干配置。因爲代碼中使用了局域網內部署的nuget服務器,因此有些包是沒法還原的,這裏直接把程序集附上。可下載的連接是 :性能

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

相關文章
相關標籤/搜索