MAGAZINE

ルーターマガジン

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

RubyのNokogiriで子要素のみのテキストを取得し兄弟要素から指定する

2025.05.02
Pocket

Nokogiriのtextメソッドは子孫要素まで結合されて困る

RubyのNokogiriでHTMLをスクレイピングするとほぼ必ず使うメソッド「text」は、子孫要素全てのテキストを結合して取得する仕様となっています。そのため、HTMLの構造によってはCSSセレクタでは上手く要素を指定出来ない場合があります。以下はその例です。

require 'nokogiri'

html =<<-EOS
<html>
  <p>
    セール中<br>
    1200
    <span>円</span>
  </p>
</html>
EOS

doc = Nokogiri::HTML.parse(html)
p doc.at_css('p').text
# -> "\n    セール中\n    1200\n    円\n  "

「セール中」「1200」「1200円」などが欲しかった場合、いずれにせよpタグを指定せざるを得なく、textメソッドの返り値の文字列を加工する必要があります。

子要素のテキストを配列で取得する

そこで、以下のようにtextsメソッドを作成しました。 子孫要素ではなく子要素のテキストを取得しています。

require 'nokogiri'

class Nokogiri::XML::Node
  def texts() # 子要素の全てのtextを取り出す
    self.children.select{|n| n.name == 'text' }
  end
end

html =<<-EOS
<html>
  <p>
    セール中<br>
    1200
    <span>円</span>
  </p>
</html>
EOS

doc = Nokogiri::HTML.parse(html)
p doc.at_css('p').texts
#-> [#<Nokogiri::XML::Text:0x3c "\n    セール中">, #<Nokogiri::XML::Text:0x50 "\n    1200\n    ">, #<Nokogiri::XML::Text:0x64 "\n  ">]

各テキストを結合せずに配列にして返しているので、selectメソッドやfindメソッドなどで必要なテキストを選択することが可能になりました。

~の前のテキスト、~の後のテキストを取得する

また、StringにせずにNokogiri::XML::Textで返しているので、兄弟要素を指定して参照出来ます。例えば、「spanタグの前のテキスト」という指定をする場合は、以下ように指定出来ます。

price_text_node = doc.at_css('p').texts.find{|t| t.next_sibling&.name == 'span'}
p price_text_node
#-> #<Nokogiri::XML::Text:0x3c "\n    1200\n    ">
p price_text_node.text
#-> "\n    1200\n    "

おわりに

いかがでしたでしょうか。これで、textメソッドでは指定しにくい構造のテキストでも、自由に厳密な指定が可能になりましたね。

以下の記事では、「そもそも内部構造があるDOM に対する text はエラーとする」というバッチも紹介しているので、ぜひご参考ください。

Nokogiri でスクレイピングしていて レイアウトが変わると困る!

それでは良いスクレイピングライフを!

Pocket

CONTACT

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