[SeleniumVBA]よく使っているXpathの活用事例について
はじめに
インストール不要でありながら高機能なSeleniumVBAを使用すれば、ExcelVBAで要素検索する際にもXpathをフル活用できます。
Xpathは業務システムで要素検索がうまく特定できないときに、大いに活用させてもらいました。感謝を込めて、よく使う事例を厳選してフィードバックさせていただきたいと思います。
Xpathの記法について
(1)「//」 と「/」、「.」と「..」について
「//」は基点からすべての下位要素、「/」基点の直下の要素を表します。
「.」は自分自身の要素、「..」は基点の直近上位の要素を表します。
「..」は、親要素もしくは兄弟要素の検索に便利なので、末尾の【実例2】を御参照ください。
基点の指定はIsPresentを用いる際によく利用しています。「.//」は基点以降のすべての下位要素を表し、「./」は基点の直下の要素を表します。
IsPresentを使ってXpathで基点を指定する場合は、「.」から始まるように書きます。以下の事例1、事例2を御参照ください。
With driver
Dim elm as WebElement '基点の要素
Dim elmfound as WebElement ’検索して見つかった要素
’基点の要素をelmとする
Set elm = .FindElement(By.ID, "〇〇")
'事例1 「.」がある場合、elmを基点として検索してくれる
If .IsPresent(By.XPath, ".//div[@id = '××']",2000,elm,elmfound)=False then Stop
'事例2 「.」がない場合、基点を指定したつもりでも、基点は最上位になってしまう
If .IsPresent(By.XPath, "//div[@id = '××']",2000,elm,elmfound)=False then Stop
End WIth
また、「//」 と「/」でみると変更に強いのは「//」なので、最上位の要素を基点にする場合は「//」を用いたうえで「//」のうしろに特定できるタグ、属性もしくはテキストを指定してあげると良いです。
(2)「/ancestor::タグ名」について
「/ancestor::タグ名」は、基点からすべての上位要素の指定したタグを検索します。
(3)ダブルコーテーションとシングルコーテーション
Xpath全体にダブルコーテーションでくくります。またXpath内において文字列とする箇所は、その箇所をシングルコーテーションでくくります。
SeleniumVBAではシングルコーテーションに代えて、ダブルコーテーションを2個続けることで代用することも可能です。実務でも下記【実例2】その4で活用しました。
'【通常】ダブルコーテーション内にシングルコーテーションをはさむ
strXpath ="//th[@class = '〇〇']/ancestor::table"
’【別の書き方】シングルコーテーションに代えて、ダブルコーテーション2連続でも可能
strXpath ="//th[@class = ""〇〇""]/ancestor::table"
(4)属性及びテキスト部分の指定について
属性を指定するときは、属性名の前に必ず「@」を使用します。
テキスト部分については、属性はないので「@」は使用せず代わりに「text()」を使用します。
'【事例】HTMLが以下の場合
'<th class="〇〇">××</th>
'@の使用例 thタグのclass属性が〇〇であるtabelを検索
strXpath ="//th[@class = '〇〇']/ancestor::table"
'text()の使用例 thタグのテキストが××であるtabelを検索
strXpath ="//th[text() = '××']/ancestor::table"
(5)括弧の表記順について
括弧の中に括弧が含まれる場合は、まず角括弧 [ ]でくくり、その次に括弧 ( )でくくります。括弧の閉じ忘れが起こりやすいので御注意ください。下記のcontains条件やstarts-with条件で使います。
contains条件
「~が含まれる」というような条件を指定する場合に使います。
'【事例】HTMLが以下の場合
'<th class="△△〇〇">□××□</th>
'contains使用例1 thタグのclass属性に〇〇が含まれるtabelを検索
strXpath ="//th[contains(@class, '〇〇')]/ancestor::table"
'contains使用例2 thタグのテキストに××が含まれるtabelを検索
strXpath ="//th[contains(text(), '××')]/ancestor::table"
starts-with条件
「~から始まる」というような条件を指定する場合に使います。(余談:Xpath2.0になってからサポートされるends-withは、2024年10月現在では使用できないです)
'【事例】HTMLが以下の場合
'<th class="〇〇">××△△</th>
'starts-with使用例 thタグのテキストが××から始まるtabelを検索
strXpath ="//th[starts-with(text(), '××')]/ancestor::table"
and条件
要素を一つに特定するために、複数の条件を同時に指定したいケースがあります。この場合、以下のように複数の条件をつなげるだけで良いです。
'【事例】HTMLが以下の場合
'<th class="〇〇" type ="△△>□□××</th>
’and条件その1 class属性が〇〇であり、かつ、テキストに××が含まれるthタグを検索
strXpath = "//th[@class='〇〇'][contains(text(),'××')]"
'and条件 その2 class属性が〇〇であり、かつ、type属性が△△であるthタグを検索
strXpath = "//th[@class='〇〇'][@type='△△']"
その1は、class属性が〇〇であり、かつ、テキストに××が含まれるthタグを検索したい、というようなand条件です。
その2は、class属性が〇〇であり、かつ、type属性が△△であるthタグを検索したい、というようなand条件です。
既存要素を基点としてIsPresentで検索
IsPresentでXpathを利用すると、すでに検索した既存の要素を基点として、要素を検索することができ、業務でもよく利用しています。
IsPresentはFilndElenemt系とは異なり、暗黙的待機の設定はその都度行っています(デフォルトの待機時間は0秒となるようです)。ここでの待機時間は2000ミリ秒(=2秒)に設定しています。
以下は、IsPresentによりXpathを利用して検索する例ですが、以下のとおり2通りの書き方があります。
Dim elmfound1 as WebElement
Dim elmfound2 as WEbElement
With driver
’【準備】
’Selectタグを調べた。最大2秒待機して見つかったらelmfound1が返ってくる
.IsPresent(By.Xpath, "//select[@name='〇〇']",2000,,elmfound1) = False Then Stop
'上記で見つけた要素(elmfound1)を基点にOptonタグのテキストを調べてelmfound2を取得してクリックする
’【記載例1】
If .IsPresent(By.Xpath, "./option[text()='××']",2000,elmfound1,elmfound2) = False Then Stop
’【記載例2】
If elmfound1.IsPresent(By.Xpath, "./option[text()='××']",2000,elmfound2) = False Then Stop
'クリックする
elmfound2.Click
End With
「記載例1」と「記載例2」は、どちらも同じ結果になります。違いは、基点(elmfound1)をどこに書くかです。私は「記載例1」の書き方をよく利用しています。
【実例1】テーブルの特定及び値の取り出し
以下の記事は実例です。同一ページに複数のテーブルがあり、どのテーブルの属性の値も同じであったため、テーブルの特定に困りました。
テーブルの並び順で決め打ちするのはメンテナンスが悪くなるし、どうしようか困り果てていました。
そうこうしているうちに、テーブルの見出しはそうそう変わるものではないと考え、Xpathを利用してテーブルの見出し(thタグ)のテキストを元にテーブルを特定することで何とかなりました。
Dim elmTable as WebElement
Dim elmTbody as WebElement
Dim arrTable as Variant
Dim arrTbody as Variant
Dim strXpath as String
With driver
'Xpathを指定(見出しに〇〇が存在するテーブル)
strXpath="//th[text()='〇〇']/ancestor::table"
’検索(elmTableを返す)
If .IsPresent(By.Xpath, strXpath, 2000, , elmTable) = False Then Stop
'見つかったelmTableを配列に格納
arrTable =elmTable.TableToArray(skipHeader:=True)
'配列をアクティブのシートA1を基点として貼り付け
Range("A1").Resize(UBound(arrTable, 1), UBound(arrTable, 2)).Value = arrTable
まず、上記のXpath部分のうち「//th[text()='〇〇']」は、ページ内のthタグ(見出し)のテキストが〇〇(〇〇は他に重複がない固有であるもの)を検索します。
そして「/ancestor::table」により、上位方向にtableタグを検索して見つかったらelmTableとして取得しています。
テーブルの要素が見つかれば、TableToArrayにより、テーブルの値を2次元配列に格納できますので、それをExcelの任意の一つのセルから貼り付けています。
【実例2】実務で使っているコード
最後に、実務で使っているコードを以下にそのまま掲載します。変数が入り交じっていますがご容赦ください。
'その1(親要素の検索)例では2階層上位の要素
strXpath = "//th[text()='申請番号']/../.."
'その2(兄弟要素の検索)
strXpath = "//th[text()='申請番号']/../td"
strXpath = "//th[text()='申請番号']/../*[contains(text()='〇〇')]"
’その3
strXpath = "//th[text()='項番']/ancestor::table//tr[" & cntRow & "]/td[" & colHenko & "]/input"
’その4
strXPath = "//tbody/tr/td[contains(@onmouseenter,""" & strEdate & """)]" & _
"[contains(@onmouseenter,""" & endTime & "');" & """)]"
その1は親要素の検索です。「/..」で表記します。親要素は1つに特定できるので、後ろにタグ名を書かなくても動作します。「/../..」と続けることで2階層以上の要素も検索できます。何段階上位になるか不明な時は、その3のancestorを使用します。
その2は兄弟要素の検索です。「/../タグ名」で表記します。2例目はタグ名は特定できないがため「*」として、かつテキストに〇〇が含まれる検索を示しています。実務としては見出し<th>が「縦並び」のときにその内容<td>を拾いたいときに使っています。
その3は「行ったり来たり」。見出し名のthタグからtdタグ行くには、下図のとおりtableを経由しなくてはなりません。このように特にテーブル関係の要素を拾いたいときに活躍します。
その4はシングルコーテーション自身を文字列として検索したかったために苦しんだ末、通常「'」を使うところを「""」で代用しました。ExcelVBAでは引用符がたくさん登場しますが、ちょっとでも間違えるとエラーを吐くので気を使います。
また「& _」で改行しているので、分かりづらかったかもしれませんが、複数のcontainsによるand条件も使いました。
おわりに
以下の記事にテーブルのセル位置を特定するためにXpathをフル活用した実例がありますので、御参考になるかと思います。
この記事が気に入ったらサポートをしてみませんか?