それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Xpathまとめコマンドとその使用例・チートシートでカンタンに実験する

はじめに。

xpath 便利です。

Xpathの書式を学ぶにはトライ・アンド・エラーが1番です。Xpathをもっと使うのためには、手軽なツールが必要です。

libxml のxpathコマンド

手軽なツールはlibxmlについてくるxmlintです。xmllint についてはココに書きました→xmlを扱う xmllint コマンドで xpath - それマグで!

しかし、xmlint では日本語の取扱が残念なので、xpathコマンドをnokogiriで再発明しました。

足りないので、自分で作った。

Xpathの便利さをもっと気づいて欲しい。なので手軽にXpathを試して練習したい。手軽に試したら、Xpathできたら楽しいよね。

学習用に、Xpathコマンドを作りました。

で、お手軽にXpathを作れるような方法を考えた結果、Nokogiriを利用したコマンドを作ることにした。

xpath · GitHub

ダウンロード
git clone git@gist.github.com:894c5aeabc620344bcea.git
cd 894c5aeabc620344bcea
cp xpath /usr/local/bin/
chmod +x /usr/local/bin/xpath

起動には、ruby のnokogiriが必要です。

sudo gem install nokogiri
OSX 標準のxpath/xlimt 関数は日本語の扱いが残念

xpathOS Xには標準添付です。しかし、日本語が残念です。

わざわざ作ったのは、OSXxpathは日本語の扱いが残念だったのでした。/usr/bin/xpathPerlで書かれているので、Perlを編集したらいいんだろうけど、まぁOSX標準添付を編集するのも依存増えるからいや。なので、じぶんでつくることにした。

ruby + libxml なら他への移植も簡単だし。

Xpath のサンプル

実験環境が整ったら、Xpathについて見ていきたいと思います。

Xpath は次の書き方を覚えておけば戦える。

//*         All nodes.
(//*)[1]    First of all node.
(//a[1])    A Node list of "First child" in a parent node.
//a//span   A span node ancestor of a.
//a/@href   An  attribute  named href in all a node.
//a[@href="/index.html"] A node with href  attribute is  "index.html".
//a[contains(@href,"index.html")] A node contains  "index.html" in its href.
//title | //meta  Node list of <title> or <meta> nodes.(Join result)
//img[ contains(@src,'jpg') or contains(@src,'png')]  Nodes with attributes has some keyword.
//form[ //input[name="username"] ]    A node which has a node of pointed by xpath.
//div[@id=main]//form   A node ancester of node.
//div/*   A node list of children of div.
//div//*  A node list of ancesstor of div.
//table//td[2] A td node second child of parent which in a table node.

xpath チートシート

xpath 内容
//* 全てのノード
//a 全ての<a> ノード
(//a)[1] 全ての<a> ノードを取得して、最初の1個
(//a)[2] 全ての<a> ノードを取得して、2番め(配列アクセス)
(//a[1]) 親ノード中の最初の1個の<a>をすべて
//a/span span ノードで、親が<a>のものをすべて
//a/@href aノードのすべてのhref属性
//a/text() aノードのすべてのtext()表現
//a[@href="/index.html"] aノードのうち href属性が"/index.html"と合致するモノすべて
//a[contains(@href,"index.html")] aノードのうち href属性に index.html を含むものすべて
//title | //meta title と meta タグを両方
//img/@alt | //img/@alt img@alt と img@src を複数同時に
//img[ contains(@src,'jpg') or contains(@src,'png')] img ノードで src に jpg/pngを含むものすべて
//div[ contains(@class,'link') and contains(@class,'book')] div.link.bookに相当するもの
//form[ ./input[name="username"] ] 子ノードに //input[name="username"]を持つform ノード
//div[@id=main]//form form ノードで、親が div[@id=main] の物をすべて
//div/* div の子要素すべて
//div//* div の子孫要素をすべて
//table//td[2] table タグで2番めのtdのもの(2列目をすべて)
//*[@id] id属性があるものをすべて
id("tid_123") id属性がtid_123のもの(id="tid_123")
id("tid123")/following-sibling::div #tid123に隣接する div
//tbody//tr[ position() > 2 ] tbody中のtrで3番目以降もの(Range:範囲指定)
//li[.="次へ"] textContent が"次へ" に完全一致(equal) するもの
//li[not(@data-aria)] not で否定

これだけあれば、8割の場面で戦えると思う。

xpath のコマンド

たとえば、このブログにどれくらいA要素が含まれるか

xpath http://takuya-1st.hatenablog.jp/  "count((//a))"
137.0
--------------------
 xpath: count((//a))
result: Float

たとえば、このブログのmeta要素を取り出すと・・・

xpath http://takuya-1st.hatenablog.jp/  "/html/head/meta"
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=7; IE=9; IE=10; IE=11">
<meta itemprop="name" content="それマグで!">
(中略)
<meta name="google-site-verification" content="h5FlKu0qSTuoG6mttmTa0x1d0Nq9-Z2XmBMnkYI8hsU">
--------------------
 xpath: /html/head/meta
result: 19 found

たとえば、curl の結果などと組合せるには

curl -s http://www.drk7.jp/weather/ | ./xpath "//a[text()='大阪府']"
<a href="/weather/xml/27.xml">大阪府</a>
<a href="/weather/json/27.js">大阪府</a>
<a href="/weather/xml/pollen/27.xml">大阪府</a>
<a href="/weather/json/pollen/27.js">大阪府</a>
--------------------
 xpath: //a[text()='大阪府']
result: 4 found

この実験したソースはコレ。

gist.github.com

では残りの二割は?

  • 子孫ノード、祖先ノード
  • 兄弟ノードの選択
  • 文字列関数
  • ノード関数

などの各種セレクタと関数とを組み合わせると、更に戦える。

祖先ノード

ノードをツリーのルート方向へ上位へ辿って探していく

たとえばfooterを含むセクションを探すには

//footer/ancestor::section
兄ノードを辿る

たとえば、アクティブなli#active のひとつ前のli

<li> ←ここ
<li id="active">
<li>

これを選ぶには

//li[@id='active']/preceding::li

便利ね.ただし liタグが複数アレば全部取れちゃうので絞ること

弟ノードを取り出す。

選択しているノードの1つ後ろを取り出すには

<li>
<li>
<li>
<li id="active">
<li>←ここを取り出す。

これを選ぶには

//li[@id='active']/following::li

便利ね.ただし後ろに liタグが複数アレば全部取れちゃうので絞ること

自分上位の「祖先」を辿る

//input[name='user']//ancestor::form

兄弟ノードで後続3つを選ぶ

条件に div[position()<4] マッチしたもの。を取り出す。「範囲指定」で配列から取り出す。

div[@class]/following-sibling::div[position()<4]

テーブルの指定カラムを取り出す。

cat out | xpath '//tbody/tr/td[1]/text() | //tbody/tr/td[2]/text() |  //tbody/tr/td[3]/text() '

このようにすれば、並び順を維持したまま、テーブルの中身を取り出せる。

Xpathだってデータベース

XPathはとても便利で、HTML・XMLを扱うのには必須のツール。

  • テキストファイルを検索するのにgrep
  • テーブルを検索するのにSQL
  • JSONを検索するのに jq
  • XMLを検索するのには Xpath

Xpathはクエリ、XMLはテーブルといえる。XMLだって立派なデータベースになると思います。

xpath 扱えるとyamlよりずっと便利ですよ。yaml ならJSONのほうが楽だし。

関連資料

XPATHについて。XMLのXpathで条件にマッチしたノードを取り出す。 - それマグで!

document.evaluate でXPATH する具体的サンプル - それマグで!

Xpathで「条件◯◯」を子孫ノードに持つ要素を選択する - それマグで!

文字を含むタグを取り出すxpath - それマグで!

2015-08-03 重ねがけ出来るようにした

ページ階層をたどってURLを抽出するときに不便だから、重ねがけ出来るようにした。

xpath http://www.yahoo.co.jp "//a/@href" | xpath "//a/@href"|xpath "//a/@href"

ただし、絶対URLしか対応できない。。。。

2015-09-02 追記

xpath 表現でid() を知ったのでサンプルに追記

2015-12-02 追記

Google Sites: Sign-inのサイトに再見できたので、追記。

2018-02-17 追記

サンプルを拡充

2022-05-10

textContent へのequal(= 完全一致)のサンプル '//li[.="次へ"]/span' を追加

2023-05-23

記述修正。