🔍

XPathの記法メモ

2022/11/21に公開

数年前にWebページの一括編集の自動化などで、CSSセレクターでは複雑なノードの参照が難しかったため、XPathを使った際にまとめた記法などをメモとして残しておきます。

はじめに

XPathは通常の構文がありますが、省略して書くことのできる構文も存在します。
また、XPathはバージョンが存在していて、各言語でサポートしているXPathのバージョンによって、扱うことのできない記法もありますので、ご注意ください。

ノードの種類

XPathで扱うノードは次の種類があります。

種類 説明
ルートノード ツリー上の最上位のノードです。
要素ではありません。
要素ノード 要素を表すノードです。
<タグ名> ~ </タグ名><タグ名/>となっている部分が対象です。
テキストノード 要素や属性で囲まれている文字となっているノードです。
コメントノード コメントアウトとして記述されているノードです。
属性ノード 要素に付与されている属性です。
コメントアウトノード <!-- ~ -->となっているコメントアウトのノードです。
処理命令ノード <? ~ ?>となっている処理命令のノードです。
名前空間ノード xmlns:~となっている名前空間のノードです。

パス

XPathはOSのディレクトリのように、階層ごとのノードを/で区切ってパスを表します。
2種類のパスがあり、先頭に/を付けると絶対パス、付けないと相対パスになります。

例えば、"html要素の子要素のbody要素にある子要素のp要素"とすると、絶対パス、相対パスは次のようになります。

絶対パス:

/html/body/p

相対パス:

html/body/p

また、//を使用すると以降の子孫ノード全てを対象にします。
body要素配下にある全てのp要素をとすると次のようになります。

/html/body//p

記法

ルート(最上位)ノードを選択

/を先頭に指定することで、ルート(最上位)ノードを選択することができます。
最初の要素ではなくドキュメントの先頭を指します。

コード例

次のコードは、ルート(最上位)ノードを選択します。

/

ノード名から選択

ノード名から選択するには、ノード名をそのまま指定するか*[name() = "ノード名"]と指定することで選択できます。

コード例

次のコードは、ドキュメント内にある全てのpという名前のノードを選択します。

//p

複数のノードから何番目のノードを選択

複数のノードから何番目のノードを選択するには、対象ノード[position() = 番号]または対象ノード[番号]を指定します。
ノードは1から番号が割り当てられていて、その番号を各括弧を使用して指定することで、指定した番号のノードを選択することができます。

コード例

次のコードは、pノードの3つ目のノードを選択します。

//p[3]

全てのノードを選択

node関数を指定することで、現在の階層の要素やテキストを含む全てのノードを選択することができます。
node関数はnode()のように使用します。

コード例

次のコードは、divノードの子ノード全てを選択します。

//div/node()

全ての要素を選択

*を指定することで、現在の階層の全ての要素を選択することができます。
要素以外のノード(テキストノードなど)は含みません。

コード例

次のコードは、divノードの子要素全てを選択します。

//div/*

自身のノードを選択

自身のノードを選択するには、.またはself::ノードの種類を指定します。
self::ノードの種類の場合は、self::node()のように選択するノードの種類を指定します。

例えば一度検索し終え取得したノードから、再度検索をかけるときに、さらに条件を付けて検索をかけるときなどに役立ちます。

コード例

次のコードは、divノード自身を選択します。

//div/.

自身の前方にある全ての兄弟ノードを選択

自身の前方にある全ての兄弟ノードを選択するには、preceding-sibling::対象ノードを指定します。

コード例

次のコードは、div#foo要素の前方にある全ての兄弟ノードを選択します。

//div[@id="foo"]/preceding-sibling::*

自身の後方にある全ての兄弟ノードを選択

自身の後方にある全ての兄弟ノードを選択するには、following-sibling::対象ノードを指定します。

コード例

次のコードは、div#foo要素の後方にある全ての兄弟ノードを選択します。

//div[@id="foo"]/following-sibling::*

自身の前方にある兄弟ノードを選択

自身の前方にある兄弟ノードを選択するには、preceding::対象ノードを指定します。
ただし、兄弟ノードだけでなく兄弟ノードの子孫ノード、自身の親ノード前方の兄弟ノードと子孫ノードを含みます。

コード例

次のコードは、div#foo要素の前方にある兄弟ノードを選択します。

//div[@id="foo"]/preceding::*

自身の後方にある兄弟ノードを選択

自身の後方にある兄弟ノードを選択するには、following::対象ノードを指定します。
ただし、兄弟ノードだけでなく兄弟ノードの子孫ノード、自身の親ノード後方の兄弟ノードと子孫ノードを含みます。

コード例

次のコードは、div#foo要素の後方にある兄弟ノードを選択します。

//div[@id="foo"]/following::*

特定の子ノードを選択

特定の子ノードを選択するには、ノードの種類またはchild::ノードの種類を指定します。
ノードの種類は、child::node()のように選択するノードの種類を指定します。

コード例

次のコードは、bodyノードの子であるp要素を選択します。

//body/p
//body/child::p

特定の子孫ノードを選択

特定の子孫ノードを選択するには、//ノードの種類またはdescendant::ノードの種類を指定します。
descendant::node()のように選択するノードの種類を指定します。

コード例

次のコードは、bodyノードの子孫であるp要素を選択します。

//body//p

//とdescendantの違い

//descendantには[]を使用して特定のノードを選択した時に違いがあります。
親要素が同じ構成になっている場合、////p[1]と検索した場合、親ごとに1つずつ選択するのに対し、descendantdescendant::p[1]とした場合、1つだけ選択します。

//body//p[1]
//body/descendant::p[1]

自身を含む特定の子孫ノードを選択

自身を含む特定の子孫ノードを選択するには、descendant-or-self::ノードの種類を指定します。
descendant-or-self::node()のように選択するノードの種類を指定します。

コード例

次のコードは、bodyノード自身あるいは子孫であるノードを選択します。

//body/descendant-or-self::node()

親ノードを選択

親ノードを選択するには、..またはparent::ノードの種類を指定します。
parent::node()のように選択するノードの種類を指定します。

コード例

次のコードは、最初のp要素の親要素を選択します。

//p[1]/..

先祖ノードを選択

先祖ノードを選択するには、..またはancestor::ノードの種類を指定します。
ancestor::node()のように選択するノードの種類を指定します。

先祖ノードは一致した全てのノードを選択します。

コード例

次のコードは、最初のp要素の先祖ノードであるdiv要素全てを選択します。

//p[1]/ancestor::div

自身を含む先祖ノードを選択

先祖ノードを選択するには、ancestor-or-self::ノードの種類を指定します。
ancestor-or-self::node()のように選択するノードの種類を指定します。

コード例

次のコードは、main要素にある子要素の最初のdiv要素の先祖ノードであるdiv要素全てと自身を選択します。

//main/div[1]/ancestor-or-self::div

特定のID名(ID属性値)に一致するノードを選択

特定のID名(id属性値)に一致するノードを選択するには、id関数または対象ノード[@id="ID名"]を指定します。
id関数はid("ID名")のように、第一引数にID名(ID属性値)を指定して使用します。
id関数はドキュメント全体の中から、対象のID名(id属性値)に一致するノードを選択します。

コード例

次のコードは、ドキュメント全体の中からfooというID名(id属性値)に一致するノードを選択します。

id("foo")
//*[@id="foo"]

特定のクラス名に一致するノードを選択

特定のクラス名に一致するノードを選択するには、対象ノード[@class="クラス名"]を指定します。
単一指定されているクラス名のみ一致し、複数指定している場合には一致しません。

[]は条件を付けることができ、@属性名は対象の属性を条件にすることができます。

コード例

次のコードは、ドキュメント全体の中からfooというクラス名に一致する要素を選択します。

//*[@class="foo"]

特定のクラス名を含むノードを選択

特定のクラス名を含むノードを選択するには、対象ノード[contains(concat(" ", normalize-space(@class), " "), " クラス名 ")]を指定します。

contains関数は、第一引数で指定した文字に、第二引数で指定した文字が部分一致するかを判定します。
concat関数は、引数で指定した文字を連結します。
normalize-space関数は、第一引数で指定した文字の両端のスペースを除き、連続する2つ以上のスペースを1つのスペースに置き換えます。

コード例

次のコードは、ドキュメント全体の中からfooというクラス名を持つ要素を選択します。

//*[contains(concat(" ", normalize-space(@class), " "), " foo ")]

特定の属性が存在するノードを選択

特定の属性が存在するノードを選択するには、対象ノード[attribute::属性名]または対象ノード[@属性名]を指定します。

コード例

次のコードは、href属性が存在するa要素を選択します。

//a[@href]

属性名の一部が含まれるノードを選択

属性名の一部が含まれるノードを選択するには、対象ノード[attribute::*[contains(local-name(), "検索属性名")]]を指定します。

コード例

次のコードは、属性名に"-foo-"が含まる要素を選択します。

//*[attribute::*[contains(local-name(), "-foo-")]]

属性名が特定の文字から始まるノードを選択

属性名が特定の文字から始まるノードを選択するには、対象ノード[attribute::*[starts-with(local-name(), "検索属性名")]]を指定します。

コード例

次のコードは、属性名が"data-"から始まる要素を選択します。

//*[attribute::*[starts-with(local-name(), "data-")]]

特定の属性値を持つノードを選択

特定の属性値を持つノードを選択するには、対象ノード[attribute::属性名="属性値"]または対象ノード[@属性名="属性値"]を指定します。
属性値は完全一致による判定が行われます。

コード例

次のコードは、id属性の値がfooの要素を選択します。

//*[@id="foo"]

特定の属性値が含まれるノードを選択

特定の属性値が含まれるノードを選択するには、対象ノード[contains(attribute::属性名, "属性値")]または対象ノード[contains(@属性名, "属性値")]を指定します。
属性値は部分一致による判定が行われます。

コード例

次のコードは、href属性の値にfooが含まれるa要素を選択します。

//a[contains(@href, "foo")]

特定の属性値から始まるノードを選択

特定の属性値から始まるノードを選択するには、対象ノード[starts-with(attribute::属性名, "属性値")]または対象ノード[starts-with(@属性名, "属性値")]を指定します。

コード例

次のコードは、href属性の値がhttpから始まるa要素を選択します。

//a[starts-with(@href, "http")]

特定の属性値で終わるノードを選択

特定の属性値で終わるノードを選択するには、対象ノード[ends-with(attribute::属性名, "属性値")]または対象ノード[ends-with(@属性名, "属性値")]を指定します。
ただしends-with関数はXPath 1.0では対応していません。
XPath 1.0ではsubstring関数とstring-length関数を組み合わせて実現します。

コード例

次のコードは、href属性の値がhttpから始まるa要素を選択します。

XPath 2.0の場合:

//a[ends-with(@href, ".pdf")]

XPath 1.0の場合:

//a[substring(@href, string-length(@href) - 3) = ".pdf"]

属性が付与されていないノードを選択

属性が付与されていないノードを選択するには、対象ノード[not(@*)]または対象ノード[attribute::*]または対象ノード[count(attribute::*) = 0]を指定します。

コード例

次のコードは、属性を持たないdiv要素を選択します。

//div[not(@*)]

テキストノードを選択

テキストノードを選択するには、text関数を指定します。
CDATAの中身を含みます。

コード例

次のコードは、p要素の全てのテキストノードを選択します。

//p/text()

特定のテキストが含まれているノードを選択

特定のテキストが含まれているノードを選択するには、対象ノード[contains(text(), "検索文字")]を指定します。
contains関数を用いて対象ノードのテキストノードから文字を検索して判定を行います。

コード例

次のコードは、p要素のテキストにfooという文字が含まれているp要素を選択します。

//p[contains(text(), "foo")]

指定番号(位置)以前のノードを選択

指定番号(位置)以前のノードを選択するには、対象ノード[position() < 基準位置]または対象ノード[position() <= 基準位置]を指定します。
position関数は親ノードを基準とした位置を取得します。
比較演算子である<は対象の位置未満、<=は対象の位置以下となります。

コード例

次のコードは、3つ目より前の位置のp要素を選択します。

指定番号(位置)以降のノードを選択

指定番号(位置)以降のノードを選択するには、対象ノード[position() > 基準位置]または対象ノード[position() >= 基準位置]を指定します。
position関数は親ノードを基準とした位置を取得します。
比較演算子である>は対象の位置を超える、>=は対象の位置以上となります。

コード例

次のコードは、3つ目より後の位置のp要素を選択します。

//p[position() > 3]

剰余による指定数置きにノードを選択

剰余による指定数置きにノードを選択するには、mod演算子を指定します。
数値 mod 数値のように指定し、左辺を右辺の値で除算した余りで得られた数を奇数または偶数として、ノードを選択するようにします。
余りが"1"の時は奇数、"0"の時は偶数で選択することができます。

コード例

次のコードは、2つ置きの偶数の位置のdiv要素を選択します。

//div[position() mod 2 = 1]

最後のノードを選択

最後のノードを選択するには、last関数を指定します。
last関数は親ノードを基準とした位置を取得します。

コード例

次のコードは、最後のp要素を選択します。

//p[last()]

コメントアウトノードを選択

コメントアウトのノードを選択するには、comment関数を指定します。

コード例

次のコードは、ドキュメント内にある全てのコメントアウトノードを選択します。

//comment()

特定の子ノードを持つノードを選択

特定の子ノードを持つノードを選択するには、対象ノード[子ノード]を指定します。

コード例

次のコードは、子要素である#foo要素が含まれるdiv要素を選択します。

div[*[@id="foo"]]

特定の子ノードを含まないノードを選択

特定の子ノードを持たないノードを選択するには、対象ノード[not(子ノード)]を指定します。
not関数を使用することで引数に指定する内容を反対評価します。

コード例

次のコードは、子要素である#foo要素を含まないdiv要素を選択します。

div[not(*[@id="foo"])]

特定の子孫ノードを持つノードを選択

特定の子孫ノードを持つノードを選択するには、対象ノード[.//子孫ノード]を指定します。

コード例

次のコードは、子孫要素である#foo要素が含まれるdiv要素を選択します。

div[*[@id="foo"]]

特定の子孫ノードを含まないノードを選択

特定の子孫ノードを持たないノードを選択するには、対象ノード[not(.//子孫ノード)]を指定します。
not関数を使用することで引数に指定する内容を反対評価します。

コード例

次のコードは、子孫要素である#foo要素を含まないdiv要素を選択します。

div[not(*[@id="foo"])]

複数の条件でノードを選択

複数の条件でノードを選択するには、or演算子またはand演算子を指定します。
また丸括弧(())でグループ化することで、より複雑に条件を指定することができます。

コード例

次のコードは、ID属性値がfooまたはbarで、かつ子要素にp要素を持つdiv要素を選択します。

//div[(@id="foo" or @id="bar") and p]

テキストの一部を切り出す

テキストの一部を切り出すには、substring関数を指定します。
substring(テキスト, 開始位置, 文字数)のように指定します。
第一引数で指定する対象となるノードは単一である必要があります。

コード例

次のコードは、p要素のテキストから1文字目から4文字を切り出します。

substring(//p[1]/text(), 1, 4)

テキストが先頭から一致するか判定

テキストが先頭から一致するか判定するには、starts-with関数を指定します。
starts-with(テキスト, 判定する文字)のように指定します。
第一引数で指定する対象となるノードは単一である必要があります。

コード例

次のコードは、p要素のテキストがfooで始まっているかを判定します。

starts-with(//p[1]/text(), "foo")

ノードに付与されている特定の属性の値を取得

ノードに付与されている特定の属性の値を取得するには、対象ノード/attribute::属性名または対象ノード/@属性名を指定します。
返ってくる内容は属性名と属性値の両方を含みます。

属性値のみを取得したい場合はstring関数を使用します。

コード例

次のコードは、a要素のhref属性値を取得します。

string(//a[1]/@href)

ノードに付与されている全ての属性の値を取得

ノードに付与されている全ての属性の値を取得するには、対象ノード/attribute::*または対象ノード/@*を指定します。
返ってくる内容は属性名と属性値の両方を含みます。

コード例

次のコードは、a要素の属性値を全て取得します。

//a[1]/@*

ノード名を取得

ノード名を取得するには、name関数を指定します。

コード例

次のコードは、body要素の1つ目の子要素の要素名を取得します。

name(/html/body/*[1])

ノードの数を取得

ノードの数を取得するには、count関数を指定します。

コード例

次のコードは、ドキュメント内の全てのノードの数を取得します。

count(//*)

両端のスペースの削除および連続するスペースの置き換え

normalize-space関数を使用することで、両端のスペースの削除および連続するスペースを1つのスペースに置き換えることができます。

コード例

次のコードは、p要素に付与されているクラス名のスペースを処理を行い取得します。

normalize-space(//p[1]/@class)

文字列を連結

文字列を連結するには、convat関数を指定します。
引数で指定した文字列を連結します。

コード例

次のコードは、p要素に付与されているクラス名のスペースを処理を行い取得します。

concat(//p[1]/text(), //p[2]/text())

文字列を置換

文字列を置換するには、translate関数を指定します。
translate(元となる文字列, 検索文字列, 置換文字列)と引数を指定します。

translate関数での文字列の置換は一般的な処理と違い、第二引数で指定した文字列の1文字の位置と、第三引数で指定した文字列の同じ位置に該当する1つの文字を置換します。
第二引数の1文字の位置が第三引数の位置に見つからなかった場合は削除されます。

例えば、translate("how do you do", "ohw", "AB")とした場合、結果は"BA dA yAu dA"となります。
これは、"o"の位置は1なので、その位置に該当する"A"という文字に置き換わります。
同じように"h"の位置は2なので、その位置に該当する"B"という文字に置き換わります。
最後の"w"の位置は3ですが、置換後の文字列にはその位置に該当する文字が存在しないため削除されます。

コード例

次のコードは、p要素のテキストに含まれる"a"または"b"または"c"を大文字に変換します。

translate(//p[1]/text(), "abc", "ABC")

値を数値として取得

値を数値として取得するには、number関数を指定します。
例えばwidth属性など取得する場合、string関数で取得すると文字列として取得しますが、これをnumber関数を用いることで数値として取得することができます。

コード例

次のコードは、width属性の値を数値として取得します。

number(//img[1]/@width)

複数のノードを検索して選択

複数のノードを検索して選択するには、|を指定します。
例えば//foo|//barと指定した場合、fooというノードを検索した後に、barというノードを検索して選択します。

コード例

次のコードは、h1要素、h2要素、h3要素を順番に検索し選択します。

//h1|//h2|//h3

比較演算子

結果同士や属性などの比較で使用することのできる演算子は次の通りです。

演算子名 演算子 説明
等しい = 値が等しい場合はtrueを返す
等しくない != 値が等しくない場合はtrueを返す
より大きい > 左の値が右の値より大きい(超える)場合はtrueを返す
以上 >= 左の値が右の値と同じか大きい(超える)場合はtrueを返す
より小さい < 左の値が右の値より小さい場合はtrueを返す
以下 <= 左の値が右の値と同じか未満の場合はtrueを返す

コード例

次のコードは、img要素とalt属性のある要素の数を比較します。

count(//img) = count(//*[alt])

各言語やツールでXPathを書く

JavaScript

JavaScriptでは、document.evaluateメソッドを使用することで、XPathを扱うことができます。

// XPathからノードを検索し取得
const xpathResult = document.evaluate('//h1', document, null, XPathResult.ANY_TYPE, null);

// 結果から要素を取得
const h1Elem = xpathResult.iterateNext();

console.log(h1Elem);

https://developer.mozilla.org/ja/docs/Web/API/Document/evaluate

PHP

PHPでは、SimpleXMLの機能であるSimpleXMLElement::xpathメソッドを使用するか、DOMDocumentの機能であるDOMXPath::queryメソッドを使用することで、XPathを扱うことができます。

SimpleXMLElement::xpathメソッドの場合:

// XMLファイルを読み込み
$xml = simplexml_load_file('sample/test.xml');

$results = $xml->xpath('//h1');

foreach ($results as $node) {

}

DOMXPath::queryメソッドの場合:

// DOMドキュメントを生成
$doc = new DOMDocument();

// XMLファイルを読み込み
$doc->load('sample/test.xml');

// XMLのDOMをXPathが扱えるよう変換
$xpath = new DOMXPath($doc);

// XPathからノードを検索して取得
$results = $xpath->query('//h1');

foreach ($results as $result) {

}

https://www.php.net/manual/ja/simplexmlelement.xpath.php

https://www.php.net/manual/ja/domxpath.query.php

Python

Pythonではいくつか方法がありますが、Seleniumというブラウザを操作するためのパッケージの機能を利用することで、XPathを扱うことができます。
find_element_by_xpathメソッドあるいはfind_elements_by_xpathメソッドを使用します。

# モジュールパッケージの読み込み
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# ヘッドレスモードで起動するオプション
options = Options()
options.add_argument('--headless')

# Chromeを起動
driver = webdriver.Chrome(options=options)

# URLを開く
driver.get('https://www.example.co.jp/test.html')

# XPathでノードを取得
node = driver.find_element_by_xpath('//h1')

https://pypi.org/project/selenium/

https://kurozumi.github.io/selenium-python/locating-elements.html#locating-by-xpath

Ruby

Rubyではいくつか方法がありますが、Seleniumというブラウザを操作するためのパッケージの機能を利用することで、XPathを扱うことができます。
find_elementメソッドあるいはfind_elementsメソッドを使用します。

# モジュールパッケージの読み込み
require 'selenium-webdriver'

# ヘッドレスモードで起動するオプション
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')

# Chromeを起動
driver = Selenium::WebDriver.for :chrome

# URLを開く
driver.get('https://www.example.co.jp/test.html')

# XPathでノードを取得
elem = driver.find_element(:xpath, '//h1')

https://rubygems.org/gems/selenium-webdriver/

ブラウザの開発者ツール

ブラウザの開発者ツールにあるインスペクターパネルやコンソールパネルなどで、$x関数を使用することで、XPathを扱うことができます。

const results = $x('//h1');

console.log(results);

https://developer.chrome.com/docs/devtools/console/utilities/#xpath-function

https://firefox-source-docs.mozilla.org/devtools-user/web_console/helpers/index.html

Discussion