XPath 学習ノート

Web スクレイピングの基本技術である XPath に関するノート。さしあたり XPath 1.0 だけで済ませるが、これしか習得しなくても十分いろいろなことが実現できる。

練習方法

ブラウザーの開発ツールのコンソールを利用するか、Scrapy のシェルを利用する。

開発ツールのコンソール

モダンなウェブブラウザーには開発ツールというのが実装されていて、そのうちのコンソールで XPath 式を評価することができる。Chrome 系では関数 $x() が XPath を評価する。

Todo

できればスクリーンショットをここに入れる。

  1. ブラウザーで対象となるファイルのローカルパスなり URL なりを指定して開く。

  2. 例えばページ上で右クリックメニューを表示して、そこから「検証」を選択する。

  3. 雑多な画面が出てくるので、コンソールを表示するためのタブを見つけてそれをクリックする。

  4. コンソールが出てくるはずなので、そこで $x() とタイプして、評価したい XPath 式を丸括弧内にタイプする。

あとは JavaScript で処理する。

Scrapy シェル

HTML ファイルまたはそのコードが手許にあるときには、それを対象として Scrapy シェルを起動する。以下、対象となる HTML ファイルが target.html という名前でカレントディレクトリーにある場合の例を示す。

bash$ scrapy shell ./target.html --nolog

それから次のようにして XPath 式を試すことができる。

[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7eff721870a0>
[s]   item       {}
[s]   request    <GET file:///path/to/target.html>
[s]   response   <200 file:///path/to/target.html>
[s]   settings   <scrapy.settings.Settings object at 0x7eff72184fa0>
[s]   spider     <DefaultSpider 'default' at 0x7eff71419070>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
In [1]: response.xpath('//h1')
Out[1]:
[<Selector xpath='//h1' data='<h1>内容</h1>'>,
 <Selector xpath='//h1' data='<h1>応答</h1>'>]

Note

Scrapy 利用ノート も参照。

基本仕様

XPath 1.0 の急所に絞って記す。

文法・構文

ファイルパスのように表現する

スラッシュ一つから始まる UNIX のファイルシステムの絶対パス表記のような XPath 式は、ドキュメントルートからのパスを表現する。

/AAA
/AAA/CCC
/AAA/DDD/BBB

以下、このような AAA, BBB などが何であるのかを敢えて説明しないが、例えば要素ノードの名前などは当てはまると記しておく。

文頭のダブルスラッシュ // は「文書内のすべての~」

スラッシュ二つ // から始まる XPath 式は、後続する条件を満たし、かつドキュメント内にある要素全てを表す。

//BBB
//DDD/BBB
  • 例えば最初の式は「ドキュメント内にある BBB すべて」を表す。

  • 次の式では「すべて」が DDD のほうにかかることに注意する。

星印 * は全ての要素にマッチする

星印 * はあらゆる要素にマッチする。

/AAA/CCC/DDD/*
/*/*/*/BBB
//*
  • 二番目の式はルートから数えて 4 階層目にある要素 BBB すべてを選択する。

  • 最後の式は「文書中にあるすべての要素」を表現するふつうの XPath 式だ。

角括弧 [] は条件を与えて絞り込む

角括弧 [] を使って要素をさらに限定するための何かを指定する。

番号を指定すると、選択集合にある要素の位置を指定することになる。関数 last() を指定すると、選択集合の最後の要素を指定することになる。

/AAA/BBB[1]
/AAA/BBB[last()]

記号 @ で属性を表す

属性を指定するには @ 接頭辞を用いることができる。

//BBB[@id]
//@id
//BBB[@name]
//BBB[@*]
//BBB[not(@*)]

今までは XML の要素を選択する例だったが、この例では属性を選択することに注意。 XML では要素も属性もノードと呼ばれる抽象概念の特殊概念として捉えること。

  • 三番目の式と最後の式はそれぞれ「(何でもいいから)属性を有する要素」「属性を(何も)有しない要素」を表す。

属性値

属性の値を選択の判定方法として用いることができる。

//BBB[@id='b1']
//BBB[@name='bbb']
//BBB[normalize-space(@name)='bbb']
  • 最初の XPath 式は「属性 id の値が b1 であるような要素 BBB すべて」を表現する。

  • 関数 normalize-space() は文字列の前後から空白を除去し、なおかつ連続する空白文字の列を単一の空白文字に置き換える。ここでは @name に、つまり属性 name の値に作用する。

XML 文書は単一ノードを根とする木構造であるから、特定のノードを基点として他のノードを指定するのに、両者の関係でそれを実現する仕組みがある。XPath の仕様はそれを axis と呼んでいる。日本語なら「軸」であるが、英語では祖先、子孫、親、子、兄弟、等々の関係を総称して軸と呼ぶのだろうか。

name

description

comment

child::

子ノード

既定の軸

descendant::

子孫ノード

attribute::

属性ノード

self::

自身

明示的に指定する場合があるのでこれがある

descendant-or-self::

子孫または自身

和集合

following-sibling::

弟ノード

便宜上そう呼ぶことにする

following::

後続ノード

自身の子孫を除外

namespace::

名前空間ノード

ancestor::

祖先ノード

preceding-sibling::

兄ノード

便宜上そう呼ぶことにする

preceding::

先行ノード

自身の祖先を除外

ancestor-or-self::

祖先または自身

和集合

child::

child:: はコンテキストノードの子すべてを含む。軸 child:: は既定の軸なので省略することができる。

/AAA
/child::AAA
/AAA/BBB
/child::AAA/child::BBB
/child::AAA/BBB

例の最初と二番目の XPath 式は同じであり、「ルートの子要素である AAA すべて」の意味となる。また、三番目と四番目と五番目が同じものを表現する。

どうやら軸の名称はすべて英語単数形らしいが、実際に表現されるものが複数あることは普通だ。

descendant::

descendant:: はコンテキストノードの子、あるいはさらにその子、等々、を含む。結果的に descendant:: は属性や名前空間を含むことはない。

/descendant::*
/AAA/BBB/descendant::*
//CCC/descendant::*
//CCC/descendant::DDD
  • 最初の式はルートの子孫要素すべてを、したがってすべての要素を意味する。

  • 二番目は /AAA/BBB の子孫要素のすべてを表す。書き忘れたがコンテキストノード自身は式の表現するものに含まれない。

  • 三番目は「文書内の要素 CCC それぞれに対する子孫ノードすべて」を表す。

  • 四番目は「文書内の要素 CCC それぞれに対する子孫ノードであるような要素 DDD すべて」を表す。 「すべて」が二度出ることに注意する。

attribute::

attribute:: はノードの属性を意味する。ふつうは略記法の @ を用いる。

self::

self:: はコンテキストノード自身を表す。

parent::

parent:: はコンテキストノードの親ノードを表す。親ノードは高々一つ存在する。

//DDD/parent::*

ancestor::

ancestor:: はコンテキストノードの親、あるいはその親、等々、を表す。コンテキストノードがルートでない限りは常にルートノードを含む。

/AAA/BBB/DDD/CCC/EEE/ancestor::*
//FFF/ancestor::*

最初の例では /, /AAA, /AAA/BBB, … などが得られることに注意。

following-sibling::

following-sibling:: はコンテキストノードの「後続(文書のより後方にある)の兄弟ノードすべて」を含む。便宜上、これを「弟」と呼ぶことにする。

/AAA/BBB/following-sibling::*
//CCC/following-sibling::*

preceding-sibling::

preceding-sibling:: はコンテキストノードの「先行(文書のより前方にある)兄弟ノードすべて」を含む。便宜上、これを「兄」と呼ぶことにする。

/AAA/XXX/preceding-sibling::*
//CCC/preceding-sibling::*

following::

following:: はコンテキストノードの「文書内にあるより後方のノードすべて」を含む。ただしコンテキストノード自身の子孫ノード、属性ノード、名前空間ノードを含まない。

/AAA/XXX/following::*
//ZZZ/following::*

namespace::

namespace:: はコンテキストノードの名前空間を表す。これは使わない。

preceding::

preceding:: はコンテキストノードの「文書内にあるより前方のノードすべて」を含む。ただしコンテキストノード自身の祖先ノード、属性ノード、名前空間ノードを含まない。

/AAA/XXX/preceding::*
//GGG/preceding::*

descendant-or-self::

descendant-or-self:: は軸 descendant:: およびコンテキストノード自身を含む。

/AAA/XXX/descendant-or-self::*
//CCC/descendant-or-self::*

ancestor-or-self::

ancestor-or-self:: は軸 ancestor:: およびコンテキストノード自身を含む。それゆえ常にルートノードを含む。

/AAA/XXX/DDD/EEE/ancestor-or-self::*
//GGG/ancestor-or-self::*

文書中の要素ノードを類別する

ancestor::, descendant::, following::, preceding::, self:: は属性と名前空間を無視すれば文書中のノード全体を類別する。この事実は XPath の設計の基本のはずなので、よく憶えておくことだ。

//GGG/ancestor::*
//GGG/descendant::*
//GGG/following::*
//GGG/preceding::*
//GGG/self::*
//GGG/ancestor::* | //GGG/descendant::* | //GGG/following::* | //GGG/preceding::* | //GGG/self::*

関数

関数一覧を表にしておく。引数リストは省略。

name

description

comment

boolean()

XPath 式を true または false に評価する

明示的に真偽値に変換する必要があるときに使う

ceiling()

C/C++ の ceil() と同様

使いそうにない

concat()

複数の文字列を cat する

任意の個数の文字列を与えて構わない

contains()

第一引数の文字列が第二引数の文字列を部分として含むか

頻出

count()

ノード集合の個数を返す

頻出

false()

UNIX の false と同様

リテラルの false が存在しないのでこれがある

floor()

C/C++ のそれと同様

使いそうにない

id()

与えられた何かにマッチするノードを検索して、それを含むノードを返す

微妙にわかりにくい

lang()

last()

式評価コンテキストから決まるサイズの値を返す

頻出

local-name()

ノードセットの最初のノードのローカル名を返す

ローカル名とは何か

name()

ノードセットの最初のノードの完全修飾名を返す

頻出

namespace-uri()

ノードセットの最初のノードの名前空間 URI を返す

http://www.w3.org/1999/xhtml のような文字列

normalize-space()

空白文字を適宜トリムする

前回のノート参照

not()

与えられた真偽値の否定値を返す

頻出

number()

引数を数値に変換して返す

ここで言う変換とは?

position()

式評価コンテキストから決まる位置を返す

配列の添字であると考える

round()

C/C++ のそれと同様

starts-with()

第一引数の文字列が第二引数の文字列から始まるか

頻出

string()

文字列に変換する

頻出

string-length()

文字列を構成する文字の個数を返す

引数なしのときは文書全体の文字数を返すようだ

substring()

部分文字列を返す

start/length 方式

substring-after()

第一引数文字列のうち、第二引数文字列以降の部分文字列を返す

返り値は第二引数文字列を含まない

substring-before()

第一引数文字列のうち、第二引数文字列以前の部分文字列を返す

返り値は第二引数文字列を含まない

sum()

各ノードの数値の和を返す

詳細不明

translate()

Python の str.translate() と同様

true()

UNIX の true と同様

リテラルの true が存在しないのでこれがある

ちなみにこの図表のインデックス列はブラウザーの開発ツールのコンソールで XPath から生成、ソートした:

for(let i of $x('//a[starts-with(@name, "function-")]/b[2]/text()')){
     console.log(i);
}

以下、有用な関数に絞って記す。

関数 count()

関数 count() は選択要素の個数を返す。

//*[count(BBB)=2]
//*[count(*)=2]
//*[count(*)=3]
  • 最初の式は「子要素 BBB がちょうど 2 個あるような要素すべて」か。

  • 残りの式も * が二回ずつでてきて意味をつかめない。

関数 name(), contains(), starts-with()

//*[name()='BBB']
//*[starts-with(name(),'B')]
//*[contains(name(),'C')]
  • 関数 name() は要素の名前を返す。

  • 関数 starts-with() は引数を二つとる。最初の引数の文字列が次の引数の文字列で始まるような文字列であれば真を返す。

  • 関数 contains() は最初の引数の文字列が次の引数の文字列を部分に含むならば真を返す。

  • 角括弧は Boolean を受けるという性質があることを意識すること。

  • 最初の式はもっと自然な表現がある。

関数 string-length()

関数 string-length() は文字列を構成する文字の個数を返す。

//*[string-length(name()) = 3]
//*[string-length(name()) < 3]
//*[string-length(name()) > 3]

演算子

以下の表に挙げる演算子は基本的には二項演算子だ。演算子の記号の両辺にオペランドをとると解釈する。演算子 // だけは左辺に何も置かないで記述することができる。

operator

description

comment

and

logical and

Python の and と同じ

or

logical or

Python の or と同じ

mod

remainder

JavaScript の % のそれと同じ(特に符号のあるオペランドに対して)

div

division

浮動小数点演算

*

multiplicaion

仕様書に説明がない?

/

path-compose

除算ではない

//

path-abbreviate

/descendant-or-self::node()/ を意味する記号

|

set-union

オペランドはノード集合

+

addition

C/C++ と同じ

-

subtraction

C/C++ と同じ

=

equality

C/C++ の == と同じ

!=

inequality

C/C++ と同じ

<

less than

C/C++ と同じ

<=

less than or equal

C/C++ と同じ

>

greater

C/C++ と同じ

>=

greater than or equal

C/C++ と同じ

  • 論理演算子は一般のプログラミング言語同様の短絡評価をする。

  • 一部の演算子はパス式の記号と混同されないように、前後に空白を入れるなどの配慮を必要とする。

演算子 |

パイプ記号 | を使って複数のパスを結合することができる。というよりは、複数の XPath 式の和集合を得ると考えられる。

//CCC | //BBB
/AAA/EEE | //BBB
/AAA/EEE | //DDD/CCC | /AAA | //BBB

この例だと最初のものは「文書内の要素 CCC すべてまたは文書内の要素 BBB すべて」を意味する。

論理演算

XPath 式は真偽値に対する二項演算の形式で論理演算をすることができる。演算の優先順位は次のリストに示す順番どおりになっている:

  • or

  • and

  • =, !=

  • <=, <, >=, >

算術演算

演算の優先順位は一般のプログラミング言語と同様のようだ。

//BBB[position() mod 2 = 0]
//BBB[position() = floor(last() div 2 + 0.5) or position() = ceiling(last() div 2 + 0.5)]
//CCC[position() = floor(last() div 2 + 0.5) or position() = ceiling(last() div 2 + 0.5)]

関連リンク

仕様書や参考になる文書のリンクをまとめる。