- PR -

WebBrowser の DocumentCompleted の発生タイミングが、画像のロードを待つので遅い

1
投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-06-24 12:56
Visual C# 2008 でいわゆる Web 巡回ツールのような Windows Application を作っています。この中で WebBrowser コントロールをフォームに貼り付けて使っています。自動巡回させるために、アクセスしたページ中のボタンやハイパーリンクを押す操作をおこなうきっかけには、WebBrowser の DocumentCompleted イベントを使っています。

しかし、このイベントは、対象とするページに画像などがあると、その画像のロードが完了してから起こるらしく、画像が大きかったり HTTP/1.1 を使っていなかったりするような Web サイトだと、イベントの発生時期が遅く、アプリケーションの動きがもっさりしてしまいます。
たとえば、ページが、
コード:
<html>
<body>
<img src="〜" alt="どうでもいいでかい画像">
<form>
<input type="submit">
</form>
<a href="〜">次のページ</a>
</body>
</html>


だった場合、<body> 〜 </body> の部分を読み込んだら、img タグなどの画像のロードを待たずに body の中にあるボタン(input type=submit)やハイパーリンク(a href)を押したいのですが、そのようなことはできるでしょうか?

試しに、
コード:
using System;
using System.Windows.Forms;

namespace Hoge
{
    public partial class Form1 : Form
    {
        private void MyBodyOnLoad(object o, EventArgs ea)
        {
            Console.WriteLine("MyBodyOnLoad");
        }

        public Form1()
        {
            InitializeComponent();

            // webBrowser1.Document.Body.AttachEventHandler("onload", new EventHandler(MyBodyOnLoad));
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            Console.WriteLine("DocumentCompleted");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            webBrowser1.Navigate("http://www.example.com/");
        }
    }
}


のように AttachEventHandler で body の onload を登録すればよいのかとも思ったのですが、ページにアクセスしないことには body のインスタンスが null のままだし、そもそも WebBrowser コントロールに限らず、body の onload は body に含まれる img のロードが完了した後に起こるものらしく、これではぜんぜんダメだと思いました。
test
会議室デビュー日: 2008/04/01
投稿数: 9
お住まい・勤務地: 古の都
投稿日時: 2008-06-24 13:05
引用:

unibonさんの書き込み (2008-06-24 12:56) より:
しかし、このイベントは、対象とするページに画像などがあると、その画像のロードが完了してから起こるらしく、画像が大きかったり HTTP/1.1 を使っていなかったりするような Web サイトだと、イベントの発生時期が遅く、アプリケーションの動きがもっさりしてしまいます。



画像を読み込まないようにしたらまずいのかな?

http://jumbofoot.cocolog-nifty.com/yass_vbnet_tips/2006/09/webbrowser_1de3.html
こんな感じで
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2008-06-24 16:26
引用:

unibonさんの書き込み (2008-06-24 12:56) より:

Visual C# 2008 でいわゆる Web 巡回ツールのような Windows Application を作っています。この中で WebBrowser コントロールをフォームに貼り付けて使っています。自動巡回させるために、アクセスしたページ中のボタンやハイパーリンクを押す操作をおこなうきっかけには、WebBrowser の DocumentCompleted イベントを使っています。


DocumentCompleted イベントはそういうイベントなのでしょうがないですよね。 ちなみに ReadyState は Complete まで待つ必要はなく Interactive の時には操作可能です。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-06-24 23:14
引用:

testさんの書き込み (2008-06-24 13:05) より:
画像を読み込まないようにしたらまずいのかな?

http://jumbofoot.cocolog-nifty.com/yass_vbnet_tips/2006/09/webbrowser_1de3.html
こんな感じで


ありがとうございます。かなり高度なことをやる必要があるみたいですね。VB.NET を使ったことがないのですぐには試せないのですが、ちなみに、IE の設定で手動で、「画像を表示する」をオフにすれば、たしかに速くなります。

引用:

じゃんぬねっとさんの書き込み (2008-06-24 16:26) より:
DocumentCompleted イベントはそういうイベントなのでしょうがないですよね。 ちなみに ReadyState は Complete まで待つ必要はなく Interactive の時には操作可能です。


ありがとうございます。タイマーで ReadyState を0.5秒位の間隔でポーリングしてみることを試してみました。簡単な構造のサイトにアクセスすると、しばらくの間はサクサク動いたのですが、私の今のプログラムでは DocumentCompleted の引数である WebBrowserNavigatingEventArgs で URL を取得していることに、今、気づきました。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=43920&forum=7
のような問題です。すなわち、DocumentCompleted をタイミングを取得するほかに、この引数を得るためにも使っていました。

したがって、DocumentCompleted の発生前にこの情報をどうやって取得するかが、新たな問題になってしまいました。
また、フレーム構造になっていると、ReadyState の値の遷移がどうなっているのかが、イマイチ不明です。

なお、その投稿の中で、私は自分で、

> ちなみに Navigated イベントを調べていたら、その前に発生する Navigation イベントのほうには、引数に frame の name が入っているようなので、これが使えないものかなとも思っています。

と書いているので、このへんが使えるのかな、とも思うのですが、ちょっと考えてみます。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-06-28 15:02
その後、試行錯誤して、つぎのようなコードである程度、目的が達成できました。

ロジックとしては、Navigated イベントで url を取得してそれをフィールドに保存し、webBrowser1.Document.Body に body の内容が設定された時点で、その url を取得して使うことができます(下記の例では取得はするけど使っていませんが)。これにより、DocumentCompleted を使っているプログラムをこのやりかたで挿げ替えることができるはずです。

また、タイマーでポーリングするよりは ProgressChanged のタイミングで調べたほうがわざわざタイマーを設置する必要もなく、ダウンロードが進んだタイミングで処理できるので、こっちのほうが良さそうでした。

ただ、まだ大きな課題があって、body が <body> から </body> まできちんとダウンロードできたかどうか、ということが取得できません。ReadyState が Interactive であっても webBrowser1.Document.Body == null のときもあり、あまり便利に使えそうな状態ではないようです。
今のところ、特定の Web サイトだったら、ページの末尾にありそうな文字列がダウンロードしたコンテンツに含まれているかどうかで判定できますが、きちんとダウンロードが完了したかを厳密には判定できません。

また、まだ確認はしていませんが、フレーム構造だと、これでもうまく行かなさそうな気がします。

コード:
using System;
using System.Windows.Forms;

namespace Hoge
{
    public partial class Form1 : Form
    {
        private Uri url;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        }

        private void Process()
        {
            // DocumentCompleted イベントの引数と互換な情報。
            WebBrowserDocumentCompletedEventArgs e = new WebBrowserDocumentCompletedEventArgs(url);

            // 以下は、アプリケーション固有のテキトーな実装の例。
            // ここでは、body 内の複数のリンクの中から乱数で1つ選んで click して巡回する例。

            HtmlElement body = webBrowser1.Document.Body;
            if (body == null)
            {
                throw new Exception();
            }
            else
            {
                HtmlElementCollection hec = body.GetElementsByTagName("a");
                if (hec.Count == 0)
                {
                    MessageBox.Show("リンクが見つかりません。");
                }
                else
                {
                    Random random = new Random();
                    int i = random.Next(hec.Count);
                    HtmlElement he = hec[i];
                    Console.WriteLine("href = " + he.GetAttribute("href"));
                    he.InvokeMember("click");
                }
            }
        }

        private void Callback(bool force)
        {
            if (url != null)
            {
                if (webBrowser1.ReadyState == WebBrowserReadyState.Interactive
                    || webBrowser1.ReadyState == WebBrowserReadyState.Complete)
                {
                    bool exec;
                    if (force)
                    {
                        exec = true;
                    }
                    else
                    {
                        if (webBrowser1.Document.Body != null
                            && webBrowser1.Document.Body.InnerHtml.Contains("〜body の終わり付近に含まれる文字列〜"))
                        {
                            exec = true;
                        }
                        else
                        {
                            exec = false;
                        }
                    }

                    if (exec)
                    {
                        Process();
                        url = null;
                    }
                }
            }
        }

        private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
        {
        }

        private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
        {
            url = e.Url;
            Callback(false);
        }

        private void webBrowser1_ProgressChanged(object sender, WebBrowserProgressChangedEventArgs e)
        {
            Callback(false);
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            Callback(true);
            url = null;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            webBrowser1.Navigate("http://www.example.com/");
        }
    }
}

じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2008-06-29 17:05
引用:

unibonさんの書き込み (2008-06-28 15:02) より:

ただ、まだ大きな課題があって、body が <body> から </body> まできちんとダウンロードできたかどうか、ということが取得できません。ReadyState が Interactive であっても webBrowser1.Document.Body == null のときもあり、あまり便利に使えそうな状態ではないようです。


あちゃー、ダメでしたか。 MSDN での説明を見る限りでは大丈夫だと思っていたのですが、デタラメを書いてしまって申し訳ありませんでした。 勉強になりました。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
1

スキルアップ/キャリアアップ(JOB@IT)