Pythonを用いたWebブラウザ自動化において、Seleniumでの要素特定は最も基本的かつ重要なステップです。
特に、IDやクラス名が動的に変化する現代のWebサイトでは、要素内の「テキスト」を頼りに要素を特定する機会が増えています。
本記事では、XPathを駆使してテキストの部分一致や、擬似的な正規表現検索を実現するための高度な指定方法について詳しく解説します。
SeleniumにおけるXPathテキスト検索の基礎
XPath(XML Path Language)は、HTMLドキュメントの構造を詳細に指定して要素を取得するための言語です。
Seleniumでテキストを基準に要素を探す際、最も基本的なのはtext()関数を使用した完全一致による検索です。
例えば、ボタンの中に「ログイン」という正確な文字列が含まれている場合、以下のコードで要素を特定できます。
from selenium import webdriver
from selenium.webdriver.common.by import By
# ブラウザの起動
driver = webdriver.Chrome()
# 完全一致による要素の検索
element = driver.find_element(By.XPATH, "//button[text()='ログイン']")
print(element.text)
ログイン
しかし、実際の開発現場では、テキストの前後に余計な空白が含まれていたり、一部の文言だけが判明しているケースが多々あります。
このような状況で威力を発揮するのが、部分一致検索のテクニックです。
contains関数によるテキストの部分一致検索
XPathで部分一致を実現するには、contains()関数を利用します。
この関数は、第1引数の文字列の中に第2引数の文字列が含まれているかどうかを判定するものです。
特定のキーワードを含むリンクや、一部が動的に変わるラベルを特定する際に非常に便利です。
# 「送信」という文字が含まれるすべてのdiv要素を取得
elements = driver.find_elements(By.XPATH, "//div[contains(text(), '送信')]")
# 取得した要素のテキストを表示
for el in elements:
print(el.text)
お問い合わせを送信する
データを送信
送信済み
contains関数の注意点は、大文字と小文字が区別されるという点です。
例えば「Python」を探す際に「python」と指定しても、XPath 1.0の標準仕様ではヒットしません。
2026年現在の主要ブラウザが採用しているXPathエンジンにおいても、この挙動は共通しています。
starts-with関数による前方一致検索
特定の文字列で始まる要素を探したい場合は、starts-with()関数が最適です。
「エラー:」で始まる警告文や、「ID_」で始まる動的な属性値を持つ要素の特定に役立ちます。
# 「エラー:」で始まるテキストを持つspan要素を取得
error_message = driver.find_element(By.XPATH, "//span[starts-with(text(), 'エラー:')]")
print(error_message.text)
エラー:パスワードが正しくありません。
なお、XPath 1.0には「ends-with(後方一致)」関数が存在しない点に注意してください。
後方一致が必要な場合は、後述する複雑な記述を用いるか、Python側で取得後にフィルタリングを行うのが一般的です。
空白や改行を無視するnormalize-space関数の活用
WebサイトのHTMLソースコードには、可読性を高めるために不要な改行や半角スペースが含まれていることがよくあります。
通常のtext()検索では、これらの空白文字も厳密に照合されるため、要素が見つからない原因となります。
この問題を解決するのが、normalize-space()関数です。
この関数は、文字列の前後にある空白を取り除き、連続する空白を1つの半角スペースに置き換えてくれます。
# 前後に改行や空白がある要素でも確実に特定する
# ターゲットHTML: <div> 注文を 確定する </div>
xpath_query = "//div[normalize-space(text())='注文を 確定する']"
element = driver.find_element(By.XPATH, xpath_query)
print(f"取得成功: {element.text}")
取得成功: 注文を 確定する
normalize-space()はcontains()と組み合わせて使用することも可能です。
これにより、空白による検索失敗のリスクを大幅に減らすことができます。
XPathでの正規表現検索の代用と実現方法
多くの開発者が「XPathで正規表現(Regex)を使いたい」と考えますが、Seleniumが利用するXPath 1.0には正規表現用の関数は用意されていません。
しかし、複数の条件を組み合わせることで、擬似的に正規表現のような柔軟な検索を実現できます。
複数条件の組み合わせ (and / or)
「AまたはBを含む」や「Aを含み、かつBも含む」といった条件は、論理演算子で記述できます。
# 「完了」または「成功」という文字を含む要素
xpath_or = "//*[contains(text(), '完了') or contains(text(), '成功')]"
# 「2026年」を含み、かつ「5月」も含む要素
xpath_and = "//*[contains(text(), '2026年') and contains(text(), '5月')]"
elements_or = driver.find_elements(By.XPATH, xpath_or)
elements_and = driver.find_elements(By.XPATH, xpath_and)
JavaScript実行による完全な正規表現の利用
どうしても高度な正規表現が必要な場合は、Seleniumのexecute_scriptメソッドを使用して、JavaScript側で要素を抽出する方法があります。
JavaScriptのArray.fromと正規表現オブジェクトを組み合わせることで、XPathだけでは不可能な複雑なパターンマッチングが可能になります。
# JavaScriptを使って正規表現で要素を検索する関数
def find_elements_by_regex(pattern, tag="*"):
script = f"""
const regex = new RegExp('{pattern}');
const elements = Array.from(document.querySelectorAll('{tag}'));
return elements.filter(el => regex.test(el.textContent));
"""
return driver.execute_script(script)
# 「単価:」の後に数字が続く要素を検索
regex_pattern = "単価:\\\\s?\\\\d+"
matched_elements = find_elements_by_regex(regex_pattern, "p")
for el in matched_elements:
print(el.text)
商品の単価: 1500
単価:500
この方法は、ブラウザ側のJavaScriptエンジンを直接利用するため、非常に柔軟で強力です。
ただし、Seleniumの標準的なfind_elementとは異なり、要素の待機(Expected Conditions)との親和性が低い点に留意してください。
実戦で役立つXPath検索の比較表
状況に応じて最適なメソッドを選択できるよう、各検索手法の特徴を以下の表にまとめました。
| 手法 | XPath構文 | 主な用途 |
|---|---|---|
| 完全一致 | [text()='値'] | 固定テキストの検索 |
| 部分一致 | [contains(text(), '値')] | キーワードによる抽出 |
| 前方一致 | [starts-with(text(), '値')] | 接頭辞による特定 |
| 空白除去 | [normalize-space()='値'] | 表記揺れ(空白・改行)対策 |
| 正規表現代用 | [contains(...) and contains(...)] | 複数条件での絞り込み |
テキスト検索における「.」と「text()」の違い
XPathには、contains(text(), '値')とcontains(., '値')という、似て非なる書き方が存在します。
これらには、子要素のテキストを含むかどうかという決定的な違いがあります。
text()を使用する場合
text()は、その要素が直接持つテキストノードのみを対象とします。
例えば、<div>こんにちは<span>世界</span></div>という構造において、div[contains(text(), '世界')]はヒットしません。
「.」(ドット)を使用する場合
一方で、.(コンテキストノード)を指定すると、その要素配下にあるすべてのテキストを結合して検索対象にします。
前述の例では、div[contains(., '世界')]と記述すれば、子要素のspanタグ内にある「世界」という文字に反応してdiv要素を取得できます。
# 子要素に特定のテキストを持つ親要素を特定する
parent_element = driver.find_element(By.XPATH, "//div[contains(., '子要素のテキスト')]")
このように、「.」を使うことで検索の網羅性が向上しますが、意図しない大きな親要素までヒットしてしまうリスクもあります。
ターゲットとするHTMLの構造に応じて、適切に使い分けることが重要です。
2026年のSelenium開発で意識すべきベストプラクティス
XPathによるテキスト検索は非常に強力ですが、乱用するとテストの保守性を低下させる原因にもなります。
ここでは、安定したスクレイピングやテストコードを書くためのポイントを3つ紹介します。
1. 可能な限り上位の要素から絞り込む
//*[contains(text(), '値')]のように、すべての要素(*)を対象に検索を行うと、パフォーマンスが低下します。
//button[contains(text(), '検索')]のように、タグ名を指定して検索範囲を限定することを心がけましょう。
2. 明示的待機を組み合わせる
テキストベースの要素検索は、ページの読み込み状況によって失敗しやすい傾向があります。
WebDriverWaitを利用して、目的のテキストを持つ要素が表示されるまで待機する処理を必ず入れましょう。
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, "//a[contains(text(), 'ダウンロード')]")))
3. 属性検索との併用
テキストだけで要素を特定しようとすると、同じ文言がページ内に複数存在する場合に誤検知が発生します。
//div[@class='menu-item' and contains(text(), '設定')]のように、クラス名や他の属性と組み合わせて一意性を高めるのが定石です。
まとめ
PythonとSeleniumを組み合わせたブラウザ操作において、XPathによるテキスト検索は非常に柔軟な手段を提供してくれます。
完全一致のtext()、部分一致のcontains()、そして空白を処理するnormalize-space()を使い分けることで、ほとんどの要素特定が可能になります。
また、XPath自体は正規表現を直接サポートしていませんが、論理演算子やJavaScript実行を併用することで、複雑なパターンマッチングにも対応できます。
本記事で紹介したテクニックを活用し、変化に強く、精度の高い自動化スクリプトの構築を目指してください。
正確なXPathの記述は、自動化プロジェクトの成功を左右する大きな鍵となります。
