using Microsoft.Extensions.Logging; using OpenQA.Selenium; using OpenQA.Selenium.Edge; namespace BlueLaminate.Scraper.Browser; /// /// Builds a non-headless Edge (Chromium) WebDriver pointed at a local, auth-free /// proxy endpoint (a that chains to the /// residential gateway). Deliberately uses zero CDP: enabling DevTools /// domains — even just to answer proxy auth — is a Cloudflare automation tell, and /// the local proxy already carries the upstream credentials, so there's no 407 to /// answer in the browser. Combined with a warmed, persistent profile this is the /// lowest-fingerprint configuration we can manage without an undetected-chromedriver /// (which has no .NET equivalent). /// /// Bandwidth: the residential plan is metered per GB, so images are disabled at the /// content-settings level by default. Cloudflare gates on JS/TLS/behaviour, not /// whether pictures render, so this stays realistic. /// /// public sealed class BrowserDriverFactory { private readonly ILogger _logger; public BrowserDriverFactory(ILogger logger) { _logger = logger; } /// /// Launch Edge routed through ("host:port", no /// auth). When is set the profile persists across /// runs (so a once-cleared Cloudflare cf_clearance cookie and browsing /// history carry over — a warmed profile looks far less like a fresh bot); when /// null a throwaway profile is used. /// public IWebDriver Create(string? proxyEndpoint, bool blockImages = true, string? profileDir = null) { var options = new EdgeOptions(); // Route browser traffic through the local proxy via the launch argument // rather than EdgeOptions.Proxy (which would also route Selenium Manager's // driver download). No scheme = all protocols use the proxy. When null/empty // the browser uses the machine's direct connection (diagnostic --no-proxy). if (!string.IsNullOrWhiteSpace(proxyEndpoint)) { options.AddArgument($"--proxy-server={proxyEndpoint}"); } // Reduce the most obvious automation tells; residential exit + a real // (non-headless) browser + a warmed profile do the rest. options.AddArgument("--disable-blink-features=AutomationControlled"); options.AddExcludedArgument("enable-automation"); options.AddAdditionalOption("useAutomationExtension", false); options.AddArgument("--no-first-run"); options.AddArgument("--no-default-browser-check"); options.AddArgument("--start-maximized"); var persist = !string.IsNullOrWhiteSpace(profileDir); var dir = persist ? profileDir! : Path.Combine(Path.GetTempPath(), "bluelaminate-edge", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); options.AddArgument($"--user-data-dir={dir}"); if (blockImages) { options.AddUserProfilePreference("profile.managed_default_content_settings.images", 2); } _logger.LogInformation( "Launching Edge via {Route} (profile: {Profile}).", string.IsNullOrWhiteSpace(proxyEndpoint) ? "DIRECT (no proxy)" : $"local proxy {proxyEndpoint}", persist ? dir : "throwaway"); return new EdgeDriver(options); } }