明けましておめでとうございます、アルバイトエンジニアのhayashiです。

今回はRubyのオープンクラスについて簡単に触れていこうと思います。

オープンクラスとは?

オープンクラスとは、新規クラスや継承などを利用せずに、既存のクラスに対して自由に追加・編集できることを言います。

オープンクラスの仕組みを使うことで、既存クラスの機能を上書きすることをモンキーパッチと呼びます。

例を見たほうが話が早いので早速見ていこうと思います。

Rubyのオープンクラス

まずはRubyのStringクラスに新しいメソッドを追加してみます。
class String
  def  kinpaku
    self + "っ・・・!"
  end
end
puts "オープンクラス".kinpaku
#=> オープンクラスっ・・・!

文字列に新たな文字列を加えるメソッドを追加しました。これは新しくメソッドを追加した形です。

次は既存のメソッドを上書きするパターンです。

class Fixnum
  def +(num)
    self - num
  end
end
puts 10 + 9
#=> 1

Fixnumクラスの+メソッドを減算するメソッドに書き換えました。これは致命的な改変ですがRubyは許しています。

モンキーパッチをする際には問題ない編集か確認しながら細心の注意を払わなければいけません。

Rubyのnet/httpライブラリを上書きする

実例としてRubyのnet/httpで接続するときのレスポンスを待つread_timeoutを上書きしてみます。

クローリング会社としては、レスポンスのタイムアウト時間の調整は日常茶飯事なのです。

net/http.rbのNet::HTTPクラスのソースはこのようになっています。(一部抜粋)

#Ruby 2.5.0
def initialize(address, port = nil)
  @address = address
  @port    = (port || HTTP.default_port)
  @local_host = nil
  @local_port = nil
  @curr_http_version = HTTPVersion
  @keep_alive_timeout = 2
  @last_communicated = nil
  @close_on_empty_response = false
  @socket  = nil
  @started = false
  @open_timeout = 60
  @read_timeout = 60
  @continue_timeout = nil
  @max_retries = 1
  @debug_output = nil

  @proxy_from_env = false
  @proxy_uri      = nil
  @proxy_address  = nil
  @proxy_port     = nil
  @proxy_user     = nil
  @proxy_pass     = nil

  @use_ssl = false
  @ssl_context = nil
  @ssl_session = nil
  @sspi_enabled = false
  SSL_IVNAMES.each do |ivname|
    instance_variable_set ivname, nil
  end
end
---省略---
def read_timeout=(sec)
  @socket.read_timeout = sec if @socket
  @read_timeout = sec
end

デフォルトのread_timeoutが60秒に設定されているのがわかります。レスポンスを60秒以上待ち続けるとタイムアウトします。

そしてread_timeout=メソッドでread_timeoutを設定できるようになっています。

しかしスクリプト全体のデフォルトの待ち時間を変えたい場合や、net/httpをラップしたライブラリを使っているときはread_timeout=メソッドは使えません。

そこでデフォルトのread_timeoutを上書きするinitialize_newメソッドを作ります。

class Net::HTTP
  def initialize_new(address, port = nil)
    initialize(address, port)
    @read_timeout = 120  #上書き
  end
  alias_method :initialize :initialize_new
end

alias_methodはメソッドに別名をつけるメソッドです。alias_method [新しいメソッド名][元のメソッド名]と書きます。

この例では、initializeが呼び出されるとinitialize_newが呼び出されます。その中でinitializeが呼び出され、諸々の変数が設定された後に@read_timeoutを120に上書きしています。

initializeを直接書き換えても良いのですがのちのち混乱してしまうのでモンキーパッチした箇所はなるべく隔離しておくためにこのように書きました。

さいごに

クローリング会社としてスクレイピング・クローリングを続けていると様々なサイトを目にする機会があります。

リクエストしてから2分間応答がないサーバー、idやclassが何も振られていないhtmlなどなど…世の中にはユニークなサイトが沢山あります。

弊社ではそういったユニークなサイトでもスクレイピング・クローリングしてきた実績があります!

データ収集でお困りの際はぜひ弊社のデータアグリゲーションサービスにお問い合わせください。