Screenshots von Webseiten oder HTML-Dateien in .NET (C#)
Öffnen Sie mit Selenium WebDriver eine Seite in Chrome im Headless-Modus und speichern Sie ihren sichtbaren Viewport als PNG. Die folgende C#-Konsolenanwendung akzeptiert einen lokalen HTML-Pfad oder eine HTTP-URL, wartet auf ein Element, das Bereitschaft signalisiert, und speichert den Screenshot, ohne eine vorhandene Datei zu ersetzen.
Voraussetzungen
Diese Anleitung verwendet Bash unter Linux, das .NET 10 SDK
und einen installierten Chrome- oder Chromium-Browser mit passendem ChromeDriver. Getestet wurde
sie mit SDK 10.0.111 und Chromium/ChromeDriver 152.0.7977.82. Installieren Sie aktuelle
Wartungsupdates für .NET 10, eine aktiv unterstützte LTS-Version.
Für das optionale lokale HTTP-Beispiel benötigen Sie außerdem eine gepflegte Python-3-Installation
mit den Optionen http.server --bind und --directory; getestet wurde es
mit Python 3.14.7.
Rendern Sie nur Seiten und HTML-Dateien, denen Sie vertrauen. Ein Browser kann deren JavaScript ausführen und Netzwerkanfragen senden. Dieses Beispiel erfasst den Viewport einschließlich leerer Bereiche. Es erfasst keine vollständige scrollbare Seite, validiert keine HTTP-Statuscodes und stellt nicht fest, ob eine beliebige Anwendung vollständig geladen ist.
Prüfen Sie die installierten Tools, bevor Sie das Projekt erstellen:
dotnet --version && chromium --version && chromedriver --version
Prüfen Sie für das HTTP-Beispiel Python und vergewissern Sie sich, dass dessen Hilfe beide Optionen aufführt, bevor Sie das Projekt erstellen:
python3 --version && python3 -m http.server --help
Wenn Sie Google Chrome verwenden, ersetzen Sie chromium durch dessen
ausführbare Datei. Setzen Sie diese Pfade auf den tatsächlichen Browser und den passenden Treiber
auf Ihrem Rechner. Dieses Linux-Beispiel verwendet folgende Pfade:
export CHROME_BINARY=/usr/bin/chromium &&
export CHROMEDRIVER_PATH=/usr/bin/chromedriver
Selenium WebDriver verwenden
Beginnen Sie in einem beschreibbaren Verzeichnis außerhalb eines bestehenden .NET-Repositorys.
Übergeordnete SDK-, NuGet- und MSBuild-Konfigurationen können das Verhalten eines neuen Projekts
verändern. Der Einrichtungsblock bricht bei solchen Dateien in übergeordneten Verzeichnissen und
bei einem bereits vorhandenen Ziel WebpageScreenshots ab, bevor er etwas erstellt.
Führen Sie ihn in Bash aus:
(
cd -P . || exit 1
parent="$PWD"
while :; do
for config in global.json NuGet.Config NuGet.config nuget.config \
Directory.Build.props Directory.Build.targets Directory.Packages.props; do
if [ -e "$parent/$config" ]; then
printf 'Choose a directory outside this .NET configuration: %s\n' "$parent/$config" >&2
exit 1
fi
done
[ "$parent" = / ] && break
parent=${parent%/*}
[ -n "$parent" ] || parent=/
done
if [ -e WebpageScreenshots ]; then
printf 'WebpageScreenshots already exists; keep it and choose a new directory.\n' >&2
exit 1
fi
dotnet new console -n WebpageScreenshots --framework net10.0 --no-restore &&
dotnet add WebpageScreenshots package Selenium.WebDriver --version 4.28.0 &&
dotnet add WebpageScreenshots package Selenium.Support --version 4.28.0
)
Die Pakete sind auf die getestete Selenium-Version 4.28.0 festgelegt. Falls die Paketinstallation fehlschlägt, behalten Sie das neue Projekt, beheben Sie das gemeldete NuGet- oder Netzwerkproblem und wiederholen Sie die Schritte zur Installation der Abhängigkeiten im selben übergeordneten Verzeichnis:
dotnet add WebpageScreenshots package Selenium.WebDriver --version 4.28.0 &&
dotnet add WebpageScreenshots package Selenium.Support --version 4.28.0
Ersetzen Sie die generierte Datei WebpageScreenshots/Program.cs durch dieses vollständige Programm.
Die Argumente lauten input output.png width height ready-element-id timeout-seconds. Breite und Höhe beschreiben den Viewport in
CSS-Pixeln; das Zeitlimit gilt separat für die Navigation und das Warten auf Bereitschaft.
using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.Extensions;
using OpenQA.Selenium.Support.UI;
try
{
if (args.Length == 3 && args[0] == "cleanup")
{
int days = int.Parse(args[2], CultureInfo.InvariantCulture);
if (days < 1) throw new ArgumentException("Retention must be at least one day.");
string directory = Path.GetFullPath(args[1]);
DateTime cutoff = DateTime.UtcNow.AddDays(-days);
foreach (string file in Directory.EnumerateFiles(directory, "webshot-*.png"))
{
if (Regex.IsMatch(Path.GetFileName(file), @"\Awebshot-[A-Za-z0-9_-]+\.png\z")
&& File.GetLastWriteTimeUtc(file) < cutoff)
{
File.Delete(file);
Console.WriteLine($"Deleted {file}");
}
}
return 0;
}
if (args.Length != 6)
throw new ArgumentException("Usage: input output.png width height ready-element-id timeout-seconds; or cleanup directory days");
int width = int.Parse(args[2], CultureInfo.InvariantCulture);
int height = int.Parse(args[3], CultureInfo.InvariantCulture);
int seconds = int.Parse(args[5], CultureInfo.InvariantCulture);
if (width < 200 || width > 3840 || height < 200 || height > 2160)
throw new ArgumentException("Choose a viewport from 200x200 to 3840x2160.");
if (seconds < 1 || seconds > 60 || string.IsNullOrWhiteSpace(args[4]))
throw new ArgumentException("Supply a readiness element ID and a timeout from 1 to 60 seconds.");
string url;
if (Uri.TryCreate(args[0], UriKind.Absolute, out Uri? remote)
&& (remote.Scheme == "http" || remote.Scheme == "https"))
{
url = remote.AbsoluteUri;
}
else
{
string path = Path.GetFullPath(args[0]);
if (!File.Exists(path)) throw new FileNotFoundException("HTML file not found.", path);
// Escape each Linux path segment so literal %, #, and ? keep their filename identity.
url = "file://" + string.Join("/", path.Split('/').Select(Uri.EscapeDataString));
}
string outputPath = Path.GetFullPath(args[1]);
if (!outputPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("The output filename must end in .png.");
if (File.Exists(outputPath) || Directory.Exists(outputPath))
throw new IOException("Output already exists; choose a new filename.");
string browser = Environment.GetEnvironmentVariable("CHROME_BINARY")
?? throw new ArgumentException("Set CHROME_BINARY to your browser executable.");
string driverPath = Environment.GetEnvironmentVariable("CHROMEDRIVER_PATH")
?? throw new ArgumentException("Set CHROMEDRIVER_PATH to your driver executable.");
if (!File.Exists(browser) || !File.Exists(driverPath))
throw new FileNotFoundException("Browser or ChromeDriver executable not found.");
var options = new ChromeOptions { BinaryLocation = browser };
options.AddArgument("--headless=new");
using var service = ChromeDriverService.CreateDefaultService(
Path.GetDirectoryName(driverPath), Path.GetFileName(driverPath));
byte[] png;
object result;
using (var driver = new ChromeDriver(service, options, TimeSpan.FromSeconds(30)))
{
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(seconds);
driver.Manage().Window.Size = new System.Drawing.Size(Math.Max(800, width), Math.Max(600, height));
driver.ExecuteCdpCommand("Emulation.setDeviceMetricsOverride", new Dictionary<string, object>
{
["width"] = width,
["height"] = height,
["deviceScaleFactor"] = 1,
["mobile"] = false
});
driver.Navigate().GoToUrl(url);
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(seconds));
wait.Until(d => d.FindElement(By.Id(args[4])).Displayed);
png = driver.TakeScreenshot().AsByteArray;
var window = driver.Manage().Window.Size;
result = new
{
url = driver.Url,
viewportWidth = driver.ExecuteScript("return window.innerWidth"),
viewportHeight = driver.ExecuteScript("return window.innerHeight"),
windowWidth = window.Width,
windowHeight = window.Height,
devicePixelRatio = driver.ExecuteScript("return window.devicePixelRatio")
};
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
using (var output = new FileStream(outputPath, FileMode.CreateNew, FileAccess.Write))
output.Write(png);
Console.WriteLine($"Saved {args[1]}");
Console.WriteLine(JsonSerializer.Serialize(result));
return 0;
}
catch (Exception error)
{
Console.Error.WriteLine($"Screenshot failed: {error.Message}");
return 1;
}
Die Gültigkeitsbereiche von using geben den Browser und den Treiberdienst
sowohl bei regulärem Abschluss als auch bei Ausnahmen frei. Das Programm puffert das PNG, bevor es
das Ziel öffnet. Schlägt die Navigation oder das Warten auf Bereitschaft fehl, entsteht daher kein
Screenshot. FileMode.CreateNew verweigert auch eine Ausgabe, die seit der ersten Prüfung
erstellt wurde. Ein Fehler beim Schreiben auf den Datenträger kann eine unvollständige neue Datei
hinterlassen. Prüfen und entfernen Sie nur diese Datei, bevor Sie es erneut versuchen.
Browserkompatibilität
Diese Implementierung wählt Chrome und ChromeDriver explizit aus. Halten Sie deren Versionen kompatibel; die Anleitung zur Versionsauswahl von ChromeDriver erklärt, wie Sie ein passendes Paar erhalten. Selenium Manager kann Treiber finden und herunterladen, doch dieses Programm verwendet stattdessen die von Ihnen angegebenen Pfade. Firefox und Edge benötigen ihre jeweiligen Treiber- und Optionsklassen; sie sind nicht Teil dieser Anleitung.
Lokale HTML-Dateien erfassen
Speichern Sie diese Seite als WebpageScreenshots/sample.html. Sie hat einen blauen Hintergrund und
eine weiße Karte mit dem Text LOCAL PAGE.
Die Bereitschaftsmarkierung ist sofort vorhanden:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Local screenshot example</title>
<style>
html, body { margin: 0; background: #0c5393; }
#content-loaded { margin: 32px; padding: 24px; background: white; color: black;
font: bold 32px sans-serif; }
</style>
<h1 id="content-loaded">LOCAL PAGE</h1>
</html>
Kompilieren und starten Sie das Programm aus dem übergeordneten Verzeichnis.
&& verhindert, dass nach einem fehlgeschlagenen Build eine ältere
Binärdatei ausgeführt wird, und -- übergibt die übrigen Argumente an die
Anwendung:
dotnet build WebpageScreenshots --disable-build-servers &&
dotnet run --project WebpageScreenshots --no-build -- \
WebpageScreenshots/sample.html screenshots/webshot-local.png 800 600 content-loaded 10
Bei Erfolg gibt das Terminal Saved screenshots/webshot-local.png aus, gefolgt von JSON mit Angaben zur
tatsächlichen URL, zum Viewport, zum äußeren Fenster und zum Pixelverhältnis. Öffnen Sie dieses
PNG: Es sollte 800 × 600 Pixel groß sein, mit der vollständigen Karte
LOCAL PAGE nahe dem oberen Rand und einem blauen
Hintergrund bis zum unteren Rand.
Der Viewport im JSON sollte 800 × 600 betragen, mit einem Pixelverhältnis von eins. Ein nicht leeres
PNG allein belegt nicht, dass Sie die beabsichtigte Seite erfasst haben.
Dynamische Inhalte verarbeiten
Die Navigation wartet auf das Dokument, doch JavaScript kann die Seite danach aktualisieren. Verwenden Sie ein sichtbares Bereitschaftselement, das Ihre Anwendung erst einblendet, wenn der gewünschte Inhalt bereit ist. Ein sichtbares umschließendes Element, dessen Daten noch geladen werden, ist kein geeignetes Signal.
Speichern Sie diese zweite Seite als WebpageScreenshots/delayed.html. Die Karte ist zunächst
verborgen und erscheint nach 1,5 Sekunden mit einem grünen Hintergrund, durch den ein zu früher
Screenshot leicht erkennbar ist:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Delayed screenshot example</title>
<style>
html, body { margin: 0; background: white; }
#content-loaded { margin: 32px; padding: 24px; background: white; color: black;
font: bold 32px sans-serif; }
</style>
<h1 id="content-loaded" hidden>DELAYED PAGE</h1>
<script>
setTimeout(() => {
document.documentElement.style.background = '#198754';
document.body.style.background = '#198754';
document.getElementById('content-loaded').hidden = false;
}, 1500);
</script>
</html>
Prüfen Sie für eine reproduzierbare HTTP-Quelle python3 --version und starten Sie dann
diesen Server im Vordergrund in einem zweiten Terminal aus demselben übergeordneten Verzeichnis.
Er liefert das ausgewählte Verzeichnis über Loopback aus:
python3 -m http.server 8765 --bind 127.0.0.1 --directory WebpageScreenshots
Falls dieser Port belegt ist, wählen Sie einen freien Port und aktualisieren Sie beide URLs unten. Lassen Sie den Server laufen, während Sie die statische und die verzögerte Seite erfassen:
dotnet run --project WebpageScreenshots --no-build -- \
http://127.0.0.1:8765/sample.html screenshots/webshot-http.png 800 600 content-loaded 10 &&
dotnet run --project WebpageScreenshots --no-build -- \
http://127.0.0.1:8765/delayed.html screenshots/webshot-delayed.png 800 600 content-loaded 10
Öffnen Sie beide Dateien. Das statische Bild sollte der blauen lokalen Seite entsprechen; das
verzögerte Bild sollte DELAYED PAGE auf seiner weißen
Karte vor grünem Hintergrund zeigen, mit 800 × 600 Pixeln. Ein vollständig weißes Bild der
verzögerten Seite bedeutet, dass Sie zu früh aufgenommen haben. Stoppen Sie den Server anschließend
mit Strg+C in seinem Terminal.
Bei einer fehlenden oder dauerhaft verborgenen Markierung beendet sich das CLI nach Ablauf der
Wartezeit mit Status eins und ohne neues PNG. Ersetzen Sie für eine reale Anwendung
content-loaded durch deren tatsächliche Bereitschafts-ID.
Screenshot-Speicherung verwalten
Speichern Sie Aufnahmen in einem Verzeichnis, das Ihnen gehört, und wählen Sie für jedes aufzubewahrende Bild einen neuen Dateinamen. Wenn Sie einen Befehl mit einem bereits vorhandenen Ziel wiederholen, schlägt er fehl und erhält diese Datei. Benachbarte PNGs bleiben bei der Aufnahme unberührt.
Für eine optionale Aufbewahrungsregel reservieren Sie Dateinamen, die dem Muster
webshot-[letters, digits, underscores, or hyphens].png entsprechen, für diese Anwendung. Im übergeordneten Verzeichnis
löscht dieser Befehl passende Dateien, deren letzter Schreibzugriff mehr als sieben Tage
zurückliegt, ohne Unterverzeichnisse zu durchsuchen:
dotnet run --project WebpageScreenshots --no-build -- cleanup screenshots 7
Andere Dateinamen bleiben unberührt. Das Präfix ist eine Konvention. Verwenden Sie die Bereinigung daher nur in einem Verzeichnis, das Sie kontrollieren und in dem jede passende Datei zu dieser Anwendung gehört. Um alle Screenshots aufzubewahren, lassen Sie die Bereinigung weg. Ein Löschfehler gibt Status eins zurück; frühere Löschungen in diesem Durchlauf werden nicht rückgängig gemacht.
Unterschiedliche Bildschirmgrößen berücksichtigen
Das CLI verwendet das Überschreiben der Gerätemetriken in Chrome, um den Viewport und ein Pixelverhältnis von eins festzulegen. Das äußere Browserfenster kann größer sein, besonders bei schmalen Breiten. Damit testen Sie CSS-Breakpoints, ohne den User-Agent zu ändern oder eine mobile Touch-Emulation zu aktivieren; ein bestimmtes Telefon wird dadurch nicht nachgebildet.
Behalten Sie für einen schmalen Viewport dieselbe Eingabe und Bereitschaftsmarkierung bei und ändern Sie die Abmessungen:
dotnet run --project WebpageScreenshots --no-build -- \
WebpageScreenshots/sample.html screenshots/webshot-narrow.png 375 667 content-loaded 10
Prüfen Sie, ob das PNG 375 × 667 Pixel groß ist und die Karte lesbar bleibt. Das Programm akzeptiert Breiten von 200 bis 3.840 und Höhen von 200 bis 2.160; größere Aufnahmen liegen außerhalb der Grenzen dieses Beispiels.
Fehlgeschlagene Aufnahmen diagnostizieren
Ermitteln Sie anhand des CLI-Status ungleich null und der Fehlermeldung den fehlgeschlagenen Schritt. Eine fehlende HTML-Datei wird gemeldet, bevor Chrome startet. Ein fehlender oder inkompatibler Browser oder Treiber verhindert den Start einer Sitzung. Prüfen Sie bei einer Zeitüberschreitung beim Warten auf Bereitschaft die ID und ob das Element sichtbar wird; ein höheres Zeitlimit behebt keine falsche Markierung. Prüfen Sie bei einem Ausgabefehler die Verzeichnisberechtigungen und wählen Sie einen noch nicht verwendeten Dateinamen. Bewahren Sie vorherige Screenshots auf, während Sie die Eingabe oder Einrichtung korrigieren, und führen Sie dann denselben Befehl mit einem neuen Ziel erneut aus.
