Open1

xpathってなんじゃい

noznoz

xpathってなんじゃい

スクレイピング (selenium利用) でのxpathとは

XPathの由来

XPath (XML Path Language) は1999年にW3Cによって策定されたXMLドキュメント内のノードを選択するためのクエリ言語です。XPathは「XML Path Language」の略で、XMLドキュメントの階層構造を「パス」の概念で表現し、特定の要素を指定できます。

代表的な使われ方

  • Webスクレイピング: HTMLドキュメント内の特定の要素を取得
  • XML処理: XML文書の解析とデータ抽出
  • テスト自動化: UI要素の特定と操作
  • データ変換: XSLT(XSL Transformations)での使用

スクレイピングでXPathを知っておくべき理由

  • CSSセレクタでは不可能な「親要素への遡り」「兄弟要素の取得」「テキスト内容での検索」ができる
  • 動的サイトや複雑なDOM構造でも確実に目的の要素を特定できる
  • 一つのXPath式で複数の条件を組み合わせた高度な検索が可能

1. 基本構文

# 絶対パス: ルートから要素まで
driver.find_element(By.XPATH, "/html/body/div/p")

# 相対パス: どこからでも該当要素を検索
driver.find_element(By.XPATH, "//p")

# 属性指定
driver.find_element(By.XPATH, "//div[@class='content']")

# 複数属性の指定
driver.find_element(By.XPATH, "//input[@type='text'][@name='username']")

# 属性値の部分一致
driver.find_element(By.XPATH, "//div[contains(@class, 'product')]")

# 属性値の前方一致
driver.find_element(By.XPATH, "//div[starts-with(@id, 'item-')]")

# 属性の存在確認(値は問わない)
driver.find_element(By.XPATH, "//img[@alt]")

# 属性が存在しない要素
driver.find_element(By.XPATH, "//div[not(@class)]")

詳細な属性指定パターン

基本的な属性マッチング

# 完全一致
driver.find_element(By.XPATH, "//div[@class='menu-item']")

# 複数の値を持つ属性(class属性など)での完全一致
driver.find_element(By.XPATH, "//div[@class='nav item active']")

# 複数属性の組み合わせ(AND条件)
driver.find_element(By.XPATH, "//button[@type='submit'][@disabled='true']")

文字列関数を使った属性マッチング

# contains(): 部分一致(最も頻繁に使用)
driver.find_element(By.XPATH, "//div[contains(@class, 'product')]")

# starts-with(): 前方一致
driver.find_element(By.XPATH, "//input[starts-with(@name, 'user_')]")

# ends-with(): 後方一致(XPath 2.0以降、一部のブラウザでサポート外)
# 代替案: substring関数を使用
driver.find_element(By.XPATH, "//img[substring(@src, string-length(@src) - 3) = '.jpg']")

# normalize-space(): 前後の空白を除去してマッチング
driver.find_element(By.XPATH, "//span[normalize-space(@title)='重要な情報']")

属性値の条件指定

# 数値比較(文字列として比較されるため注意)
driver.find_element(By.XPATH, "//input[@maxlength > '10']")

# 複数の値のいずれかにマッチ
driver.find_element(By.XPATH, "//input[@type='text' or @type='email' or @type='password']")

# 特定の値以外
driver.find_element(By.XPATH, "//div[@status != 'disabled']")

# 属性の存在チェック
driver.find_element(By.XPATH, "//img[@alt]")  # alt属性が存在する
driver.find_element(By.XPATH, "//div[not(@hidden)]")  # hidden属性が存在しない

data属性やカスタム属性

# data属性の取得
driver.find_element(By.XPATH, "//div[@data-product-id='12345']")
driver.find_element(By.XPATH, "//button[contains(@data-action, 'purchase')]")

# カスタム属性
driver.find_element(By.XPATH, "//section[@role='main']")
driver.find_element(By.XPATH, "//input[@aria-label='検索キーワード']")

実用的な属性指定の例

# CSSクラスが複数設定されている場合の安全な指定方法
# NG: class="nav-item active selected" に対して [@class='nav-item'] は失敗
# OK: contains関数を使用
driver.find_element(By.XPATH, "//li[contains(@class, 'nav-item')]")

# 動的に生成されるIDへの対応
driver.find_element(By.XPATH, "//div[starts-with(@id, 'product-') and contains(@id, '-details')]")

# フォーム要素の柔軟な指定
driver.find_element(By.XPATH, "//input[(@type='text' or @type='email') and @required]")

# 複数条件での絞り込み
driver.find_element(By.XPATH, "//a[@href and contains(@class, 'btn') and not(contains(@class, 'disabled'))]")

2. よく使う軸(Axes)

  • descendant::: 子孫要素すべて
  • parent::: 親要素
  • following-sibling::: 後続の兄弟要素
  • preceding-sibling::: 先行の兄弟要素
# 親要素を取得
driver.find_element(By.XPATH, "//span[@class='price']/parent::div")

# 次の兄弟要素を取得
driver.find_element(By.XPATH, "//h2/following-sibling::p[1]")

3. 述語(Predicates)

# インデックス指定(1から始まる)
driver.find_element(By.XPATH, "//div[@class='item'][1]")

# 最後の要素
driver.find_element(By.XPATH, "//div[@class='item'][last()]")

# 条件指定
driver.find_element(By.XPATH, "//a[contains(@href, 'example.com')]")

4. 便利な関数

# テキスト内容で検索
driver.find_element(By.XPATH, "//button[text()='送信']")

# 部分一致
driver.find_element(By.XPATH, "//div[contains(@class, 'product')]")

# 正規化された空白でのテキスト検索
driver.find_element(By.XPATH, "//span[normalize-space()='価格']")

# 複数条件のOR
driver.find_element(By.XPATH, "//input[@type='text' or @type='email']")

# 複数条件のAND
driver.find_element(By.XPATH, "//div[@class='item' and @data-status='active']")

5. 実践的なスクレイピングパターン

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

# 商品リストから価格を取得
prices = driver.find_elements(By.XPATH, "//div[@class='product']//span[@class='price']")

# テーブルの特定行・列を取得
cell = driver.find_element(By.XPATH, "//table//tr[2]/td[3]")

# 動的に生成されるIDを持つ要素
element = driver.find_element(By.XPATH, "//div[starts-with(@id, 'dynamic_')]")

# ネストしたリストアイテム
items = driver.find_elements(By.XPATH, "//ul[@class='menu']//li[not(ul)]")

6. パフォーマンスとメンテナンス性の考慮

避けるべきパターン:

# 深いネスト(脆弱)
driver.find_element(By.XPATH, "/html/body/div[1]/div[2]/div[3]/span[1]")

# インデックスに依存しすぎ(レイアウト変更に弱い)
driver.find_element(By.XPATH, "//div[5]/p[2]")

推奨パターン:

# 意味のある属性を使用
driver.find_element(By.XPATH, "//div[@data-testid='user-profile']")

# 複数の候補を用意
xpath_candidates = [
    "//button[@id='submit-btn']",
    "//button[text()='送信']",
    "//input[@type='submit']"
]

7. デバッグとテスト

# ブラウザの開発者ツールでXPathをテスト
# Console: $x("//div[@class='content']")

# 要素が見つからない場合のハンドリング
try:
    element = driver.find_element(By.XPATH, "//div[@class='content']")
except NoSuchElementException:
    print("要素が見つかりません")

XPathはHTMLの構造を理解し、柔軟に要素を特定できる強力なツールですが、サイトの構造変更に敏感なため、堅牢なセレクタを作成することが重要です。