129 lines
3.8 KiB
C#
129 lines
3.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FinlyticCore.Models.Settings;
|
|
using FinlyticCore.Services;
|
|
using Microsoft.Playwright;
|
|
|
|
namespace FinlyticCore.Services.PlaywrightScrapper;
|
|
|
|
public interface IPlaywrightBrowserFactory : IAsyncDisposable, IDisposable
|
|
{
|
|
/// <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 IFinlyticLogger<PlaywrightBrowserFactory> _finlyticLogger;
|
|
private readonly SemaphoreSlim _browserLock = new(1, 1);
|
|
|
|
private IPlaywright? _playwright;
|
|
private IBrowser? _browser;
|
|
|
|
public PlaywrightBrowserFactory(IFinlyticLogger<PlaywrightBrowserFactory> finlyticLogger)
|
|
{
|
|
_finlyticLogger = finlyticLogger;
|
|
}
|
|
|
|
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"
|
|
}
|
|
});
|
|
|
|
await _finlyticLogger.LogInfoAsync(CoreSettingKeys.PlaywrightChannel, "[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 void Dispose()
|
|
{
|
|
try
|
|
{
|
|
if (_browser != null)
|
|
{
|
|
_browser.CloseAsync().GetAwaiter().GetResult();
|
|
_browser.DisposeAsync().GetAwaiter().GetResult();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore any sync disposal timeouts
|
|
}
|
|
finally
|
|
{
|
|
_playwright?.Dispose();
|
|
_browserLock.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_browser != null)
|
|
{
|
|
try
|
|
{
|
|
await _browser.CloseAsync();
|
|
await _browser.DisposeAsync();
|
|
}
|
|
catch
|
|
{
|
|
// Ignore disposal errors
|
|
}
|
|
}
|
|
|
|
_playwright?.Dispose();
|
|
_browserLock.Dispose();
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
} |