子供を条件にして親を選択するときXpathをどう書く?


問題

以下のようなHTMLで「次へ」ボタンをクリックする挙動を再現するために、aタグをクリックさせたいとします。「次へ」という文字列を使いXPathで検索をかけると、どうしても//div/a/span/span[contains(text(), '次へ')]のようになってしまい、aタグを選択することができません。

だからといって、divタグより上のタグで検索をかけると、//div/a[2]のようにマジックナンバーを使って何番目の要素か指定したり、//div/a[last()]のようにHtmlタグのツリー構造に強く依存するなど、苦渋の決断を強いられてしまいます。

<div class="ui-controlgroup-controls">
    <a href="url1" data-role="button" data-theme="f" data-corners="true" data-shadow="true" data-iconshadow="true" data-wrapperels="span" class="ui-btn ui-btn-up-f ui-shadow ui-btn-corner-all ui-first-child">
        <span class="ui-btn-inner">
            <span class="ui-btn-text">前へ</span>
        </span>
    </a>
    <a href="url2" data-role="button" data-theme="f" data-corners="true" data-shadow="true" data-iconshadow="true" data-wrapperels="span" class="ui-btn ui-btn-up-f ui-shadow ui-btn-corner-all ui-last-child">
        <span class="ui-btn-inner">
            <span class="ui-btn-text">次へ</span>
        </span>
    </a>
</div>

その手があったか

OctoparseのXpath紹介ページ「XPathとは?XPathの基本知識を学ぼう!」を読んでいて、解決方法を思いつきました。その時の画像です。

ピンクの下線で強調した//li[a]//a[contain(text(), 'Next')]を組み合わせると、//li[a[contain(text(), 'Next')]]となることに気が付いたのです。

ここまで気が付けば後は簡単です。

import selenium
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
from time import sleep

options = ChromeOptions()
# options.add_argument('--headless')

driver = Chrome(options=options)

#

url = "??????? this is url ?????????"
driver.get(url)
# sleep(0.5)

input_element :selenium.webdriver.remote.webelement.WebElement
# 施設空き状況

input_element = driver.find_element_by_css_selector("施設空き状況ボタンのCSSセレクタ")
input_element.click()
# sleep(0.5)

# sleep(0.5)

input_element = driver.find_element_by_css_selector("地域からボタンのCSSセレクタ")
input_element.click()
# sleep(0.5)

# sleep(0.5)

input_element = driver.find_element_by_xpath("//a[span[span[contains(text(), '次へ')]]]")
input_element.click()
# sleep(0.5)

"//a[span[span[contains(text(), '次へ')]]]"としましたが、今回のHTMLの場合は"//a[span/span[contains(text(), '次へ')]]"でも同様の結果が得られますね。

また、「次へ」という文字列を使って判別していますが、例えば、クラス指定で//a[span/span[@class='ui-btn-text']]とか、idがあれば、//a[span/span[@id='next-button']]などもできそうです。

Xpathは本当に便利です。Xpathに感謝です。

別の方法 (2020/5/14 追記)

この記事で対象にしているHTMLでは、以下のXPATHはすべて同じ結果となります。

  • "//a[span/span[contains(text(), '次へ')]]"
  • "//a[span[span[contains(text(), '次へ')]]]"
  • "//a/span/span[contains(text(), '次へ')]/../.."
  • "//a/span/span[contains(text(), '次へ')]/parent::*/parent::*"
  • "//a/span/span[contains(text(), '次へ')]/parent::node()/parent::node()"
  • "//a/span[span[contains(text(), '次へ')]]/.."
  • "//a/span[span[contains(text(), '次へ')]]/parent::*"
  • "//a/span[span[contains(text(), '次へ')]]/parent::node()"

参考にしたページ

#working-with-xpaths

  • Windows 10
  • Miniconda 3
  • Python 3.8.2
  • Selenium 3.141.0
  • Chrome Driver 80.0.3987.106

PythonとSeleniumのバージョンさえ同じであれば、挙動は同じだと思われます。

Excelsior!