Pythonを用いたWebブラウザ自動化において、要素を特定する「ロケータ」の選定はスクリプトの安定性を左右する極めて重要な工程です。
中でもXPathは、HTMLの階層構造を自在に指定できる強力な記述方法として、多くのエンジニアに利用されています。
XPathを使いこなすことで、IDやクラス名が動的に変化する複雑なWebサイトでも、目的の要素を確実かつ柔軟に取得することが可能になります。
本記事では、PythonとSeleniumを組み合わせた実務で役立つXPathの書き方と、要素取得のテクニックを体系的に整理してお伝えします。
XPathの基本概念とSeleniumでの利用準備
XPath(XML Path Language)は、XMLやHTMLドキュメント内の特定の要素や属性を指定するための言語です。
SeleniumでXPathを使用する場合、By.XPATHという識別子を用いて要素を検索します。
まずは、Python環境でSeleniumを使用してXPathを指定するための基本的なインポート文を確認しておきましょう。
from selenium import webdriver
from selenium.webdriver.common.by import By
# ブラウザの起動
driver = webdriver.Chrome()
# 特定のURLへ遷移
driver.get("https://example.com")
# XPathを使用して要素を取得
element = driver.find_element(By.XPATH, "//h1")
print(element.text)
Example Domain
XPathには大きく分けて「絶対パス」と「相対パス」の2種類が存在します。
実務においては、構造の変化に強い「相対パス」の利用が推奨されます。
絶対パスと相対パスの違い
絶対パスは、HTMLのルート(一番上の要素)から目的の要素までのすべての階層を記述する方法です。
/html/body/div[1]/section/form/inputのように記述しますが、サイトのデザインが少しでも変わると動かなくなるため、保守性が低いです。
一方、相対パスは、特定の要素を起点にして検索を開始する方法で、//から書き始めます。
//input[@id='username']のように記述すれば、ページ内のどこにその要素があっても見つけ出すことができます。
Seleniumのスクリプトを作成する際は、常に相対パスを優先して使用しましょう。
XPathの基本的な書き方:属性指定による要素取得
XPathで最も頻繁に使用されるのが、属性(ID、Class、Nameなど)を指定して要素を特定する方法です。
基本書式は、//タグ名[@属性名='値']となります。
IDやName属性を利用する
IDはページ内で一意であることが多いため、最も信頼性の高い取得方法です。
//input[@id='login_button']と記述することで、IDが「login_button」であるinput要素を直接指定できます。
同様に、//input[@name='q']のように、Name属性を使ってフォーム部品を指定することも一般的です。
Class属性を利用する
クラス名で指定する場合は、//div[@class='container']のように記述します。
ただし、複数のクラスが指定されている要素の場合、@class='値'では完全一致が求められる点に注意が必要です。
一部のクラス名のみで判定したい場合は、後述するcontains()関数を使用するのが一般的です。
| 取得対象 | XPathの書き方 | 備考 |
|---|---|---|
| すべてのリンク | //a | ページ内の全てのaタグを取得 |
| 特定のIDを持つ要素 | //*[@id='main-content'] | タグ名を問わずIDで検索 |
| 特定の名前を持つ入力欄 | //input[@name='user_mail'] | フォーム送信などで多用 |
| 特定のクラスを持つ段落 | //p[@class='description'] | 完全一致が必要 |
柔軟な要素取得を実現する「関数」の活用
属性の完全一致だけでは要素を特定できない場合、XPathに備わっている便利な関数を利用します。
これにより、部分一致やテキストの内容に基づいた高度な検索が可能になります。
contains関数(部分一致)
contains()関数は、属性値の一部が含まれているかどうかを判定します。
例えば、//div[contains(@class, 'menu-')]と記述すれば、”menu-item”や”menu-active”といった複数のクラスに対応できます。
これは、動的に生成されるID(例:button-12345の「12345」が毎回変わる場合)の対策として非常に有効です。
text関数(テキスト内容による指定)
ボタンのラベルやリンクの文字列を基準に要素を探したい場合は、text()関数を使用します。
//button[text()='送信する']と記述すれば、「送信する」というテキストを持つボタンをピンポイントで取得できます。
テキストの一部で検索したい場合は、//a[contains(text(), 'ログイン')]のように記述します。
starts-with関数(前方一致)
属性値が特定の文字列から始まる要素を探すには、starts-with()関数が役立ちます。
//input[starts_with(@id, 'user_')]のように指定することで、IDの接頭辞を基準にした取得が可能です。
# テキストの一部が含まれる要素を取得する例
element = driver.find_element(By.XPATH, "//span[contains(text(), '価格')]")
# IDが特定の文字から始まる要素を取得する例
input_field = driver.find_element(By.XPATH, "//input[starts-with(@id, 'order_')]")
XPathの「軸(Axes)」を利用した要素移動
目的の要素自体に特徴的な属性がない場合、その周辺にある要素(親、子、兄弟)をヒントに特定することができます。
これをXPathの「軸」と呼び、複雑なDOM構造を攻略する鍵となります。
parent / ancestor(親・先祖要素へ移動)
ある要素の親要素を取得したい場合は、/parent::タグ名を使用します。
例えば、//span[text()='削除']/parent::buttonと記述すれば、「削除」という文字が入ったspanを内包するbutton要素を取得できます。
さらに上の階層を辿る場合は、/ancestor::divのように指定することで、先祖にあたるdiv要素へ遡ることができます。
following-sibling / preceding-sibling(兄弟要素へ移動)
同じ階層に並んでいる要素間で移動する場合は、兄弟軸を使用します。
//label[text()='メールアドレス']/following-sibling::inputと記述すれば、「メールアドレス」というラベルの次に配置されているinput要素を取得できます。
逆方向に遡る場合は、preceding-sibling::を使用します。
child / descendant(子・子孫要素へ移動)
ある要素の直下にある子要素を指定するには、/child::タグ名または単に/タグ名を記述します。
その配下にあるすべての要素から探す場合は、//div[@id='container']//pのように、スラッシュを2つ重ねて「子孫」を指定します。
| 軸の名称 | 記述例 | 取得できる要素 |
|---|---|---|
| parent | //span/parent::div | spanの直上にあるdiv |
| ancestor | //input/ancestor::form | inputを包んでいるform全体 |
| following-sibling | //h2/following-sibling::p | h2と同じ階層にある、h2より後のp |
| preceding-sibling | //button/preceding-sibling::input | buttonと同じ階層にある、buttonより前のinput |
論理演算子(and / or / not)による複数条件指定
1つの条件だけでは要素を絞り込めない場合、論理演算子を組み合わせて精度を高めることができます。
複数の条件を掛け合わせることで、誤検知を大幅に減らすことが可能です。
and演算子の使用
//input[@type='text' and @name='user_id']のように記述すると、typeがtextであり、かつnameがuser_idである要素のみに一致します。
or演算子の使用
//button[@id='submit' or @id='save']のように記述すれば、どちらかのIDを持つ要素を取得できます。
not関数の使用
特定の条件を除外したい場合は、not()を使用します。
//div[contains(@class, 'item') and not(contains(@class, 'disabled'))]と記述すれば、「item」クラスは持っているが「disabled」クラスは持っていない要素を取得できます。
実務で役立つXPathのテクニックと注意点
Seleniumの運用を安定させるためには、ただ書けるだけでなく「壊れにくいXPath」を書くスキルが求められます。
ここでは、現場で意識すべきポイントをいくつか紹介します。
インデックスによる指定
同じ要素が複数ある場合、(//a[@class='link'])[1]のように括弧で囲ってインデックスを指定することで、何番目の要素かを選択できます。
ただし、要素の順番が変わると誤った箇所を操作するリスクがあるため、可能な限り属性や軸での指定を優先してください。
ブラウザの開発者ツールを活用する
Google Chromeなどの開発者ツール(F12)を使えば、XPathの動作確認が簡単に行えます。
「Elements」パネルでCtrl + F(MacはCmd + F)を押し、検索窓にXPathを入力してみてください。
ハイライトされた要素が自分の意図通りであれば、そのXPathは正解です。
XPathの実行速度について
一般的に、XPathはCSSセレクタに比べて解析速度がわずかに遅いと言われています。
しかし、現代のブラウザやPCのスペックにおいては、その差は体感できるレベルではありません。
速度よりも、XPathにしかできない「テキスト検索」や「親方向への移動」といった強力な機能を活かす方が、開発効率は高まります。
動的な要素への対応:Wait処理との組み合わせ
XPathが正しく記述されていても、JavaScriptによるレンダリングが完了する前に実行すると、要素が見つからずエラーになります。
PythonのSeleniumでは、明示的な待機(WebDriverWait)とXPathを組み合わせて使用することが鉄則です。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 要素が表示されるまで最大10秒間待機する
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.XPATH, "//button[@id='confirm']")))
element.click()
このように記述することで、ネットワーク遅延や動的なコンテンツ読み込みが発生しても、エラーを回避して安定した自動操作が可能になります。
まとめ
本記事では、PythonとSeleniumを用いたWebスクレイピングやテスト自動化において不可欠な、XPathの書き方を整理してきました。
基本となる属性指定から、テキスト検索、軸を利用した親子関係の移動、そして論理演算子による絞り込みまで、XPathには非常に多彩な表現力が備わっています。
IDやClassといった単純な指定が通用しない複雑なWebサイトほど、XPathの真価が発揮されます。
まずは開発者ツールを活用して、自分で作成したXPathが正しく要素を指し示しているか確認する習慣をつけましょう。
適切なXPathのスキルを身につけることで、変更に強く、メンテナンス性の高い自動化プログラムを構築できるようになります。
今回ご紹介したテクニックを駆使して、ぜひ確実な要素取得を実現してください。
