feat(core): update DTOs, Trade Republic client, Yahoo scrapers, and dynamic settings

This commit is contained in:
2026-08-14 23:55:02 +02:00
parent 3d8af3940b
commit 4f733bf8c3
511 changed files with 1990 additions and 1080548 deletions
@@ -0,0 +1,60 @@
using Microsoft.Playwright;
namespace FinlyticCore.Services.PlaywrightScrapper;
public interface IPlaywrightExecutionService
{
/// <summary>
/// Führt eine Scrape-Aktion auf einer einzelnen Seite innerhalb eines isolierten Kontexts aus.
/// Der Kontext und die Page werden automatisch nach der Ausführung disposed.
/// </summary>
Task<T> ExecuteInPageAsync<T>(
Func<IPage, Task<T>> action,
BrowserNewContextOptions? contextOptions = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Führt eine Multi-Page Scrape-Aktion (z. B. bei Tabs/Popups) in einem Konfiguration-Kontext aus.
/// </summary>
Task<T> ExecuteInContextAsync<T>(
Func<IBrowserContext, Task<T>> action,
BrowserNewContextOptions? contextOptions = null,
CancellationToken cancellationToken = default);
}
public class PlaywrightExecutionService : IPlaywrightExecutionService
{
private readonly IPlaywrightBrowserFactory _browserFactory;
public PlaywrightExecutionService(IPlaywrightBrowserFactory browserFactory)
{
_browserFactory = browserFactory;
}
public async Task<T> ExecuteInPageAsync<T>(
Func<IPage, Task<T>> action,
BrowserNewContextOptions? contextOptions = null,
CancellationToken cancellationToken = default)
{
await using var context = await _browserFactory.CreateContextAsync(contextOptions, cancellationToken);
var page = await context.NewPageAsync();
try
{
return await action(page);
}
finally
{
await page.CloseAsync();
}
}
public async Task<T> ExecuteInContextAsync<T>(
Func<IBrowserContext, Task<T>> action,
BrowserNewContextOptions? contextOptions = null,
CancellationToken cancellationToken = default)
{
await using var context = await _browserFactory.CreateContextAsync(contextOptions, cancellationToken);
return await action(context);
}
}
@@ -0,0 +1,95 @@
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
namespace FinlyticCore.Services.PlaywrightScrapper;
public interface IPlaywrightBrowserFactory : IAsyncDisposable
{
/// <summary>
/// Stellt sicher, dass die IBrowser-Instanz verbunden ist.
/// </summary>
Task<IBrowser> GetBrowserAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Erstellt einen isolierten, vorkonfigurierten Browser-Kontext.
/// </summary>
Task<IBrowserContext> CreateContextAsync(BrowserNewContextOptions? options = null, CancellationToken cancellationToken = default);
}
public class PlaywrightBrowserFactory : IPlaywrightBrowserFactory
{
private readonly ILogger<PlaywrightBrowserFactory> _logger;
private readonly SemaphoreSlim _browserLock = new(1, 1);
private IPlaywright? _playwright;
private IBrowser? _browser;
public PlaywrightBrowserFactory(ILogger<PlaywrightBrowserFactory> logger)
{
_logger = logger;
}
public async Task<IBrowser> GetBrowserAsync(CancellationToken cancellationToken = default)
{
if (_browser != null && _browser.IsConnected) return _browser;
await _browserLock.WaitAsync(cancellationToken);
try
{
if (_browser != null && _browser.IsConnected) return _browser;
_playwright ??= await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu"
}
});
_logger.LogInformation("[PlaywrightFactory] Shared Chromium Instance successfully launched.");
return _browser;
}
finally
{
_browserLock.Release();
}
}
public async Task<IBrowserContext> CreateContextAsync(BrowserNewContextOptions? options = null, CancellationToken cancellationToken = default)
{
var browser = await GetBrowserAsync(cancellationToken);
options ??= GetDefaultContextOptions();
return await browser.NewContextAsync(options);
}
public static BrowserNewContextOptions GetDefaultContextOptions() => new()
{
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
ViewportSize = new ViewportSize { Width = 1280, Height = 900 },
Locale = "en-US",
ExtraHTTPHeaders = new Dictionary<string, string>
{
["Accept-Language"] = "en-US,en;q=0.9"
}
};
public async ValueTask DisposeAsync()
{
if (_browser != null)
{
await _browser.CloseAsync();
await _browser.DisposeAsync();
}
_playwright?.Dispose();
_browserLock.Dispose();
GC.SuppressFinalize(this);
}
}