Репозиторий содержит в себе все необходимые (мне) структуры и классы для возможности парсинга HTML-верстки
Весь парсинг построен на принципе конвейерной обработки странички, где каждый обработчик в конвейере независимо обрабатывает исходную HTML-страницу и инициализирует необходимые поля итогового типа. Достоинством, что я хочу отметить, данного подхода является то, что благодаря изолированной модели обработки исходной страницы можно просто переключаться и тестировать отдельные модули данного конвейера. Также, данный способ призван оптимизировать процесс отладки программного кода с целью выявления ошибок.
- IParsingSource - возможность авторизации, если необходимо и возможность работы с Cookies.
- ParsingPipeline - в этом конвейере собираются все промежуточные обработчики (ParsingMiddleware), которые обрабатывают определённый участок данных, за который они ответственны.
- ParsingMiddleware - промежуточный обработчик данных из источника. Принцип работы основан на принципе устройства Middlewares в ASP.NET Core. Отвечает за обработку определенной информации из данных с источника. Для передачи определенных данных в другие ParsingMiddleware использовать можно механизм Dependency Injection.
- MiddlewareContext - контекст Middleware-обработчика. В нем хранятся:
- Экземпляр источника (
IParsingSource
), с которого происходит непосредственное получение данных; - Экземпляр
ParsingMiddleware
, являющийся следующим в очереди обработки данных; - Экземпляр
IServiceCollection
, необходимый для передачи в другие Middlewares зарегистрированных зависимостей; - Экземпляр
IServiceProvider
, служащий для доступа к зарегистрированным сервисам.
- Экземпляр источника (
- Microsoft.Extensions.Hosting → v6.0.1
var source = new MicrosoftSource("https://www.microsoft.com/ru-ru/");
var pipe = new ParsingPipeline<MicrosoftEntity, MicrosoftSource>(source)
.Use<InitializerMiddleware>()
.Use<NewsParsingMiddleware>()
.Use<ProductsParsingMiddleware>();
var resultEntity = await pipe.StartAsync();
Console.WriteLine(resultEntity);
Во-первых, создается источник данных, откуда будет происходить получение, извиняюсь, данных. Источник должен реализовывать интерфейс IParsingSource
или костомный ITextParsing
.
Во-вторых, регистрируется обрабатываемого типа (в примере: MicrosoftEntity
). Данный тип будет создан в объекте класса ParsingPipeline
(для этого он должен иметь хотя бы один пустой конструктор).
В-третьих создается конвейер ParsingPipeline
, можно не утруждаться и не писать свой - этого хватит с лихвой. Через метод Use<T>()
указываются все пользовательские Middlewares, наследуемые от класса ParsingMiddleware
. Экземпляры указанных типов будут инициализированы внутри ParsingPipeline
.
В-четвертых, для обработки данных потребуются, непосредственно, сами обработчики, о которых упоминалось ранее. Обработчики (ParsingMiddleware
'ы) должны реализовать абстрактный метод ProcessAsync()
, одним параметром которого является обрабатываемый тип (в примере: MicrosoftEntity
). В каждом обработчике можно также переопределить метод Process(TResult)
. По умолчанию он реализуется следующим образом:
public virtual void Process(TResult toProcess) =>
ProcessAsync(toProcess).ConfigureAwait(false);
public abstract Task ProcessAsync(TResult toProcess);
И наконец то появилась возможность внедрения зависимостей в собственные ParsingMiddlewares
private static async Task<int> Main()
{
ConfigureLogger();
var pipe = new ParsingPipeline<List<MeMangaItem>, MeSource>()
.HandleAll<ExceptionHandleMiddleware>()
.Use<InitializeMiddleware>()
.Use<PagesParsingMiddleware>()
.Use<PreviewsParsingMiddleware>()
.Use<MangaParsingMiddleware>()
.Use<FilesParsingMiddleware>()
.OnHostBuilding(host => host.UseSerilog())
.WithServices(services =>
{
services.AddSingleton<HttpClientWrapper>();
services.AddSingleton(permission => GetAccessPermission());
});
var result = await pipe.StartAsync();
if (result.Status == ExecutionStatus.Ok)
return 0;
else return 1;
}
internal class InitializeMiddleware : ParsingMiddleware<List<MeMangaItem>, MeSource>
{
public InitializeMiddleware(IConfiguration config) =>
_config = config;
private readonly IConfiguration _config;
public override async Task ProcessAsync(List<MeMangaItem> toProcess)
{
var helper = Context.ServiceProvider.GetService<QueryHelper>();
await Context.Source.AuthorizeAsync();
Context.Services.AddSingleton<IHtmlParser, HtmlParser>();
await InvokeNextAsync(toProcess);
}
}
Для внедрения зависимостей используется метод OnHostBuilding()
и WithServices()
. Для того, чтобы пользоваться DI внутри middlewares необходимо зависимости внедрять внутри самих Middlewares, используя:
Context.Services.AddSingleton<IService, Service>();
Поскольку используются 2 разных ServiceContainer при инициализации Middleware и при использовании внутри них.
Также после выполнения парсинга в результате выполнения pipe.StartAsync()
будет получен объект типа PipelineExecutionResult<TResult>
, имеющий следующую структуру:
public class PipelineExecutionResult<TResult>
{
internal PipelineExecutionResult() { }
public TResult Content { get; internal set; }
public ExecutionStatus Status { get; internal set; }
}
Используя для валидации результирующих данных, воспользуйтесь ExecutionStatus
ExecutionStatus Ok;
ExecutionStatus HandleError;
ExecutionStatus NotHandleError;
Он сообщит, по какой причине был получен результат и было ли это обработано, либо же нет.
Не стоит забывать, что главной идеей данной библиотеки, в первую очередь, является возможность обеспечение удобства выгрузки контента с сайтов, файлов и чего угодно. И одним из аспектов, которые этому препятствуют является обработка ошибок. Чтобы не раздувать код в Ваших посредниках-обработчиках я думаю над возможностью обработки полученных в результате работы исключений, которые могут быть обработаны другими Middlewares
, либо же обработчиком, в котором произошло само исключение