IEを自動制御するC#のアプリケーションを作成してみましょう。
ブラウザを自動で操作するにはSeleniumという仕組みも使えますが、今回はCOMを使った操作を行います。
COMというと少し難しく聞こえますが、要は特別な仕組みでなく、Windowsの機能のみで作ってしまおうということです。
ExcelなどのVBAマクロでもほぼ同じようなやり方で出来ると思います。
サンプルとして作るのは以下のようなアプリです。
- 検索したいキーワードを入力してボタンを押すと、自動でInternet Explorer(ブラウザ)が起動する
- ブラウザでは入力したキーワードで検索される。
- 結果が表示されたら5秒後に自動でブラウザが閉じる。
プロジェクトを作成
はじめにプロジェクトを作成します。
フリーの開発環境Microsoft Visual Studio 2019を使います。
「新しいプロジェクトの作成」⇒「Windowsフォームアプリケーション」を選択します。
プロジェクトを作成すると、下記のようなフォームが表示されるので、
TextBoxとButtonを配置します。
ブラウザを自動で開いてサイトを表示するソースと設定が必要な参照
シンプルにブラウザを開いて、指定したサイトを表示させるには、以下のコードでOKです。
ブラウザを操作するハンドラ(インスタンス)を以下のように宣言し、Navigate()するだけです。
SHDocVw.InternetExplorer objIE = new SHDocVw.InternetExplorer(); //オブジェクトを作成
objIE.Navigate("about:blank"); //空ページの表示
objIE.Visible = true; //IEを表示
さて、このハンドラを使ってIEの自動操作していくわけですが、それにあたって、いくつか参照が必要なので以下のように追加していきます。
SHDocVwのクラスを使えるように、「参照」から「参照の追加」でCOMの「Microsoft Internet Controls」にチェックを入れ、追加します。
また、Webページを表示した後、要素を取得するのに「mshtml.HTMLDocument」を使うので、COMの「Microsoft HTML Object Library」を参照に追加します。
System.Web.HttpUtility.UrlEncodeを使うには、アセンブリの「System.Web」を参照で追加します。
Yahooに対して検索を行うには、
のようなURLを投げる必要がありますが、検索キーワードの部分が日本語などのマルチバイトの文字の場合、そのままだとエラーになります。
この場合、URLEncodeしてリクエストを投げる必要があるためです。
要素の取得の方法
さて、さっそく自動操作していきましょう。
まず、以下のようにInternetExplorerをインスタンス化します。
その後、Navigate()メソッドで、targetUrlのサイトを読み込みます。
さて、さっそく自動操作していきましょう。
まず、以下のようにInternetExplorerをインスタンス化します。
その後、Navigate()メソッドで、targetUrlのサイトを読み込みます。
Navigte後、ページがロードされたら、そのロードの結果、解析されたHTML文書がDocumentに入っていますので
var ObjHtml = (mshtml.HTMLDocument)objIE.Document;
string gStrRes = "";
var bodys = ObjHtml.getElementsByTagName("body");
foreach (mshtml.IHTMLElement element in bodys)
{
if (element != null)
{
gStrRes = element.outerHTML;
}
}
のようにすると、サイト全体をテキストで取得することができます。
objIEにはその結果が入っていますので、一度Navigateでサイトを読み込んでしまえば、あとは、その中の要素をgetElementsByXXXまたはgetElementByXXXで取得することができます。
例えば、getElementById()で要素を取得し、その内容を取得するには以下のようにします。
mshtml.IHTMLElement elm = ObjHtml.getElementById("js-tracked_mod_1");
Debug.WriteLine("elm=" + elm.outerHTML);
一方、getElementsByXXX という形式の関数はElementsとあるように要素が複数返ります。
body要素は1つしかないので、上記のやり方でbody要素、つまりページ全体のテキストを取得できます。
これをデバッグなどで出力すると、どんなHTMLソースが返却されたかがわかるので、
どんな要素名で取得すればいいか?
などが分かりやすいです。
注意点としては、実際に返却されるHTMLソースと、上記のソースは若干異なることがあります。
それは、返却されたHTML文書をドライバが解釈して、ツリー構造にした結果が「mshtml.IHTMLElement」だからです。
そのため、期待される要素名で取得できない場合は、デバッグで出力させてみることをお勧めします。
タグやIdで取得するメソッドはありますが、クラス名で取得するメソッドは存在しません。
そのため、特定のクラス名で取得したい場合は、
var rankurls = ObjHtml.getElementsByTagName("a");
foreach (mshtml.IHTMLElement element in rankurls)
{
//Debug.WriteLine("foreach" + (String)(element.getAttribute("className")));
if ((String)(element.getAttribute("className")) == "sw-Card__titleInner")
{
String siteUrl = element.getAttribute("href");
Debug.WriteLine("URL=" + siteUrl);
}
}
のように、タグで取得し、そのタグの要素のクラス名を指定することで取得すると良いです。
上記の例だと、aタグで取得し、その要素のクラス名が「sw-Card__titleInner」のものを抜き出しています。
すると、
<a class=”sw-Card__titleInner” href=”http://search.yahoo.co.jp/xxxxx” >南青山 <b>まめ</b></h3>
<br><div class=”sw-Cite”><cite>www.<b>mamemame</b>.info/</cite></div></a>
のような要素のhrefの属性が取得できるというわけです。
ネットワークの状態によっては、すぐにレスポンスが返ってくるかわかりませんし、ページのロードに時間がかかってしまう場合もあります。
そのため、ページが全部読み込まれるまで、完了を待つようにすると良いです。
while (objIE.Busy || objIE.ReadyState != SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
{
//無処理
System.Windows.Forms.Application.DoEvents();
System.Threading.Thread.Sleep(100);
}
サンプルソース全文
以下がサンプルソースです。
このまま貼り付けると動く状態になっています。
using System;
using System.Windows.Forms;
using System.Diagnostics;
namespace IEAutoSample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
SHDocVw.InternetExplorer objIE = new SHDocVw.InternetExplorer(); //オブジェクトを作成
objIE.Navigate("about:blank"); //空ページの表示
objIE.Visible = true; //IEを表示
String encodedKeyword = System.Web.HttpUtility.UrlEncode(this.textBox1.Text, System.Text.Encoding.UTF8);
String targetUrl = "https://search.yahoo.co.jp/search?b=0&p=" + encodedKeyword;
objIE.Navigate(targetUrl);
//読み込み完了まで待つ
while (objIE.Busy || objIE.ReadyState != SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
{
//無処理
System.Windows.Forms.Application.DoEvents();
System.Threading.Thread.Sleep(100);
}
var ObjHtml = (mshtml.HTMLDocument)objIE.Document;
var bodys = ObjHtml.getElementsByTagName("body");
foreach (mshtml.IHTMLElement element in bodys)
{
if (element != null)
{
Debug.WriteLine(element.outerHTML);
}
}
System.Threading.Thread.Sleep(5000);
if (objIE != null) {
objIE.Quit();
}
}
}
}
ブラウザが固まらないようにする別スレッド化
あと、ブラウザ操作を繰り返すときに、GUIが固まってしまう現象を回避するため、別スレッドで操作するようにしても良いです。
これは少し中級者向けなので、最後に添えます。
そのためには、backgroundWorkerのコンポーネントを追加し、
- backgroundWorker1_ProgressChanged
- backgroundWorker1_RunWorkerCompleted
- backgroundWorker1_DoWork
というメソッドを作成したうえで、イベント発生時に呼ばれるように設定します。