ブログトップ 記事一覧 ログイン 無料ブログ開設

それはそれ。これはこれ。 このページをアンテナに追加 RSSフィード

2003 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 03 | 04 | 05 | 06 | 07 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2013 | 01 | 02 | 04 | 05 | 07 | 08 | 10 | 11 | 12 |
2014 | 02 | 03 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 05 | 06 | 07 | 11 |
2016 | 07 |

2009-05-09(土)

[]スクレイピングのためのNokogiri利用メモ スクレイピングのためのNokogiri利用メモを含むブックマーク スクレイピングのためのNokogiri利用メモのブックマークコメント

スクレイピングチュートリアルを書いてみた。

参考:http://nokogiri.rubyforge.org/nokogiri/Nokogiri.html

まだまだたくさんのクラスメソッドがあるが(読んでない)、HTMLスクレイピングに限定すれば多分これくらいで十分。

2014-02-16追記

なんかたくさんブックマークされていることに気づいたので、サンプルコードRuby1.9/2対応アップデート

Mechanize周りも修正。WWW::Mechanize → Mechanize 等


(0) 前提知識

RubyHTMLDOMCSSセレクタまたはXPath


(1) クラス構造理解

Nokogiri::HTML::Document < Nokogiri::XML::Document < Nokogiri::XML::Node < Object

Nokogiri::XML::Element < Nokogiri::XML::Node < Object

Nogogiri::XML::NodeSet < Enumerable < Object


(2) HTMLドキュメントオブジェクトを得る

まずは最初に、解析したいページの Nokogiri::HTML::Document オブジェクトを得る。

Nokogiri::HTML::Document は、Nokogiri::XML::Document のさらに、Nokogiri::XML::Node のサブクラスなので、

Node のメソッドが使える。

ログイン処理など、Cookieを使ったページ遷移が必要場合
Mechanize を利用して、page = agent.get(url) で Mechanize::Page オブジェクトが返り、page.parser またはその alias の page.root で Nokogiri::HTML::Document オブジェクトが返る。ページの文字コードutf-8 以外だとうまく処理できないケースもあるので、id:otn:20090429 参照
単純なページ取得の場合
open-uriを使って、doc = Nokogiri.HTML(open(url)[,url,[jcode] ]) で Nokogiri::HTML::Document オブジェクトが返る。HTMLメソッドの第三引数でページの文字コードを指定する

(3) 基本的な処理パターン

Document 等の Nokogiri::XML::Node または Nokogiri::XML::NodeSet オブジェクトに対して、

CSSセレクタXPath検索を行い、検索結果として Nokogiri::XML::NodeSet オブジェクトを得る。

NodeSetはArrayのようなもので、eachや[]でNodeのサブクラスのElementを得て、情報を得る。

tds=doc.xpath("//td") # => tdタグの検索(NodeSetオブジェクト)
tds.size              # => tdタグの個数
tds[0]                # => 最初のtdタグ(Elementオブジェクト)
tds[0]["class"]       # => 最初のtdタグのclass名(String)
tds[0].xpath(".//a")  # => さらにその中のaタグを探す(NodeSetオブジェクト)

よく使うと思われるメソッドは、xpathまたはcss、NodeSetからElementを取り出す[]やeach、Elementの属性値を取る[]、テキストの取り出しtext、あとは近くのノードをたどるparentやchild・children・previous・nextくらいでしょうか。あ、NodeSetに対するempty?とかsizeも。


(4) Nodeの参照系メソッド

検索
at("検索")
XPathCSSセレクタ検索し、結果の最初のノード(Element)。無ければnil
css("CSSセレクタ")
引数は複数やArrayも可能。CSSセレクタ検索し、NodeSetを返す。無ければ空のNodeSet
xpath("XPath")
引数は複数やArrayも可能。XPath検索し、NodeSetを返す。無ければ空のNodeSet
search("検索")、/ "検索"
引数は複数やArrayも可能。XPathCSSセレクタ検索し、NodeSetを返す。無ければ空のNodeSet
○自ノード情報
node_name、name
ノード名(String)
css_path
このノードのCSSセレクタ(String)
path
ノードのXPath(String)
node_type、type
ノードタイプ(Fixnum)
blank?
白文字のみのテキストノードか?
cdata?
CDATAノードか?
text?
テキストノードか?
commnet?
コメントノードか?
element?、elem?
エレメントノードか?
属性情報

属性値(String)を返すものと、属性(Attr < Node)を返すものがある。

["属性名"]、get_attribute("属性名")
属性値(String)。無ければnil
key?("属性名")、has_attribute?("属性名")
属性があるか?
keys
属性名(String)の一覧(Array)
values
属性値(String)の一覧(Array)
attributes
属性名(String)と属性オブジェクト(Attr)のハッシュ(Hash)
attribute("属性名")、attribute_nodes
属性オブジェクト(Attr)やそのリスト(Array)
each { |k,v| 。。。}
属性名(String)と属性値(String)でブロック呼び出し
○子ノード情報
child
最初の子ノード(Element)
children
子ノード(Element)のリスト(Array)
content、text、inner_text、to_str
テキスト子孫ノードの内容をつなぎ合わせたもの(String)。IEFirefoxJavaScriptのinnerTEXT、textContent相当
inner_html
子孫ノードのHTMLをつなぎ合わせたもの(String)。IEFirefoxJavaScriptのinnerHTML相当
兄弟ノード情報
previous_sibling、previous
兄ノード(Element)
next_sibling、next
弟ノード(Element)
○親ノード情報
parent
親ノード(Element)
ancestors
親、祖父・・・ノード(Element)のリスト(Array)
document
そのノードを含むDocumentオブジェクトを得る。
テキスト
to_html、to_html("エンコード")
ノード全体をHTMLテキストに(String)。IEJavaScriptのouterHTML相当
to_xhtml、to_xhtml("エンコード")
ノード全体をXHTMLテキストに(String)
write_html_to(io,"エンコード")
ノード全体をHTMLテキスト(String)にして書き出す(io)

(5) NodeSetの参照系メソッド

検索
at("検索")
css("CSSセレクタ")
xpath("XPath")
search("検索")、/ "検索"
○Enumerator、Arrayもどき
length、size
[添え字]
empty?
first
last
each { |x| 。。。}
push(node)、<< node
to_a、to_ary
テキスト
inner_text、text
すべてのエレメントに適用してつなぎ合わせ
inner_html
すべてのエレメントに適用してつなぎ合わせ
to_html(*arg)
すべてのエレメントに適用してつなぎ合わせ
to_xhtml(*arg)
すべてのエレメントに適用してつなぎ合わせ

(6) サンプル:各地の今日の天気

XPashとCSSを混ぜてみた。

Windowsで動かしたので、出力はSJIS。-Kは念のためeuc-jpに。

#!/usr/bin/ruby -Ke
require "rubygems"
require "nokogiri"
require "open-uri"
require "kconv"

doc = Nokogiri.HTML(open("http://weather.asahi.com"))

doc.search("//table[@class='font12' and @bgcolor]//tr[position()>1]").each do |tr|
	place   = tr.search("td[1]").text
	weather = tr.search("td[2] > img").map{|img| img["alt"]}.join("|")
	puts "#{place}\t#{weather}".tosjis
end
2014-02-16追記

エンコードをRuby1.9or2らしく書くとこんな感じか。

#!/usr/bin/ruby
require "rubygems"
require "nokogiri"
require "open-uri"

Encoding.default_external = "Windows-31J"

doc = Nokogiri.HTML(open("http://weather.asahi.com","r:euc-jp"),nil,"euc-jp")

doc.search("//table[@class='font12' and @bgcolor]//tr[position()>1]").each do |tr|
	place   = tr.search("td[1]").text
	weather = tr.search("td[2] > img").map{|img| img["alt"]}.join("|")
	puts "#{place}\t#{weather}"
end

(7) サンプル:マイミク最新日記

Windowsで動かしたので、出力はSJIS。-Kは念のためutf-8に。

2014-02-16修正

ページ内容(HTML)が変わっていたので、現状の物に対応

#!/usr/bin/ruby -Ku
MAIL="foo@example.jp"
PASS="password"

require "rubygems"
require "mechanize"
require "kconv"

agent = Mechanize.new
agent.user_agent_alias = "Windows IE 9"

agent.get("http://mixi.jp")
agent.page.form("login_form").field("email").value=MAIL
agent.page.form("login_form").field("password").value=PASS
agent.page.form("login_form").submit

agent.get("http://mixi.jp/home.pl")
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
	puts sprintf("%s\t\%s\t%s", node.search("li.date")[0].text,
		node.search("p.name>a")[0].text, node.search("p.title")[0].text).tosjis
end
2014-02-16追記

エンコードをRuby1.9or2らしく書くとこんな感じか。

#!/usr/bin/ruby
MAIL="foo@example.jp"
PASS="password"

require "rubygems"
require "mechanize"

Encoding.default_external = "Windows-31J"

agent = Mechanize.new
agent.user_agent_alias = "Windows IE 9"

agent.get("http://mixi.jp")
agent.page.form("login_form").field("email").value=MAIL
agent.page.form("login_form").field("password").value=PASS
agent.page.form("login_form").submit

agent.get("http://mixi.jp/home.pl")
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
agent.page.root.search("ul.homeFeedList > li.diary").each do |node|
	printf "%s\t\%s\t%s\n", node.search("li.date")[0].text,
		node.search("p.name>a")[0].text,
		node.search("p.title")[0].text
end

(以下おまけ)

(8) Nodeの更新メソッド

○自ノード処理
node_name="名前"、name="名前"
ノード名の差し替え
dup
ノードのコピーを作る。dup(0)だと子ノードはコピーしない
unlink、remove
ノードの削除
replace(node)
ノードを置き換え
swap("テキスト")
ノードをテキストHTMLから作ったノードと置き換えて元のノードを返す(Element)
属性書き換え
["属性名"]="値"、set_attribute("属性名","値")
属性値をセット
delete("属性名")、remove_attribute("属性名")
属性の削除。属性値(String)を返す。無ければnil
○子ノードの処理
add_child(node)、<< node
nodeを子ノードとして追加(self)
content="テキスト"
子孫ノードすべてをテキストノードで置き換え
inner_html="文字列"
子孫ノードすべてを文字列HTMLタグとして置き換え
兄弟ノードの処理
add_previous_sibling(node)
nodeを兄ノードとして追加(self)
add_next_sibling(node)
nodeを弟ノードとして追加(self)
before("テキスト")
テキストを兄テキストノードとして追加(self)
after("テキスト")
テキストを弟テキストノードとして追加(self)
○親ノードの処理
parent=node
親ノードから切り離して別ノードの最後の子ノードに
○その他
encode_special_chars("文字列")
< > & " をエンコードする
fragment("文字列")
文字列HTMLタグとして DocumentFragmentオブジェクトを作る

(9) NodeSetの更新メソッド

add_class("クラス名")
すべてのエレメントにクラス追加
remove_class("クラス名")
すべてのエレメントからクラス削除
attr("属性名","属性値",&blk)、set("属性名","属性値",&blk)
すべてのエレメントに属性セット
remove_attr("属性名")
すべてのエレメントから属性削除
after("テキスト")
最後のエレメントに弟としてテキストノード追加
before("テキスト")
最初のエレメントに兄としてテキストノード追加
dup
ノードセットの複製
unlink、remove
すべてのエレメントをそれぞれの親から削除