MAGAZINE

ルーターマガジン

Ruby

Nokogiriで容量が大きいxmlをparseしてメモリ溢れ(プロセスkilled)するときのNokogiri::XML::Reader

2019.08.14
Pocket

Nokogiri::XML::Readerは容量が大きいXMLファイルを扱うのに最適なクラスです。

Nokogiri::XML::Readerに至った経緯

Nokogiriで少しサイズの大きいXMLファイルをパースしたいという時に、HTMLと同じ要領で

Nokogiri::XML.parse(xml) 
を手元のMacで実行できたのを確認し、Nokogiriすごい!便利!と浮かれておりましたが、 AWSのubuntu環境で実行しようとすると
$ bundle exec ruby xml_test.rb 
Killed 
となり、rubyプロセスが強制終了してしまいました。 Nokogiriのparseメソッドは、XMLのDOM構造をオブジェクトとして展開する為、XMLファイルのサイズによっては膨大なメモリを要するようです。

そこで、これの代わりとなる軽量なXMLパーサーは何か良いものはないかなと探してみて、辿り着いたものがNokogiri::XML::Readerです。

基本的な使い方

require 'nokogiri'

xml_str = <<'EOS'
<campany>
<!-- 会社概要 -->
<name prefix="Inc.">rooter</name>
</campany>
EOS

reader = Nokogiri::XML::Reader(xml_str)
reader.each do |node|
  p node.name
end
実行結果は以下。
"campany"
"#text"
"#comment"
"#text"
"name"
"#text"
"name"
"#text"
"campany"
上のように、Nokogiri::XML::Readerに対して、ブロックを渡すことができます。 そのブロック変数は、要素1つを示すNokogiri::XML::Readerのインスタンスクラスです。 なので、このような使い方も出来ます。
p reader.first.name # "campany"

要素の種類を判別する

reader.each do |node|
  p node.name
  p node.node_type
end
実行結果は以下。
"campany"
1
"#text"
14
"#comment"
8
"#text"
14
"name"
1
"#text"
3
"name"
15
"#text"
14
"campany"
15
これらの数字は、以下の定数の値と対応しています。
  • 開始タグ
    p Nokogiri::XML::Reader::TYPE_ELEMENT == 1 #true
  • 終了タグ
    p Nokogiri::XML::Reader::TYPE_END_ELEMENT == 15 #true
  • コメント
    p Nokogiri::XML::Reader::TYPE_COMMENT == 8 #true
  • タグ間の空白文字以外の文字
    p Nokogiri::XML::Reader::TYPE_TEXT == 3 #true
  • タグ間の空白文字
    p Nokogiri::XML::Reader::TYPE_SIGNIFICANT_WHITESPACE == 14 #true

テキストフィールドの値を取得する

reader.each do |node|
  p node.name
  p node.value
end
実行結果は以下。
"campany"
nil
"#text"
"\n"
"#comment"
" 会社概要 "
"#text"
"\n"
"name"
nil
"#text"
"rooter"
"name"
nil
"#text"
"\n"
"campany"
nil
node_typeがTYPE_COMMENTやTYPE_TEXTではないものに対してはnilが返される。

属性を取得する

reader.each do |node|
  p node.name
  p node.attributes
end
実行結果は以下。
"campany"
{}
"#text"
{}
"#comment"
{}
"#text"
{}
"name"
{"prefix"=>"Inc."}
"#text"
{}
"name"
{"prefix"=>"Inc."}
"#text"
{}
"campany"
{}
属性が存在しない要素に対しては、空のハッシュが返される。

終わりに

如何でしたでしょうか。Nokogiri::XML::Readerは、Nokogiri::XML::Documentのようにオブジェクトに要素を展開せず、IOクラスにように順に読んで行くだけなので、より高速に、より軽量に処理することが出来ます。
通常は、Nokogiri::XML::Documentの方が、ノード単位で扱えて便利ですが、
メモリが足りなくてDocumentクラスが取得出来ないぞ!という時に、どうぞお役立てください。
Pocket

CONTACT

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