Nokogiriについて学ぶ

はじめに

webスクレイピングについて調べたところ、「nokogiri」というgemの情報が出てきた。

そこで、nokogiriに実際に触って理解を深めた。

できること

公式:https://nokogiri.org/

公式のhow toの冒頭に「Nokogiri is a large library, and so it's challenging to briefly summarize it. We've tried to provide long, real-world examples at Tutorials .」とある。

翻訳すると、「Nokogiriは大きなライブラリなので、簡単にまとめるのは難しいです。チュートリアルで長い実例を提供するようにしました。」

どうやらたくさんの機能を備えたライブラリのようだ。

公式の一例

#! /usr/bin/env ruby

require 'nokogiri'
require 'open-uri'

# HTMLを取得し、パースする
doc = Nokogiri::HTML(URI.open('<https://nokogiri.org/tutorials/installing_nokogiri.html>'))

# CSSでノードを検索する
doc.css('nav ul.menu li a', 'article h2').each do |link|
  puts link.content
end

# xpathでノードを検索する
doc.xpath('//nav//ul//li/a', '//article//h2').each do |link|
  puts link.content
end

# 混ぜて使用する
doc.search('nav ul.menu li a', '//article//h2').each do |link|
  puts link.content
end

上の例を手元のirbで試してみる。

HTMLを取得し、パースする

irb(main):004:0> doc = Nokogiri::HTML(URI.open('<https://nokogiri.org/tutorials/installing_nokogiri.html>'))
=> #<Nokogiri::HTML4::Document:0x148d4 name="document" children=[#<Nokogiri::XML::DTD:0x2d0 name="html">, #<Nokogiri::XML::Element:0x148c0 name="html" attributes=[#<Nokogir...
irb(main):005:0>

なんだかよくわからないオブジェクトが返ってきている。

まずこれを知るためにNokogiri::HTMLURI#openについて調べる。

open-uri公式:https://docs.ruby-lang.org/ja/latest/library/open=2duri.html

Nokogiri公式APIドキュメント:https://nokogiri.org/rdoc/index.html

  • URI#open

    • 公式によると、openの引数にhttp:// や https://、ftp:// で始まっている文字列を渡すと、 URI のリソースを取得した上で StringIO オブジェクトまたは Tempfile オブジェクトとして返すらしい。
    irb(main):005:0> doc = URI.open('<https://nokogiri.org/tutorials/installing_nokogiri.html>')
    => #<Tempfile:/var/folders/lr/fmfvf78j5dz5typy3qlwnp6r0000gn/T/open-uri20221101-44816-fouxid>
    
    • たしかにTempfileオブジェクトとして返ってきている。Tempfileオブジェクトについて調べてみると、「Tempfile オブジェクトは**FileクラスへのDelegatorとして定義されており、File**クラスのオブジェクトと同じように使うことができます。」とある。
    • ためしにIO#readを使って中身を文字列で確認してみると
    irb(main):009:0> doc.read
    => "\\n<!doctype html>\\n<html lang=\\"en\\" class=\\"no-js\\">\\n  <head>\\n    \\n      <meta charset=\\"utf-8\\">\\n      <meta name=\\"viewport\\" content=\\"width=device-width,initial-scale=1\\">\\n      \\n        <meta name=\\"description\\" content=\\"The Official Tutorial Archive™ of Nokogiri®\\">\\n      \\n      \\n        <meta name=\\"author\\" content=\\"Mike Dalessio\\">\\n      \\n      \\n        <link rel=\\"canonical\\" href=\\"<https://nokogiri.org/tutorials/installing_nokogiri.html\\>">\\n
    
    長いので以下略
    
    • となっていて、htmlリソースが入っていることがわかる。
  • Nokogiri::HTML

    • HTMLとしてパースし、Nokogiri::HTML4::Documentオブジェクトを返す。(ちなみにNokogiri::HTML.parseとしても同じ結果になる。)
    • Nokogiri::HTML4::Documentオブジェクトに対して様々なメソッドを使うことで、目的のノードを取得したり、ドキュメントのtitleを確認したりできる。
    irb(main):009:0> doc = Nokogiri::HTML(URI.open('<https://nokogiri.org/tutorials/installing_nokogiri.html>'))
    => #<Nokogiri::HTML4::Document:0x3d4dc name="document" children=[#<Nokogiri::XML::DTD:0x28eec name="html">, #<Nokogiri::XML::Element:0x3d4c8 name="html" attributes=[#<Nokog...
    irb(main):010:0> puts doc
    <!DOCTYPE html>
    <html lang="en" class="no-js">
      <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width,initial-scale=1">
          
            <meta name="description" content="The Official Tutorial Archive™ of Nokogiri®">
          
          
            <meta name="author" content="Mike Dalessio">
          
          
            <link rel="canonical" href="<https://nokogiri.org/tutorials/installing_nokogiri.html>">
    
    • Nokogiri::HTML4::Documentオブジェクトにputsを使うと、こんな感じでちゃんとHTMLとしてパースされてるのがわかる。

ノードを検索する

doc = Nokogiri::HTML(URI.open('<https://utsumate.net>'))
css = doc.css('#wrapper__start > main > div > p.mb-6.top-page-content-line-height')
css.text

=> "このアプリは、そんな思いに応えるために作られました"
  • とれた。

上記のやりかたでは情報を取得できないサイトも

ここまでで学んだやりかたで、ためしに発売スケジュール|任天堂の発売予定のゲームタイトルの情報をとってみようとすると…。なぜかとることができない。

doc = Nokogiri::HTML(URI.open('<https://www.nintendo.co.jp/schedule/index.html>'))
puts doc

として中身を確認するとわかるが、ゲームに関する情報が丸々抜けている。

なぜこんなことが起こるかというと、サイトによっては、javascriptによってindex.htmlとは別のhtmlを取得するリクエストが投げられていたり、コンテンツの一部がJSONから取得されていたりするからだ。

参考:RubyでWebスクレイピング \#5 javascript対応テクニック

こういう場合はこれまでと違ったアプローチを取らなければいけない。

次はそういったアプローチについて学んでいきたい。

感想

webスクレイピングで情報をとれるようになれば、作れるものの幅も広がるし、実生活でも役立つと思うので、引き続き学んでいきたい。