using Microsoft.Extensions.Logging; using Microsoft.Playwright; namespace FinlyticCore.Services.PlaywrightScrapper; public interface IPlaywrightBrowserFactory : IAsyncDisposable { /// /// Stellt sicher, dass die IBrowser-Instanz verbunden ist. /// Task GetBrowserAsync(CancellationToken cancellationToken = default); /// /// Erstellt einen isolierten, vorkonfigurierten Browser-Kontext. /// Task CreateContextAsync(BrowserNewContextOptions? options = null, CancellationToken cancellationToken = default); } public class PlaywrightBrowserFactory : IPlaywrightBrowserFactory { private readonly ILogger _logger; private readonly SemaphoreSlim _browserLock = new(1, 1); private IPlaywright? _playwright; private IBrowser? _browser; public PlaywrightBrowserFactory(ILogger logger) { _logger = logger; } public async Task 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 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 { ["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); } }