Laden...

Screenshot einer Webseite per WebBrowser-Control

Erstellt von oliverp19 vor 15 Jahren Letzter Beitrag vor 15 Jahren 9.728 Views
O
oliverp19 Themenstarter:in
5 Beiträge seit 2009
vor 15 Jahren
Screenshot einer Webseite per WebBrowser-Control

Moin,

ich spiele gerade ein wenig mit C# rum und möchte von einer Webseite einen Screenshot erstellen. Das klappt prinzipiell auch. Bei einigen Seiten (nicht allen) öffnen sich aber unendlich viele IE-Fenster und ich habe keine Ahnung wieso.

Ich bin noch rechter Anfänger, was C# betrifft...das sollte noch erwähnt werden 😃

Hier mal das Code-Snippet (im Netz gefunden):


WebBrowser wb = new WebBrowser();
wb.ScrollBarsEnabled = false;
wb.ScriptErrorsSuppressed = true;

wb.Navigate(url);

while (wb.ReadyState != WebBrowserReadyState.Complete) { 
  Application.DoEvents();
}

wb.Width = 1024;
wb.Height = 768;

Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
wb.Dispose();


Das Ganze funktioniert, wie schon gesagt, bei vielen URLs ohne Probleme. Eine URL, bei welcher der Fehler auftritt ist beispielsweise "http://www.mal-was-liebes.de". Wenn ich Application.DoEvents(); auskommentiere, öffnen sich zwar keine 1000 IE-Fenster, aber die Prozessorlast geht hoch und das Programm stürzt ab.

Wie könnte ich sonst noch vernünftig abfragen, ob der Browser fertig ist mit dem Laden des Dokumentes.

Vielen Dank & Grüße
Oli

A
10 Beiträge seit 2009
vor 15 Jahren

Der WebBrowser hat ein event namens DocumentCompleted. Dieses kannst du abonnieren und im Eventhandler die Verarbeitung ausführen.

O
oliverp19 Themenstarter:in
5 Beiträge seit 2009
vor 15 Jahren

Vielen Dank! Das hat tatsächlich geholfen...so konnte ich diese komische while-Schleife eliminieren. Hier der Code für die Nachwelt:


            WebBrowser wb = new WebBrowser();
            wb.ScrollBarsEnabled = false;
            wb.ScriptErrorsSuppressed = true;
 
            wb.Navigate(url);

            wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e)
            {
                if (e.Url == wb.Url)
                {
                    wb.Width = 1024;
                    wb.Height = 768;

                    Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
                    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
                    wb.Dispose();
                }

            };

Grüße
Oli

O
oliverp19 Themenstarter:in
5 Beiträge seit 2009
vor 15 Jahren

moin,

ich muss mich doch nochmal melden 😃

Die Anwendung läuft zwar nun prinzipiell so, wie sie soll...nur ist sie sehr instabil. heißt: sie stürzt regelmäßig ab. nun wüsste ich gern, wie ich das ganze etwas stabiler hinbekommen könnte...ich hatte folgende ansätze:

  • auslagern der screenshot-methode in threads (threadpool)
  • festlegen eines timeouts für die screenshot-methode

letzteres erscheint mir zunächst einfacher zu implementieren, allerdings konnte ich da im netz nicht wirklich was sinnvolles finden. hat da evtl. jemand nen tipp für mich, wie ich einen timout in meine methode (siehe oben) integrieren könnte?

wäre die thread-methode sinnvoll? also mehrere threads parallel die screenshots machen zu lassen? ich habe gelesen das webbrowser-objekt ist relativ resourcenlastig.

Dank & Grüße
Oli

1.346 Beiträge seit 2008
vor 15 Jahren

Das wird nicht gehen. Das Webbrowser-Steuerelement kann nur im Form-Thread ausgeführt werden.(Hat mich selber geärgert)

Gruß pdelvo

2.760 Beiträge seit 2006
vor 15 Jahren

heißt: sie stürzt regelmäßig ab.

Hmm.. und mit was für einer Exception?

  • festlegen eines timeouts für die screenshot-methode

Dafür bräuchtest du ja trotzdem Threads da wenn ein Thread hängt es keine Möglichkeit mehr gibt in selbigem einen Timeout festzustellen um ihn dann zu beenden.

O
oliverp19 Themenstarter:in
5 Beiträge seit 2009
vor 15 Jahren

Hallo,

Hmm.. und mit was für einer Exception?

keine Exceptions...das Programm reagiert einfach nicht mehr (keine Rückmeldung).

Dafür bräuchtest du ja trotzdem Threads da wenn ein Thread hängt es keine Möglichkeit mehr gibt in selbigem einen Timeout festzustellen um ihn dann zu beenden.

das scheint mir irgendwie das dilemma zu sein...

Ich möchte einfach Screenshots von einer größeren Anzahl Webseiten machen. Der Input kommt aus einer XML-Datei. Prinzipiell funktioniert eigentlich alles...bis auf 2 Hauptprobleme:

  • wenn eine Webseite nicht geöffnet werden kann, gibt es kein Timout...die Anwendung friert ein

  • bei einer großen Anzahl von Webseiten wird die oben gezeigte Methode direkt nacheinander für alle Webseiten aufgerufen und nicht sequentiell abgearbeitet, wie es eigentlich gedacht war...dann wird für 100 Seiten auf DocumentLoaded-Event gewartet, was leicht dazu führt, dass die Applikation einfriert.

An dieser Stelle komme ich einfach nicht weiter...hat da jemand Hinweise, wie ich sowas angehen könnte?

Grüße
Oli

2.760 Beiträge seit 2006
vor 15 Jahren

Sicher nicht unbedingt ideal aber wahrscheinlich eine (auch nicht gerade Resourcensparende) Abhilfe wäre einfach mehrere Prozesse deiner Anwendung zu starten und einen Management-Prozess darüber zu legen.

Der Managementprozess bekommt das Xml und startet darauf hin das screenshot-Programm n-mal. Das Programm das die Screenshots macht beinhaltet einen Watchdog-Mechanismus der sich regelmäßig beim Manager melden muss um nicht abgeschossen zu werden (Das kann man z.B. über Remoting mit Named-Pipes oder TCP machen).
Wenn du das ganze mit TCP implementierst dann hast du zusätzlich die Möglichkeit die Arbeit auf mehrere Rechner zu verteilen.

L
770 Beiträge seit 2006
vor 15 Jahren

Hallo,

du sagst, du hast den Input aus einer XML Datei. Speichere den Input doch in einer Liste ab, z.B.: List<string> linkToDo. Dann öffnest du den string an der ersten Position, machst über DocumentCompleted den ScreenShot und anschließend, wenn er damit fertig ist, löscht du den string an der ersten Postion.
Somit rutschen alle Links eins nach, du rufst wieder die erste Postion ab (wäre ja der nächste Link, da du den ersten gelöscht hast) und machst erneut deinen Screenshot und das ganze beginnt von vorn.

Und so würde er nacheinander die Links abklappern bis die Liste keine Links mehr beinhaltet.

Zum anderen mach dir einen Timer für ein Timeout, denn du ebenfalls bei jedem DocumentCompleted neustartest.

Hoffe du verstehst was ich meine.
Lion

lg Lion

2.760 Beiträge seit 2006
vor 15 Jahren

anschließend, wenn er damit fertig ist, löscht du den string an der ersten Postion.

Dem wäre wohl eine Queue vorzuziehen.

L
770 Beiträge seit 2006
vor 15 Jahren

Danke, hatte selbst mal mit Queue gearbeitet aber vergessen, dass es sie gibt schäm. Aber das Prinzip stimmt wohl 😃

lg Lion

O
oliverp19 Themenstarter:in
5 Beiträge seit 2009
vor 15 Jahren

...danke Leute für eure Idee. Die Lösung mit der Queue klingt vernünftig. Sobald ich Zeit habe, werde ich mich mal daran versuchen und mich ggf. hier melden.

Schönes Wochenende 😃

Grüße
Oli

94 Beiträge seit 2006
vor 15 Jahren

Ich habe den oben beschriebenen Code versucht zu verwenden. Ich hab mir dazu eine eigene Funktion programmiert, welche mir ein Bitmap zurückliefert. Bei mir funktioniert es mit folgendem Code jedoch nicht. Es wird immer null als Wert für das Bitmap zurückgeliefert.


public static Bitmap getScreenshotFromUrl(string url)
        {
            WebBrowser wb = new WebBrowser();
            wb.ScrollBarsEnabled = false;
            wb.ScriptErrorsSuppressed = true;
            wb.Navigate(url);

            Bitmap bitmap = null;
            wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e)
            {
                if (e.Url == wb.Url)
                {
                    wb.Width = 1280;
                    wb.Height = 1024;

                    bitmap = new Bitmap(wb.Width, wb.Height);
                    //Bildschirmauflösung für Screenshot
                    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
                    wb.Dispose();
                }
            };
            return bitmap;
        }

Ich hoffe ihr könnt mir helfen. Irgenwie scheint er gar nie in den Event wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e) reinzukommen...

www.mkellenberger.ch Was ich täglich (neu) entdecke...

4.931 Beiträge seit 2008
vor 15 Jahren

Du solltest besser wb.DocumentCompleted VOR dem Aufruf wb.Navigate setzen...

94 Beiträge seit 2006
vor 15 Jahren

Hmm... aber das geht doch nicht. Ich muss doch die Website überhaupt mal aufrufen bevor ich prüfen kann, ob sie geladen wurde.

Also so funktionierts genau so wenig. Oder hab ich da was falsch verstanden?


wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e)
            {
                wb.Navigate(url);
                if (e.Url == wb.Url)
                {
                    wb.Width = 1280;
                    wb.Height = 1024;

                    bitmap = new Bitmap(wb.Width, wb.Height);
                    //Bildschirmauflösung für Screenshot
                    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
                    wb.Dispose();
                }
            };

www.mkellenberger.ch Was ich täglich (neu) entdecke...

1.346 Beiträge seit 2008
vor 15 Jahren

Bei deinem Code navigiert er immer wieder zu der Seite, wqenn sie fertig geladen wurde.

Gruß pdelvo

94 Beiträge seit 2006
vor 15 Jahren

Ja. Dachte ich mir. Jedoch geht mein 1. oben geposteter Code auch nicht. Fehlt da noch was?

www.mkellenberger.ch Was ich täglich (neu) entdecke...

4.931 Beiträge seit 2008
vor 15 Jahren

Sorry, aber ich glaube, du hast die Events noch nicht verstanden (und das obwohl du anonyme Methoden benutzt).
Das Abonnieren eines Events mußt du vor dem Aufruf "Navigate()" durchführen, denn sonst kann die Methode ja nicht wissen, was sie machen muß, wenn das Dokument fertig geladen ist.
Und wo die Event-Methode im Code definiert ist (ob als anonyme Methode oder als eigenständige Methode) ist völlig egal.

Also richtig ist:


wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e)
            {
                if (e.Url == wb.Url)
                {
                    wb.Width = 1280;
                    wb.Height = 1024;

                    bitmap = new Bitmap(wb.Width, wb.Height);
                    //Bildschirmauflösung für Screenshot
                    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
                    wb.Dispose();
                }
            }

wb.Navigate(url);

Wenn du den Debugger benutzt, dann wirst du sehen, in welcher Reihenfolge er die Methoden durchläuft...

Edit: immer diese Fehlerteufelchen

94 Beiträge seit 2006
vor 15 Jahren

Das geht irgendwie auch nicht. Man der kommt nach dem Navigate nicht in den Event rein obwohl er das meiner Ansicht nach müsste.

Ich häng hier gleich mal die ganze Klasse an, dann kann man damit nach belieben rumexperimentieren. Ich stoss da leider mit meinen Programmierkenntnissen an unüberwindbare Hürden...


using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace WebScreenGrabber
{
    class Screenshot
    {
        /// <summary>
        /// Macht automatisch einen Screenshot einer Website und gibt diesen als Bitmap zurück.
        /// </summary>
        /// <param name="url">URL der Website</param>
        /// <returns>Screenshot</returns>
        public static Bitmap getScreenshotFromUrl(string url)
        {
            WebBrowser wb = new WebBrowser();
            wb.ScrollBarsEnabled = false;
            wb.ScriptErrorsSuppressed = true;

            Bitmap bitmap = null;
            wb.DocumentCompleted += delegate(object o, WebBrowserDocumentCompletedEventArgs e)
            {
                if (e.Url == wb.Url)
                {
                    wb.Width = 1280;
                    wb.Height = 1024;

                    bitmap = new Bitmap(wb.Width, wb.Height);
                    //Bildschirmauflösung für Screenshot
                    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
                    wb.Dispose();
                }
            };

            wb.Navigate(url);

            return bitmap;
        }
    }
}


www.mkellenberger.ch Was ich täglich (neu) entdecke...

4.931 Beiträge seit 2008
vor 15 Jahren

Ich habe deine Methode mal mit


getScreenshotFromUrl("http://www.google.de");

aufgerufen und komme dann in das DocumentCompleted-Event.

Dein Hauptproblem ist aber, daß du einfach nach "Navigate" sofort das (noch leere) Bitmap-Objekt zurückgibst...

Die Methode Navigate arbeitet aber asynchron, d.h. sie kehrt sofort wieder zurück.
Der Event wird aber erst nach einiger Zeit ausgelöst, so daß das Bitmap-Objekt erst dann gesetzt wird.

Du mußt also noch auf die Abarbeitung warten (am besten in einem eigenen Thread).
Zum Test kannst du auch ersteinmal die Schleife (aus em Anfangspost) verwenden:


wb.Navigate(url);

while (wb.ReadyState != WebBrowserReadyState.Complete) {
  Application.DoEvents();
}

return bitmap;

s. a. unbedingt den FAQ-Beitrag
[FAQ] Warum blockiert mein GUI?

94 Beiträge seit 2006
vor 15 Jahren

Vielen Dank für deine Antwort.
Ich werds mal wieder mit der Schleife machen.
Aber bringt die was? Ich meine, anstelle dieser sollte ich ja den DocumentCompleted-Event verwenden. nicht?

Ich werds aber auf alle Fälle nochmal so probieren und Rückmeldung geben ob nun alles klappt.

Achja, das WebBrowser-Control lässt sich leider soviel ich weiss nicht in einem eigenen Thread starten... 😦

www.mkellenberger.ch Was ich täglich (neu) entdecke...

L
770 Beiträge seit 2006
vor 15 Jahren

waum machst du es nicht auf die klassische art und hängt eine Methode dran?
Wenn du paarmal Tab nachdem += drückst, füllt er eh alles automatisch aus bzw. kannst du auch selbst codieren.

Lion

lg Lion

94 Beiträge seit 2006
vor 15 Jahren

Sry...ich kann mir nicht gerade vorstellen wie das funktionieren soll..

Kannst du mir nicht evtl gleich den entsprechenden Code hier posten?

Vielen Dank schon im voraus und ihr seid echt eine Hilfe.

Da gibt es ganz andere Foren im Netz...

www.mkellenberger.ch Was ich täglich (neu) entdecke...

L
770 Beiträge seit 2006
vor 15 Jahren
WebBrowser wb = new WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);

void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
        throw new NotImplementedException();
}

lg Lion