lambda consulting

のこぎりのつかいかた

December 13, 2015

Nokogiri

日本語も上手で有名なtenderlove(たこ焼き仮面はやめたのか?)がつくった Nokogiri 。いろいろなものが、この Nokogiri に依存しているが、自分にとっては、bundle install すると止まる奴という印象が強い。

DOM の構造を解析して、欲しい部分を抜き取るためには、なくてはならない存在だが、こののこぎりこつが必要。

使い方の流れを、かなり大雑把に言うと、

「抜いた範囲内で、さらに抜きたい」というのにはまった。(方法は後ほど)

HTMLのパース

require 'nokogiri'
require 'open-uri'

# 既にhtmlを取得していて、それをパースさせてもいいし、
html = open("http://www.lambda-consulting.jp/").read
doc = Nokogiri::HTML.parse(html)

# URLで取得されるものを直接パースさせてもいい
doc = Nokogiri::HTML(open("http://www.lambda-consulting.jp/"))

docが、パースされたオブジェクトになる。

欲しいものを抜く

欲しいものを特定する方法は2つあり、さらに、見つかった結果を「すべて取得する」か、「最初のひとつだけ」にするかでメソッドがわかれる。

欲しいものを特定する方法 その1 XPath

doc.xpath('//h2')  # => 全てのh2タグのNodeSetオブジェクト
doc.at_xpath('//h2')  # => 最初に見つかったh2タグのNodeオブジェクト

欲しいものを特定する方法 その2 CSS

こちらは、jQuery とかに近い。なので、こちらの方が使いやすいと思いきや、なぜか自分では、XPathばかり使っている。でも、それは、自分のCSSのクラス設計がいまいちだからだろう。

doc.css('.hoge')  # => 全てのhogeクラスのNodeSetオブジェクト
doc.at_css('.hoge')  # => 最初に見つかったhogeクラスのNodeオブジェクト

欲しいものを特定する方法 その3 XPath or CSS

XPathでもCSSでもどっちでもいいよというのもある。

抜いたものを出す

出し方によって、次の3つにわかれる。(言葉よりも、例を見たほうがわかりやすい)

doc = Nokogiri::HTML.parse("<h2 class='hoge'>ABCD<span>efg</span>HIJ</h2><h2 class='hoge'>1234<span>567</span>890</h2>")

doc.to_html
=> "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"
\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n
<h2 class=\"hoge\">ABCD<span>efg</span>HIJ</h2>\n
<h2 class=\"hoge\">1234<span>567</span>890</h2>\n
</body></html>\n"


# NodeSetオブジェクトの場合
doc.xpath('//h2').to_html
=> "<h2 class=\"hoge\">ABCD<span>efg</span>HIJ</h2>\n<h2 class=\"hoge\">1234<span>567</span>890</h2>"
doc.xpath('//h2').to_s
=> "<h2 class=\"hoge\">ABCD<span>efg</span>HIJ</h2>\n<h2 class=\"hoge\">1234<span>567</span>890</h2>"

doc.xpath('//h2').inner_html
=> "ABCD<span>efg</span>HIJ1234<span>567</span>890"

doc.xpath('//h2').inner_text
=> "ABCDefgHIJ1234567890"


# Nodeオブジェクトの場合
doc.at_xpath('//h2').to_html
=> "<h2 class=\"hoge\">ABCD<span>efg</span>HIJ</h2>\n"
doc.at_xpath('//h2').to_s
=> "<h2 class=\"hoge\">ABCD<span>efg</span>HIJ</h2>\n"

doc.at_xpath('//h2').inner_html
=> "ABCD<span>efg</span>HIJ"

doc.at_xpath('//h2').inner_text
=> "ABCDefgHIJ"
doc.at_xpath('//h2').content
=> "ABCDefgHIJ"
doc.at_xpath('//h2').to_str
=> "ABCDefgHIJ"

linkタグに含まれるhrefのような属性値を取得したい場合

doc = Nokogiri::HTML.parse("<h2 class='hoge'>ABCD<span>efg</span>HIJ</h2><h2 class='hoge'>1234<span>567</span>890</h2>")

doc.at_xpath('//h2').get_attribute('class')
=> "hoge"
doc.at_xpath('//h2')[:class]
=> "hoge"

[重要] 抜いた範囲内で、さらに抜きたい

Nokogiriというより、XPathを理解していなかったので、はまってしまった。。

そもそもXPathでは、

という意味になる。よって:

doc = Nokogiri::HTML.parse("<h2 class='hoge'>ABCD<span>efg</span>HIJ</h2><h2 class='hoge'>1234<span>567</span>890</h2>")

# 2つ目のh2要素を抜く
second_h2 = doc.xpath(//h2[2])
second_h2.to_html
=> <h2 class='hoge'>1234<span>567</span>890</h2>

# 2つ目のh2要素内のspanのつもりが、、、(xpath //は、ドキュメント全体の意味になる)
second_h2.at_xpath('//span').to_html
=> "<span>efg</span>"

# 「このコンテンツ内で」としたければ、xpathを./としなければならない
second_h2.at_xpath('./span').to_html
=> "<span>567</span>"