MAGAZINE

ルーターマガジン

クローリング/スクレイピング

脱CSSセレクタ!RubyとXPathでスクレイピング

2018.10.28
Pocket

こんにちは、アルバイトエンジニアのhayashiです。 今回はスクレイピングするのに便利なXPathを簡単に紹介したいと思います。

XPathって?

XPathの概要です。
XML Path Language (XPath(エックスパス)) は、マークアップ言語XMLに準拠した文書の特定の部分を指定する言語構文である。 (参照:wikipedia)
XMLとありますが、HTMLにも使うことができるのがXPathです。 htmlでマークアップされたwebサイトをスクレイピングする際にはとても便利なツールです。 今回はそんなXPathの扱い方を簡単に紹介します。

RubyでXPathを使う

弊社は主にRubyでクローラー/スクレイパーを書きます。 rubyでXPathを使うと少し便利なところがあるので後に書きます。 具体性が無いですが、例えばこういうhtmlで書かれたサイトがあったとします。
<html>
<title>sample</title>
<div class="tag">
  <li class="sp">テキスト1</li>
  <li>テキスト2</li>
  <li>テキスト3</li>
</div>
<div class="tag">
  <li class="sp">テキスト4</li>
  <li class="sp">テキスト5</li>
  <li>テキスト6</li>
</div>
</html>
これに対してこのようなrubyスクリプトを書いてみます。
require 'nokogiri'

html = File.read('sample.html')
doc = Nokogiri.XML(html)

# //divですべてのdiv要素を取得できます
node_set = doc.xpath('//div')

# 指定したタグを一つのノードとして構成されたNodeSetというクラスです
p node_set.class
# =>Nokogiri::XML::NodeSet

# これはrubyの配列のように扱うことができます!
p node_set.size
# =>2
# 今回divタグが2つあるので要素の数は2です
rubyではeachメソッドを使うことが多いのでnodeを配列のように扱えるのは便利です。 実際にdivタグごとにspクラスのliタグのテキストを取得してみようと思います。
node_set = doc.xpath('//div')
text_set = []
node_set.each do |node|
  array = []

  #クラスはこのように指定します
  array << node.xpath('.//li[@class="sp"]').text
  text_set << array
end

#結果はこうなります
p text_set
# => [["テキスト1"], ["テキスト4テキスト5"]]
些細なことですが注意点があります。 このようなコードだとどうなるでしょうか。
node_set = doc.xpath('//div')
text_set = []
node_set.each do |node|
  array = []
  array << node.xpath('//li[@class="sp"]').text
  text_set << array
end
p text_set
# => [["テキスト1テキスト4テキスト5"], ["テキスト1テキスト4テキスト5"]]
わかっている方も多いと思いますが、違いは「.」があるかないかだけです。 node_setは配列のように扱えますが、要素ごとにnodeが独立するわけではないのです。 eachしていても同じで、「//」と指定してしまうと大元のrootから根こそぎ検索してしまいます。 XPathを使っていて期待と違う結果になっていたらこんなことを疑ってみてください。 自分はこれで無駄な時間を使ってしまいました、、、

さいごに

XPathのほんの初歩の初歩を残念な実体験も披露しつつ紹介してみました。 XPathは便利な機能がまだまだあるので使ってみてください。 rooterではクローリング/スクレイピングサービスを提供しています。 ご用命の際はぜひご相談ください。
Pocket

CONTACT

お問い合わせ・ご依頼はこちらから