webbrowserで指定のページを開きパスワードを自動で入力したい。

タグの編集
投稿者 さと  (社会人) 投稿日時 2010/2/13 18:57:58
①フォームをロード時にヤフーのトップページ表示。
②ボタンを押すとwebbrowserで指定のページへ移動。
③XMLで保存されていたパスワードを自動で入力。
という動作をしたいとおもっているのですが、それがうまく動作しません・・・。


    Private Sub WebBrowser1_DocumentCompleted(ByVal sender As System.ObjectByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
        webcompflag = True

    End Sub


    Sub webconCompleted()
        Do While (webcompflag = False)

            System.Windows.Forms.Application.DoEvents()
            System.Threading.Thread.Sleep(10)
        Loop

    End Sub


    Private Sub Button2_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button2.Click
        Dim all As HtmlElementCollection = WebBrowser1.Document.All
        Dim path As String = Application.StartupPath + "\setting\path\path.xml"
        Dim dSet As New DataSet()
        Dim sRead As IO.StreamReader
        Dim dRow As DataRow

        Dim yahooaouction As New Uri("https://login.bizmanager.yahoo.co.jp/login")
        Me.WebBrowser1.Navigate(yahooaouction)


        Call webconCompleted()


        Dim IDb As HtmlElementCollection = all.GetElementsByName("user_name"
        Dim PASSb As HtmlElementCollection = all.GetElementsByName("password")
        Dim btb As HtmlElementCollection = all.GetElementsByName("login_form")

        sRead = New IO.StreamReader(path, System.Text.Encoding.Default)
        dSet.ReadXml(sRead)


        dRow = dSet.Tables("ログイン設定詳細").Rows.Item(1)
        IDb(0).InnerText = dRow("ID")
        PASSb(0).InnerText = dRow("パス")
        btb(0).InvokeMember("submit")

        sRead.Close()

    End Sub


というところまでは記述してあります。

ちなみに、

Dim yahooaouction As New Uri("https://login.bizmanager.yahoo.co.jp/login")
Me.WebBrowser1.Navigate(yahooaouction)
Call webconCompleted()

のコードを使用せずに、
①フォームをロード時に指定のページ①を表示。
②ボタンを押すとwebbrowserで表示されていた指定のページ①にXMLで保存されていたパスワードを自動で入力。

という動作だけならきっちりパスワード等を入力しログインできるのですが・・・。

どうしたらうまくログインできるのでしょうか?
投稿者 (削除されました)  () 投稿日時 2010/2/13 23:29:08
(削除されました)
投稿者 あにす  (社会人) 投稿日時 2010/2/13 23:31:36
>うまく動作しません
ではなく、具体的にどのような結果になったのかを書くと回答が付きやすいと思います。
        Dim all As HtmlElementCollection = WebBrowser1.Document.All

でNullReferenceExceptionが発生するということですよね。これはWebBrowserがページを読み込む前で、まだDocumentプロパティがNothingだからです。
この行を
        Call webconCompleted()

の後に移動してみて下さい。
投稿者 さと  (社会人) 投稿日時 2010/2/14 00:09:02
ご指摘どおり下記のように修正しました。

    Private Sub Button2_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button2.Click

        Dim path As String = Application.StartupPath + "\setting\path\path.xml"
        Dim dSet As New DataSet()
        Dim sRead As IO.StreamReader
        Dim dRow As DataRow

        Dim yahooaouction As New Uri("https://login.bizmanager.yahoo.co.jp/login")
        Me.WebBrowser1.Navigate(yahooaouction)


        Call webconCompleted()


        Dim all As HtmlElementCollection = WebBrowser1.Document.All
        Dim IDb As HtmlElementCollection = all.GetElementsByName("user_name"
        Dim PASSb As HtmlElementCollection = all.GetElementsByName("password"
        Dim btb As HtmlElementCollection = all.GetElementsByName("login_form"

        sRead = New IO.StreamReader(path, System.Text.Encoding.Default)
        dSet.ReadXml(sRead)




        dRow = dSet.Tables("ログイン設定詳細").Rows.Item(1)
        IDb(0).InnerText = dRow("ID")
        PASSb(0).InnerText = dRow("パス")
        btb(0).InvokeMember("submit")


        sRead.Close()

    End Sub


そうすると、
IDb(0).InnerText = dRow("ID")
の部分で、
'0' の値は 'index' に対して有効ではありません。'index' は 0 と -1 の間でなければなりません。 パラメータ名: index
というエラーがでます。

ソフト側のページはヤフーのトップ画面でエラーがでて、ログイン画面のページに移動もしていないようです。
投稿者 あにす  (社会人) 投稿日時 2010/2/14 10:54:16
なんで上手くいかないんでしょうねー。こちらでは自動入力に成功しているのですが…。失敗時のHTMLを保存しておけば解決のヒントになるのでは。
投稿者 さと  (社会人) 投稿日時 2010/2/14 18:45:57
あにすさんそうですか・・・。
せっかくアドバイスいただいたのにすいません。

この後にメッセージボックスでどこまで処理が進行していくのかを確認してみました。
下記のように
MessageBox.Show("1")
MessageBox.Show("2")
を挿入しました。


    Private Sub Button2_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button2.Click

        Dim path As String = Application.StartupPath + "\setting\path\path.xml"
        Dim dSet As New DataSet()
        Dim sRead As IO.StreamReader
        Dim dRow As DataRow

        Dim yahooaouction As New Uri("https://login.bizmanager.yahoo.co.jp/login")
        Me.WebBrowser1.Navigate(yahooaouction)


        Call webconCompleted()

        MessageBox.Show("1")
        Dim all As HtmlElementCollection = WebBrowser1.Document.All
        Dim IDb As HtmlElementCollection = all.GetElementsByName("user_name"
        Dim PASSb As HtmlElementCollection = all.GetElementsByName("password"
        Dim btb As HtmlElementCollection = all.GetElementsByName("login_form"

        sRead = New IO.StreamReader(path, System.Text.Encoding.Default)
        dSet.ReadXml(sRead)



        MessageBox.Show("2")
        dRow = dSet.Tables("ログイン設定詳細").Rows.Item(1)
        IDb(0).InnerText = dRow("ID")
        PASSb(0).InnerText = dRow("パス")
        btb(0).InvokeMember("submit")


        sRead.Close()

    End Sub



不思議です・・・。
MessageBox.Show("1")・MessageBox.Show("2")が表示され、しっかりログインできました。
※私がOKボタンを押す間にログインページがDocumentCompletedされている模様です。

しかし、MessageBox.Showの二つがそれぞれ表示されてすぐにOKボタンを押すと、
ページが切り替わる前に先ほどと同じエラーがでます。
※私がOKボタンを押すはやさを早くするとログインページがDocumentCompletedされる前に次の処理にいっている模様です。


思うに、ページがログインページに切り替わる(DocumentCompletedする)前に次の処理にいってしまっているのではないでしょうか?

ということは

   Private Sub WebBrowser1_DocumentCompleted(ByVal sender As System.ObjectByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
        webcompflag = True

    End Sub

    Sub webconCompleted()
        Do While (webcompflag = False)

            System.Windows.Forms.Application.DoEvents()
            System.Threading.Thread.Sleep(10)
        Loop

    End Sub


の処理に間違いがあるのでしょうか?
ちなみにこの処理はネットで検索してサンプルコードをひっぱってきたものです。

でもあにすさんは自動入力ができたとおっしゃっていたし・・・。

どなたかアドバイスをください。
投稿者 ヴァン  (社会人) 投稿日時 2010/2/14 19:07:26
Call webconCompleted() 以降の処理を、WebBrowser1_DocumentCompleted に移動させてみてはどうですか?



投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/2/14 19:51:50
DocumentCompleted イベントは、フレームが切ってあるページや、広告が挿入されているページでは
複数回発生する可能性があります。イベント引数を見て、どの URL に対して発生したのかを
追跡しておいた方が安全です。


> Sub webconCompleted()
>   Do While (webcompflag = False)

ループ待機は避けた方が良いと思いますよ。
ヴァンさんも書かれていますが、WebBrowser1.Document を処理する箇所を、
Button2_Click 内ではなく、WebBrowser1_DocumentCompleted に移動してみてください。


「ボタンが押されたら、ログインページに Navigateする」という処理を、
Button2_Click に書くのは正しいと思います。

しかし「ログインページの HTML 解析が完了したら、ID 等を入力する」という処理を、
解析完了後(DocumentCompleted)ではなく、ボタンクリック時に記述しておくのは不自然です。
投稿者 さと  (社会人) 投稿日時 2010/2/14 20:36:42
魔界の仮面弁士さんとヴァンさんがおっしゃるとおりDocumentCompletedの後に記述することも考えたのですが、このソフトの実装として

①フォームロード時にあるページを開き
②ログインページ①のページを開く。
③ログインページ①にID等を自動入力してログイン
④ログインページ②のページを開く。
⑤ログインページ②にID等を自動入力してログイン
⑥①のページを開く

というものにしたいのです。


この場合DocumentCompletedの箇所にifなどを使用して③と⑤の処理を記述しないといけないのでしょうか?


それと、魔界の仮面弁士さんがおっしゃる
>ループ待機は避けた方が良いと思いますよ。
というのはなぜなのでしょうか?

>「ログインページの HTML 解析が完了したら、ID 等を入力する」という処理を、
解析完了後(DocumentCompleted)ではなく、ボタンクリック時に記述しておくのは不自然です。

いわれてみると私もそう思いましが、解析完了後に動作をさせたいのでボタンクリック時に読み込み完了するまで待機をさせるサンプルコードを探してきたのですが、こういった方法で読み込み完了を待つコードを使用して次の処理をさせることはあまり使用しないのでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/2/15 01:01:46
> この場合DocumentCompletedの箇所にifなどを使用して
Yes です。実装手順は If でも Case でも AddHandler でも キューでも何でも良いですが。

> (3) と (5) の処理を 記述しないといけないのでしょうか?
もしも WebBrowser / HTMLDocument が、同期的に処理する仕様であったのならば
Click イベントに書いても良かったのですが、HTML 解析は非同期で行われるため、
別イベントで処理する必要があります。
イベントを処理できない言語ならば仕方ありませんが、VB はそうではありませんよね。


>>ループ待機は避けた方が良いと思いますよ。
>というのはなぜなのでしょうか?
たとえば「CheckBox がチェックされたら、ラベルの背景色を青にする」というアプリがあるとします。
さらに、「ボタンを押した後でのみ、この CheckBox 切り替えが行われる」という仕様だとしましょう。

この場合ループ待機を用いて
Private Sub Button1_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button1.Click
  Do Until CheckBox1.Checked
    Application.DoEvents()
  Loop
  Label1.BackColor = Color.Blue
End Sub
というコードを書くことはできますが……通常、こういう事はしませんよね? Checked の変化を監視するよりも
CheckBox1 の CheckedChanged イベントを利用する方が自然だと思いますが、それと同じことです。


しかし、ループ待機を行うことの問題点はもっと別の所にあります。
たとえば上記のテストコードを実行してみると分かりますが、CheckBox1.Checked が True に
ならないとループが終了しません。そのため、ボタンを押した後でチェックせずにいた場合、
フォームを閉じようとしてもアプリが終了しないという問題を引き起こします。

さとさんのコードも同様で、もし、ネットワーク障害によってページ遷移に長い時間がかかったり、
HTML解析がなかなか終わらない場合、ループ処理も長い時間がかかることになり、それと似たような
問題を引き起こします。また、処理すべきメッセージがないときに DoEvents を繰り返し呼ぶことは、
不用意に CPU 負荷を高める結果となります(シングルコア CPU の時には、特に顕著になります)。

間に Sleep をはさむ事で、CPU 負荷を軽減する事はできますが、十分な対策ではありませんし、
Sleep している間は、肝心の HTML 解析処理さえも停止することになってしまいます。
また、Sleep によってマウスやキーボード操作等のメッセージ処理も阻害されてしまうため、
アプリケーション処理にもたつきを生じさせる要因となりえます。

そして何よりも、本来処理すべきでは無い箇所で DoEvents を呼ぶ事は、イベントの再入が
発生するなどの再現性の低い不具合(後から原因を突き止めにくいバグ実装)を引き起こす
原因ともなりえます。不用意に DoEvents を繰り返し呼ぶことは、できる限り避けるべきかと。


> 読み込み完了するまで待機をさせるサンプルコード
イベント駆動型のコーディングを行いましょう。
待機中はイベントの発生を待つだけで、それ以外も「何もしない」ようにします。イベントを使えば
「現在の状況を繰り返し繰り返し問い合わせ続ける」ことをしなくても済むはずです。

もし、どうしてもループで処理する必要がある場合には、Sleep を呼ぶのではなく、
MsgWaitForMultipleObjects を使うようにします(今回は不要と思いますが)。