見出し画像

Edge自動操作のためのHTML分析のポイント(前編)

先日紹介したVBAからEdge(IEモード)を自動操作の記事では、一例としてGoogleの「検索キーワードを入力→検索ボタンをクリック」の操作を自動化しました。
しかし当たり前ですが、Webページが変われば自動化する操作内容も変わる訳で、それに合わせてVBAコードを修正しないといけません。
ここでは、Webページを自力で分析してVBAコードを組めるように、Webページ分析に焦点を当ててポイントを記しておきます。

全2回の予定。
初めてHTMLを見る人でも分かるように解説したいので、前編は色々前置きが入ります。(ただしVBAの知識は多少ある前提とします)
後編では、実践的なTips集みたいなやつを書きたいと思います。
まずは、簡単なWebページ操作の例です。


基本的な考え方

ブラウザ(Microsoft Edge)操作を自動化する上で避けて通れないのが、Webページの分析作業です。
Webページがどんな構成で、どんな部品がどこに配置されているかが分からない事には、自動化のプログラム(VBAコード)なんて書けません。
どんなWebページであっても応用が効くように、答えではなく考え方やポイントに注目してください。
以下、Googleトップページを例に見ていきます。

対象のWebページを完全に理解する必要はありません。
たとえばGoogleトップページで「検索ボックスに入力→検索ボタンを押す」という操作を自動化したいなら、下図で言う①と②の構成部品さえ特定できれば良いわけです。

必要な情報(青)と、不要な情報(ピンク)

それ以外の仕様(例えばJavaScriptによる動的表示の仕組みや、裏側で行われるサーバーとの通信がどうなっているか等)は、ちゃんと紐解けば「ふむふむ成程」と納得できますが、分からないままでも目的は達成できます。
操作に直接関係のある部品情報だけを効率よく拾っていき、その結果「あれ、ここ上手くいかないな…」と行き詰ったときだけ、前後の文脈へ視点を広げて見ればいいと思います。
そのめにも、Webページ構成の基礎知識をある程度、頭に入れといた方が捗ります。

基礎知識(Webページ構成)

基礎知識をサラッと纏めます。
・Webページの実体はコード(文字列)であり、サーバーから送られてきた
 コード情報は、ブラウザを介すことで見やすく変換されて画面へ映る。
・Webページのコードは大体、HTMLという言語で書かれている。
 XMLで書かれたWebページも存在するが、一旦同じと考えてOK。 
・HTMLと併用して、CSS言語で書かれた記述も混在することがある。
・HTMLと併用して、JavaScript言語で書かれた記述も混在することがある。
 →大事なのは、HTML(XML)言語で書かれた記述の中に、必要な情報
 (部品情報)があるという事。
  HTML(XML)言語で書かれた記述に絞って、Webページを分析する。
 →CSSは、見た目(意匠性)に関わる記述形式なので無視。
 →JavaScriptは、ページコンテンツを動的に表示する記述形式であり、場合 
  に依っては分析が必要。
  例えば、「メニューボタンを押すと出てくるプルダウンリストの中のボ 
  タンを操作したい!」というアニメーション動作が絡むと、高確率で
  JavaScript部分の分析が必要になる。
  JavaScriptが絡む場合の処理は、後述。

【HTMLのルール】
・HTMLは文章を構造化するための言語。
 「<」と「>」で挟まれたタグと呼ばれる記号で、表示する情報を囲う。
 タグには複数種類があり、どのタグで囲うかによって意味合いが異なる。
   <html>タグ:htmlドキュメントであることを表す
   <head>タグ:Webページのタイトルに関する記述を表す
   <title>タブ:ブラウザのタブに表示されるタイトル名を表す
   <body>タグ:ブラウザ本文に表示される部分
   <a>タグ:リンク情報を表す
   <img>タグ:画像情報を表す
   <form>タグ:情報入力・送信に関する情報を表す
   <textarea>タグ:複数行の値を入力できるテキストボックスを表す
   <div>タグ:単体では意味を持たない特殊タグ、区切りを表す
   (等々、書ききれないので都度紹介します…)
・基本的に、開始タグ<○○>と終了タグ</○○>のセットで使われる。
 開始タグ~終了タグまでの一塊を要素と呼ぶ。
 終了タグ</○○>が無い場合もある。
・開始タグ<○○>の中に続けて、付加情報が書き込まれることがある。
 この付加情報の種類を属性と呼ぶ。(下図の黄色部)
 この付加情報の内容をと呼ぶ。(下図の緑部)

HTMLタグの説明

・タグは階層構造を形成する。
 入れ子にする場合は、深い階層の終了タグを先に書いて要素を閉じる。
 例:<html><head>abc</head></html>
   この場合<html>タグより深い階層にある<head>タグを先に閉じる。

ということで…
理解を深めるため、実際にGoogleトップページで使われているHTMLコードを見てみましょう。
下図左がHTMLコード(を簡略化したもの)と、右がブラウザ上で表示される画面です。
HTMLコードに馴染みない人も、なんとなくイメージが掴めたでしょうか?

HTMLコード抜粋とブラウザ画面の対比


HTMLの確認方法

WebページのHTMLコードを確認する方法は、ブラウザ標準機能である「開発者ツール」を使うのが最も手軽で堅実です。

開発者ツールとは、ブラウザに標準的に組み込まれたデバックツールです。
Microsoft Edgeでは「開発者ツール」、Google Chromeでは「デベロッパーツール」などと呼び方は変わりますが機能は大体同じ。
現在表示しているWeb画面のソースコード(HTML、CSS、JavaScript)や、通信状況(HTTPリクエストやレスポンス状況)をリアルタイムで確認することができ、効率的にWeb画面を分析できます。
何より、ブラウザに組み込まれた純正機能なので、よほど制約が厳しい職場環境でない限り使えることが最大のメリットでしょう。

下図はMicrosoft Edgeブラウザの例ですが、起動方法は①ブラウザ上で右クリックし、②開発者ツールで調査するを選択すると、③画面右側(赤枠部)に「開発者ツール」が現れます。
このうち、要素タブの一番大きな窓(青枠部)にHTMLコード(というかDOMツリー)が表示されます。基本的にはここに表示されたHTMLコードを見てWebページ分析を進めます。
※DOMツリーについては後述。

開発者ツールの起動方法&HTMLコードの確認方法

もしも「開発者ツール」が起動できないときは…
 ・ブラウザがIEモードになっていなか確認する。
  (IEモードだと「開発者ツール」が使えないことも)
 ・そもそも開発者ツールが無効の設定になっていなか確認する。
 ・ほかのブラウザ(ChromeやFirefoxなど)で試してみる。
をチェックしてください。
それでもダメなときは潔くデバックツールによる分析は諦めて、HTMLコード全文(テキストデータ)を気合で読み解いてください。効率は落ちますが、それっぽいキーワードで検索すればアタリ位は付けられるでしょう…。
HTMLコード全文を表示する方法は、
 ・ブラウザ右クリック>ページのソース表示(Ctrl+U)
 ・VBA等の言語でHTTPリクエストを送信し、レスポンスを書き出す
等いろいろやり方があります。
会社のセキュリティポリシーと相談してできる方法を模索して下さい。


Webページの分析例

対象部品の要素を確認

「開発者ツール」が起動できたら、Webページの分析作業に入ります。
使うのは、「開発者ツール」の検査ツールボタンだけ。
使い方は簡単で、下図のように①検査ツールボタンをクリックして、②Webページ上の任意の項目にカーソルを合わせると、③その部分のHTMLコードが網掛け表示されます。超便利!

検査ツールボタンの使用例

文字を入力する検索ボックスにカーソルを合わせて検査すると、開始タグ<textarea>~終了タグ</textarea>までの要素全体が網掛け表示されました。

要素内の属性・値を確認

この網掛け表示された要素内に、特定したい画面構成部品である「検索ボックス」の固有情報が含まれています。特定するのに活用できそうな固有情報(つまり属性と値)を確認できれば目的達成ということです。
網掛け表示された要素内を見てみると…

<textarea>タグ内の属性のうち、活用できそうな固有情報(属性と値)に目星をつける

name属性とその値"q"、type属性とその値"search"が確認できました。
どちらの属性値も、「検索ボックス」という画面構成部品の役割と関連がありそうな特徴的な名付け("q"や"search"の値)がされているので、構成部品の特定に活用できそうです。これらの属性と値を控えておきます。
更に、id属性とその値"AP-jFqb"も確認できました。
id属性の場合は、同じページ内で一意(重複することのない一対の識別子)という特性があるため、id属性をキーにして特定することができるでしょう。念のためこの属性と値も控えておきます。
こうして構成部品の特定に必要な固有情報(属性と値)の収集が完了しました。
ここまでくれば、あとはVBAコードの記載を一部修正してやるだけです。
ブラウザ操作の自動化まであと一歩。


VBAからHTML構成部品を操作

HTMLとVBAという異なる言語を繋ぐのが、DOMと呼ばれる仕組み
(※詳細は後述)です。
VBAでは、DOMによって生成された文書オブジェクトを参照する場合、HTMLDocument型というデータ型に当てはめて取り扱うことになります。
これより先、単に「オブジェクト」と表現した場合は、WebページをIHTMLDocument型オブジェクトにしたもの(WebページをDOMの仕組みを使ってオブジェクト化したもの)を指すと考えてください。

WebページをVBAで操作する仕組み

※WebページからIHTMLDocument型オブジェクトを生成する過程の
 VBAコードを見たい人は、VBAからEdgeを自動操作を参照されたし。

検索ボックスを操作する

先ほど控えておいたWebページの分析結果をおさらいすると…
 ・Google検索ボックスに該当する要素は<textarea>タグの部分。
 ・<textarea>タグ内の属性のうち、特定に活用できそうなものは…
  ①name属性とその値"q"
  ②type属性とその値"search"
  ③id属性とその値"AP-jFqb"
でした。
このうち、今回は①name属性を使って検索ボックスを特定していきます。※当然、type属性やid属性や<textarea>タグ名を使ったアプローチ方法でも
 特定は可能と思います。正解は一つでないので、活用できそうな情報を
 見つけたら①~③の様に予め複数メモしておき、トライ&エラーで上手く
 動作するもの・使い勝手が良いもの(後から見てメンテしやすいもの等)
 を見極めて柔軟に攻略するのが良いでしょう。

それではVBAの話に入ります。
name属性を使って構成部品を特定したいときは、IHTMLDocument型オブジェクトに用意された「getElementsByName」メソッドを使います。
このメソッドでは、引数に指定した属性値を持つ要素全てを取得します。
「オブジェクト.getElementsByName("q")」
と書くと、オブジェクト(HTML全体)から「name="q"」を持つ要素全てが取得され、戻り値として要素のコレクションを返します。
イメージとしては、オブジェクトの先頭から順に「name="q"」で検索をかけていき、検索でヒットした順々に要素のコレクションへ格納していく感じです。つまり、要素のコレクションは「HTMLの記載順」になります。
※name属性は、HTML内で同じ値を重複設定することが許されています。
 name属性の特性上、値が重複する可能性があるためgetElementsByName
 メソッドの戻り値は必ず複数の要素群になります。
 たとえHTML内に1つしかないname属性の値を指定したとしても、戻り値 
 はコレクションになるため要注意。
 メソッド名もgetElement「s」ByNameと、複数形になっている事からも  
 その性質が伺えます。

オブジェクトから「name="q"」を持つ要素全て(コレクション)が取得できたら、今度はインデックス番号の指定です。
「要素群のうち〇番目の要素を指定しますよ」という順番を明示するため、「オブジェクト.getElementsByName("q")(0)」
と、後に続けてインデックス番号を書きます。
※特定したい要素がHTMLの何番目に位置しているのかを確認する際は、
 HTMLコード全文を表示した状態で、キーワード「name="q"」で検索 
 (Ctrl+F)をかけるのが手っ取り早いです。
 何回目の「name="q"」で、求めている要素(今回の場合は<textarea>タグ 
 のある要素)にたどり着くかを判断すれば良いですね。
 仮に1番目に位置する要素を指定したければインデックス番号は「0」、
 2番目ならば「1」、3番目ならば「2」と続きます。

これで<textarea>タグを持つ要素の特定が出来ました。
つまり、検索ボックスの特定ができました。

次にやることは、検索キーワードの入力操作です。
先ほど特定した検索ボックスに続けて「.Value="検索キーワード"」と追記すればOK。つまり、
「オブジェクト.getElementsByName("q")(0).Value="検索キーワード"」
となります。
こうすることで、検索ボックスの要素内のValue属性の値が、"検索キーワード"に置き換わります。これはWebページ上で手動で検索ワードを入力したときと同じ挙動です。
Googleで「地獄の油揚げ」と検索したければ、

オブジェクト.getElementsByName("q")(0).Value="地獄の油揚げ"

と書き換えてください。
これで検索ボックスの操作が完了しました。


検索ボタンを操作する

続いて、「Google検索」ボタンを押下する操作を自動化します。
基本的な流れは同じで、「開発者ツール」の検査ツールを使い要素を特定。

<input>タグ内に、value属性、name属性、role属性、type属性…と色々な情報が書かれていますね。
正解は1つではありませんが、今回は<input>タグ内のtype属性を使って、ボタンを特定・操作してみます。

ボタン特定・操作の流れですが、
 ①<input>タグ名を持つ全要素のコレクションを取得
 ②コレクションを1つづつ取り出し、その中のtype属性の値が"submit"か
  どうかを判別する
 ③ボタンをクリックする
という流れでやってみます。

タグ名を使って構成部品を特定したいときは、IHTMLDocument型オブジェクトに用意された「getElementsByTagName」メソッドを使います。
メソッド名もgetElementsBy「Tag」Nameとあるように、前述のgetElementsByNameメソッドのタグ名版と考えて良く、使い方は同じです。
「オブジェクト.getElementsByTagName("input")」
と書けば、<input>タグ名を持つ要素群(コレクション)が取得されます。

この要素群(コレクション)を対象に、For Eachステートメントを使うと
「For Each button In オブジェクト.getElementsByTagName("input")」
となりますね。
For Eachステートメントで1つづつ要素を取り出して、その要素内のtype属性の値が"submit"かどうかをチェックしていきます。
type属性の値が"submit"であれば、「Google検索」ボタンが見つかったという判断方法です。

更に続けて「Google検索」ボタンが見つかったら、Clickメソッドを使ってクリック動作を再現すれば完成です。
ボタン特定・操作の完成形コードはこちら。

    For Each button In オブジェクト.getElementsByTagName("input")
      If button.Type = "submit" Then
         button.Click
         Exit For
    End If
    Next



如何だったでしょうか。
基本的には、こんな感じでWebページ分析&VBAコードで実装、という流れになります。大筋がつかめたら、あとは実践あるのみ!
実際のWebページでどんどん試してみるのが習熟の近道だと思います。
次回記事(後編)では、Tips集(こういう時はこうする的なノウハウ集)を書いていますので、よかったらVBA実装の参考にして下さい。

DOMとは

補足ですが、HTMLとVBAという異なる言語を繋ぐ、DOMと呼ばれる仕組みについて紹介します。
(知っておくと理解が深まりますが、よく分からないままでも使えます)。

DOMとはDocument Object Modelの略で、「構造化された文書」を「オブジェクト」化して扱う仕組みです。
 ・構造化された文書=HTMLのルールに基づいて記述されたWebページ。
 ・オブジェクト=処理の対象となるもの。
オブジェクト化とは、「VBAというプログラミングの世界に、処理対象となる物体を生み出すこと」とざっくりイメージして下さい。
何もないプログラミング世界の中に物体が生み出される(実体化する)ことで、初めて具体的な操作(メソッドやプロパティの参照)が出来るようになります。
※オブジェクト生成のメカニズムに関して、ここでは詳しく触れません。
 興味がある人はインターフェース、クラス、インスタンス等を体系的に
 学んでみると良いでしょう。VBAの範疇からは外れますが…

もう少し説明します。
Webページ(HTMLコード)は、沢山の要素や属性が集まって構成された1つの文章です。この文章を、まずは要素や属性レベルに細分化するところから考えてみます。
HTMLコードを要素や属性レベルに区切って分解すると、無数のパーツに分かれますね。これら分解したパーツそれぞれに対して、DOMという共通の枠組み(構造・操作を定義した共通規格)に基づいてオブジェクト生成していくと、VBA世界の中に無数のオブジェクト(=処理対象となるもの)が実体化されることになります。
生成された幾つものオブジェクトが集まることで、HTMLコード全体に相当する巨大なオブジェクト群の集合体が形成されます。

ここで、大元となったHTML自体も、一定のルールに基づいて記述された「構造化された文書」でした。この構造化された文章を分解したときと同じ要領で(逆の要領で)、VBA世界の中に生成された幾つものオブジェクトを逆に構造的に組み立てていきます。
※一つ一つのオブジェクト自体に自身の構造情報が組み込まれているため、
 オブジェクト群を構造的に組み立てることが可能です。

構造的に再形成されたオブジェクト群は、ちょうど1つのHTMLコード全体と同じような階層構造を形成します。
※オブジェクト同士の相対位置(親と子の関係)はHTMLコードと同じ。
最終的に1つの文章(ドキュメント)オブジェクトを頂点としたピラミッド構造が出来上がる訳で、これが通称DOMツリーと呼ばれるものです。

HTMLコードとは、言ってしまえば単なるテキストデータです。
単なるテキストデータを解析して、分解して、各パーツをオブジェクト生成して、幾つものオブジェクトの集合体を形成するという一連の仕組みを、本来(狭義の)DOMと言います。
一つ一つのオブジェクトが、DOMという枠組みに則った構造・操作を持っているので、外部のプログラミング言語からは「お決まりの構造・操作が定義されたオブジェクト」として扱う事が出来ます。
「〇〇の要素を指定して、〇〇が持つ△△の操作を実行したい!」と思ったなら、既に生成された〇〇オブジェクトを参照して該当する△△メソッドを外部プログラミング言語から実行すれば良いだけな訳です。
これが異なる言語を繋ぐ仕組みです。

因みに、少しややこしいのですが、DOMの仕組みを使って生成されたオブジェクト自体も(広義の)DOMと呼ばれます。
文脈によっては、DOMツリー全体を指してDOM(またはDOMオブジェクト)と呼ぶ場合も多々あります。

まあVBA言語をメインに扱う限り、この位の理解をしておけば当面問題ないと思います…。
DOMの説明おわり。


補足:JavaScript絡みの処理

2023年9月18日追記。

●JavaScriptの強制発火

一筋縄ではいかないWebページ操作例を紹介します。
たとえば任天堂のWebサイト「Nintendo Switch Online」を例に、画面右上にある「≡ MENU」ボタンをクリックする操作を見ていきます。

「Nintendo Switch Online」の画面右上にある「≡ MENU」ボタンのソースコード

開発者ツールでこの「≡ MENU」ボタンを見てみると、id属性の値が「hardSwitch-nav__menu」であることが確認できます。
id属性を使って構成部品を特定したいときは、通常、IHTMLDocument型オブジェクトに用意された「getElementById」メソッドを使います。id属性はHTMLページ内で一意なので、
「オブジェクト.getElementById("hardSwitch-nav__menu")」
と書くと、オブジェクト(HTML全体)から当該要素だけが取得できます。
本来ならば…

オブジェクト.getElementsByid("hardSwitch-nav__menu").Click

で「≡ MENU」ボタンをクリックする動作が再現できる筈だったのですが、上手くいきません。「≡ MENU」ボタンに設定されているJavaScriptが邪魔している為です。
そんな時は、次のように書き直しましょう。

Dim pwin As Object 'HTMLWindow2
Set pwin = オブジェクト.parentWindow
pwin.execScript "window.setTimeout(""document.getElementById('hardSwitch-nav__menu').click();"",10);"

こうすることで、「≡ MENU」ボタンに設定されたJavaScriptを強制的に実行(JavaScriptを強制発火)させることが出来ます。
変数pwinには、JavaScriptのウィンドウオブジェクトを操作するためのHTMLWindow2型のデータ型を指定し、DOM生成したオブジェクト(IHTMLDocumen型)の親ウィンドウを代入します。
この親ウィンドウを通すことでJavaScriptの様々な機能を呼び出していくことが可能です。今回はexecScriptメソッドを使ってJavaScriptを強制発火しました。

execScriptメソッドの引数に指定した
「"window.setTimeout(""document.getElementById('hardSwitch-nav__menu').click();"",10);"」の部分を補足します。
この部分は、10秒ミリ秒後に
「document.getElementById('hardSwitch-nav__menu').click();」
を実行するようブラウザへ設定したあとVBAに制御を戻す、という命令内容です。VBAに制御を戻さないと(VBA側の)処理が止まってしまうので、ブラウザ側に時間差付きで命令を出すだけ出して、その後のVBA処理を継続させる流れを取っています。
JavaScript絡みの処理に出くわしたら、こんな感じでコードを書いてみましょう。

●その他のJavaScript突破方法

上記のexecScriptメソッドでJavaScriptを強制発火できないときは、アプローチを変えてみるのも手です。
例えば、「要素の特定までは出来るがクリック操作のみ再現できない」という状況であれば、要素にフォーカスした上で、
「 CreateObject("Wscript.Shell").SendKeys "{ENTER}"」等のコマンドでクリックを再現するゴリ押し方法もあります。

オブジェクト.getElementById("目的の構成部品のID").Focus
CreateObject("Wscript.Shell").SendKeys "{ENTER}"
Sleep 100
オブジェクト.getElementById("目的の構成部品のID").Value = "2024/12/31"

上記は、「Webページの入力欄をダブルクリックして焦点を合わせると初めて入力欄(TextBox)が操作可能な状態になる」というJavaScriptを突破する例です。
ダブルクリックが上手く再現できないときに{ENTER}キーを送り込むことで、JavaScriptの突破(TextBoxのアクティブ化)に成功した例です。
TextBoxがアクティブになったら、本来の目的であるテキスト入力(2024/12/31の入力)を実行しています。

また「要素の特定すらできない」という状況であれば、目的の要素の近くにある構成部品(何でもよいので特定ができる構成部品)にとりあえずFocusメソッドで焦点を当てたうえで、{TAB}キーを複数回送り込むことで、目的の要素まで移動するという離れ業もあります。
※ただしこの離れ業は、誤動作リスクが高いので非推奨…。

どうしても「JavaScriptが邪魔でWebページ操作を自動化できない!」という場合は、そもそもJavaScriptに邪魔されない操作手順を考えるのもアリです。


この記事が気に入ったらサポートをしてみませんか?