Webブラウザの自動化を支えるSeleniumを利用している際、多くのエンジニアが直面するのが「要素の特定が困難」という課題です。
近年のWebフロントエンド開発では、ReactやVue.jsといったフレームワークの普及により、HTML要素のIDやクラス名が実行時に生成される「動的な値」になることが珍しくありません。
このような不安定な要素を確実にキャプチャするためには、完全一致ではなく、特定の手がかりを基に要素を絞り込む技術が不可欠となります。
そこで強力な武器となるのが、XPathに備わっている「contains関数」と「starts-with関数」です。
本記事では、PythonとSeleniumを組み合わせた環境で、これら2つの関数をどのように使い分け、複雑な動的要素を効率的に特定するかを詳しく解説します。
動的な要素特定におけるXPathの重要性
WebアプリケーションのUIは常に進化しており、テスト自動化において「変わらない属性」を見つけることはますます難しくなっています。
例えば、ボタン要素のIDが button-7b89a2 のように、ページを更新するたびに末尾の数値が変わってしまうケースです。
通常の find_element(By.ID, "button-7b89a2") という指定方法では、次回の実行時に要素が見つからず、テストが失敗してしまいます。
こうした動的な属性値に対処するためには、不変の部分だけを抽出して検索する手法が求められます。
XPath(XML Path Language)は、XMLドキュメントやHTMLドキュメント内の要素を指定するための言語であり、標準で強力な文字列操作関数をサポートしています。
その中でも、特定の文字列が含まれているかを確認する「contains」と、特定の文字列で始まっているかを確認する「starts-with」は非常に利用頻度が高い機能です。
これらを習得することで、従来のID指定やCSSセレクターだけでは対応しきれなかった複雑なDOM構造に対しても、柔軟にアクセスできるようになります。
contains関数の基本的な使い方と活用シーン
contains関数は、指定した属性の値に「特定の文字列が含まれているかどうか」を判定するための関数です。
基本的な構文は //タグ名[contains(@属性, '検索文字列')] と記述します。
この関数の最大の利点は、属性値の一部が変化しても、共通するキーワードがあれば要素を特定できる点にあります。
クラス名の一部を利用した要素特定
現代のWebサイトでは、1つの要素に複数のクラスが割り当てられていることが一般的です。
例えば <div class="btn btn-primary active-state"> という要素がある場合、CSSセレクターでは複数のクラスを指定する必要があります。
しかし、XPathのcontains関数を使えば、”primary” という単語が含まれている要素を簡単に抽出できます。
# contains関数を使用したクラス名の一部検索
element = driver.find_element(By.XPATH, "//div[contains(@class, 'primary')]")
このように記述することで、クラス名が btn-primary であろうと primary-button であろうと、柔軟にマッチさせることが可能です。
テキストコンテンツによる検索
contains関数は属性だけでなく、要素内の「テキスト(文字列)」に対しても有効です。
//button[contains(text(), '送信')] と記述すれば、「送信する」や「今すぐ送信」といった、指定した文字列を含むすべてのボタンを対象にできます。
これは、ボタンのラベルが微妙に変化する場合や、特定のキーワードを含むリンクをクリックしたい場合に非常に強力です。
# テキスト内容に「ログイン」を含むボタンを取得
login_button = driver.find_element(By.XPATH, "//button[contains(text(), 'ログイン')]")
starts-with関数の基本的な使い方と活用シーン
starts-with関数は、その名の通り「特定の文字列で始まっているかどうか」を判定します。
基本的な構文は //タグ名[starts-with(@属性, 'プレフィックス文字列')] です。
contains関数よりも限定的な条件となるため、意図しない要素への誤マッチを防ぎやすいというメリットがあります。
システム生成されるIDのプレフィックス指定
多くのフレームワークでは、IDの先頭に「コンポーネントの種類」を付与し、その後にランダムな数値を生成します。
例えば id="user_item_102" や id="user_item_205" のような要素です。
このようなケースでは、starts-with(@id, 'user_item_') と指定することで、IDの末尾が変化しても確実にターゲットを捉えることができます。
# IDが「user_」で始まる入力フィールドを特定
user_input = driver.find_element(By.XPATH, "//input[starts-with(@id, 'user_')]")
これにより、動的に生成されるIDを持つ要素であっても、安定してスクレイピングや自動テストを継続できるようになります。
containsとstarts-withの比較と使い分け
これら2つの関数は似ていますが、状況に応じて適切に使い分けることが重要です。
以下の表に、それぞれの特徴と推奨される使用ケースをまとめました。
| 関数名 | 判定条件 | 主な利用ケース | 柔軟性 |
|---|---|---|---|
| contains | 部分一致(どこかに含まれる) | 複数のクラスを持つ要素、長いテキストの一部 | 非常に高い |
| starts-with | 前方一致(先頭から始まる) | 接頭辞が固定された動的ID、特定の命名規則 | 中程度 |
基本的には、要素をより広範囲に探したい場合はcontainsを使い、要素の構造がある程度予測できる場合はstarts-withを使うのがセオリーです。
前方一致の方が検索のスコープが絞られるため、パフォーマンスや正確性の面で優位に働くことがあります。
実践的なPythonコード:動的要素の自動操作
それでは、実際のWebサイトを想定した具体的なコード例を見ていきましょう。
ここでは、動的に変化するIDやクラスを持つフォームを操作するシミュレーションを行います。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# WebDriverのセットアップ
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
try:
# サンプルページへのアクセス(仮のURL)
driver.get("https://example.com/dynamic-form")
# 1. starts-withを使って動的IDを持つ入力フィールドを特定
# ID例: id="entry_98234", id="entry_11234"
search_input = driver.find_element(By.XPATH, "//input[starts-with(@id, 'entry_')]")
search_input.send_keys("Seleniumの学習")
# 2. containsを使って複数のクラスを持つボタンを特定
# クラス例: class="btn btn-submit-v2 ripple-effect"
submit_button = driver.find_element(By.XPATH, "//button[contains(@class, 'btn-submit')]")
# 3. テキストに特定の文言が含まれる要素を取得してクリック
# テキスト例: 「利用規約に同意して次へ進む」
agreement_link = driver.find_element(By.XPATH, "//*[contains(text(), '利用規約')]")
print("要素の特定に成功しました。")
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
driver.quit()
要素の特定に成功しました。
このコードでは、IDの一部が変わる入力欄や、バージョン管理のためにクラス名が変更されるボタンを、不変のキーワードだけで的確にキャッチしています。
より高度なXPath関数の組み合わせ
XPathの真骨頂は、複数の関数や論理演算子を組み合わせることで、さらに複雑な条件を指定できる点にあります。
例えば、「特定の文字列を含み、かつ特定の文字列で始まらない」といった高度なフィルタリングも可能です。
and演算子による複数条件の指定
「クラス名に button を含み、かつテキストに 送信 を含む要素」を探したい場合は、and を使用します。
# クラス名とテキストの両方を条件に指定
element = driver.find_element(By.XPATH, "//button[contains(@class, 'button') and contains(text(), '送信')]")
このように条件を重ねることで、ページ内に似たような要素が複数存在する場合でも、ターゲットを唯一無二に絞り込むことが可能になります。
normalize-space関数の併用
WebサイトのHTMLソース内には、改行や余計な空白が含まれていることがよくあります。
特に text() を使った検索では、空白の有無でマッチングに失敗することが少なくありません。
その際、normalize-space関数を組み合わせると、余分な空白を除去してから比較を行えます。
# テキスト前後の空白を無視して検索
# 対象HTML: <span> ログイン </span>
element = driver.find_element(By.XPATH, "//span[contains(normalize-space(), 'ログイン')]")
XPath利用時の注意点とベストプラクティス
containsやstarts-withは非常に便利ですが、使いすぎるとコードの可読性が低下したり、予期せぬ要素にマッチしたりするリスクもあります。
まず第一に、「不変で一意なID」が存在する場合は、そちらを優先して使用すべきです。
動的な要素に対してのみ、これらのXPath関数を適用するようにしましょう。
また、属性値の一部を指定する際は、短すぎるキーワード(例: “a” や “btn”)は避け、できるだけユニークな単語を選ぶことが重要です。
あまりに一般的な言葉を指定すると、ヘッダーやフッターにある関係のない要素まで取得してしまう可能性があるためです。
さらに、XPathはCSSセレクターに比べてブラウザの解析コストがわずかに高い傾向があります。
極端に巨大なDOM構造を持つページで、何百回も複雑なXPathを実行すると、実行速度に影響を与える場合があります。
パフォーマンスを重視する場合は、できるだけパスを短くし、//div のような広範囲な検索ではなく、特定の親要素から辿る相対パスを活用してください。
まとめ
PythonとSeleniumを用いたブラウザ自動化において、XPathの contains と starts-with 関数は、動的なWeb要素を制御するための必須技術です。
contains を使えば、クラス名の一部やテキストの断片から柔軟に要素を特定でき、starts-with を使えば規則性のある動的IDを確実に見つけることができます。
Webアプリケーションの構造は日々変化しますが、これらの関数を使いこなすことで、「壊れにくい」堅牢な自動化スクリプトを構築することが可能になります。
さらに高度な and 条件や normalize-space と組み合わせることで、どんなに複雑なモダンWebサイトでも自在に操作できるようになるでしょう。
まずは開発者ツール(F12)のコンソールなどでXPathの挙動を試し、最適なセレクターを見つける習慣をつけてみてください。
