はじめに。
xpath 便利です。
Xpathの書式を学ぶにはトライ・アンド・エラーが1番です。Xpathをもっと使うのためには、手軽なツールが必要です。
libxml のxpathコマンド
手軽なツールはlibxmlについてくるxmlintです。xmllint についてはココに書きました→xmlを扱う xmllint コマンドで xpath - それマグで!
しかし、xmlint では日本語の取扱が残念なので、xpathコマンドをnokogiriで再発明しました。
足りないので、自分で作った。
Xpathの便利さをもっと気づいて欲しい。なので手軽にXpathを試して練習したい。手軽に試したら、Xpathできたら楽しいよね。
学習用に、Xpathコマンドを作りました。
で、お手軽にXpathを作れるような方法を考えた結果、Nokogiriを利用したコマンドを作ることにした。
ダウンロード
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 関数は日本語の扱いが残念
xpathはOS Xには標準添付です。しかし、日本語が残念です。
わざわざ作ったのは、OSXのxpathは日本語の扱いが残念だったのでした。/usr/bin/xpathはPerlで書かれているので、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
この実験したソースはコレ。
では残りの二割は?
- 子孫ノード、祖先ノード
- 兄弟ノードの選択
- 文字列関数
- ノード関数
などの各種セレクタと関数とを組み合わせると、更に戦える。
祖先ノード
ノードをツリーのルート方向へ上位へ辿って探していく
たとえば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を扱うのには必須のツール。
Xpathはクエリ、XMLはテーブルといえる。XMLだって立派なデータベースになると思います。
xpath 扱えるとyamlよりずっと便利ですよ。yaml ならJSONのほうが楽だし。
関連資料
XPATHについて。XMLのXpathで条件にマッチしたノードを取り出す。 - それマグで!
document.evaluate で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
記述修正。