1

Mechanize のキャッシング バージョンが欲しいです。#get(uri...) は、その uri が以前に取得されたかどうかを確認し、そうであれば、Web にアクセスするのではなく、キャッシュから応答を取得するというものです。キャッシュにない場合は、Web にヒットし、応答をキャッシュに保存します。

私の素朴なアプローチはうまくいきません。(おそらく、CachedWebPage が ActiveRecord::Base のサブクラスであることは言うまでもありません):

class CachingMechanize < Mechanize

  def get(uri, parameters = [], referer = nil, headers = {})
    page = if (record = CachedWebPage.find_by_uri(uri.to_s))
             record.contents
           else
             super.tap {|contents| CachedWebPage.create!(:uri => uri, :contents => contents)}
           end
    yield page if block_given?
    page
  end

end

これは、Mechanize#get() によって返されるオブジェクトが複雑な循環構造であり、YAML も JSON もデータベースに格納するためにシリアル化する必要がないため、失敗します。

Mechanize が解析する前に、低レベルのコンテンツをキャプチャする必要があることに気付きました。

  • これを行うためのきれいな方法はありますか?Mechanize の post_connect フックを使用して、入ってくる生のページにアクセスできると思いますが、キャッシュされた生のページを後で解析のために Mechanize に渡す方法がわかりません。
  • Web ページのキャッシュを既に行っている、使用すべきパッケージはありますか?
4

1 に答える 1

3

完全にクリーンではありませんが、解決策は単純であることがわかりました。次のように Mechanize#get() の結果をキャッシュするのは簡単なことです。

class CachingMechanize < Mechanize
  def get(uri, parameters = [], referer = nil, headers = {})
    WebCache.with_web_cache(uri.to_s) { super }
  end
end

... with_web_cache() は YAML を使用して、super から返されたオブジェクトをシリアル化し、キャッシュします。

私の問題は、デフォルトで Mechanize#get() が何らかのラムダ オブジェクトを含む Mechanize::Page オブジェクトを返し、YAML でダンプおよびロードできないことでした。修正は、これらのラムダを削除することでした。これはかなり単純であることが判明しました。完全なコードは次のとおりです。

class CachingMechanize < Mechanize

  def initialize(*args)
    super
    sanitize_scheme_handlers
  end

  def get(uri, parameters = [], referer = nil, headers = {})
    WebCache.with_web_cache(uri.to_s) { super }
  end

  # private

  def sanitize_scheme_handlers
    scheme_handlers['http']      = SchemeHandler.new
    scheme_handlers['https']     = scheme_handlers['http']
    scheme_handlers['relative']  = scheme_handlers['http']
    scheme_handlers['file']      = scheme_handlers['http']
  end

  class SchemeHandler
    def call(link, page) ; link ; end
  end

end

教訓: lambda または proc を含む YAML.dump および YAML.load オブジェクトを試みないでください

これは、この例だけではありません。次のような YAML エラーが表示された場合:

TypeError: allocator undefined for Proc

シリアル化および逆シリアル化しようとしているオブジェクトにラムダまたはプロシージャがあるかどうかを確認します。(この場合のように) ラムダをオブジェクトへのメソッド呼び出しに置き換えることができれば、問題を回避できるはずです。

これが他の誰かに役立つことを願っています。

アップデート

WebCache の定義に関する @Martin の要求に応じて、次のようになります。

# Simple model for caching pages fetched from the web.  Assumes
# a schema like this:
#
#   create_table "web_caches", :force => true do |t|
#     t.text     "key"
#     t.text     "value"
#     t.datetime "expires_at"
#     t.datetime "created_at", :null => false
#     t.datetime "updated_at", :null => false
#   end
#   add_index "web_caches", ["key"], :name => "index_web_caches_on_key", :unique => true
#
class WebCache < ActiveRecord::Base
  serialize :value

  # WebCache.with_web_cache(key) {
  #    ...body...
  # }
  #
  # Searches the web_caches table for an entry with a matching key.  If
  # found, and if the entry has not expired, the value for that entry is
  # returned.  If not found, or if the entry has expired, yield to the
  # body and cache the yielded value before returning it.
  #
  # Options:
  #   :expires_at sets the expiration date for this entry upon creation.
  #               Defaults to one year from now.
  #   :expired_prior_to overrides the value of 'now' when checking for
  #                     expired entries.  Mostly useful for unit testing.
  #
  def self.with_web_cache(key, opts = {})
    serialized_key = YAML.dump(key)
    expires_at = opts[:expires_at] || 1.year.from_now
    expired_prior_to = opts[:expired_prior_to] || Time.zone.now
    if (r = self.where(:key => serialized_key).where("expires_at > ?", expired_prior_to)).exists?
      # cache hit
      r.first.value
    else
      # cache miss
      yield.tap {|value| self.create!(:key => serialized_key, :value => value, :expires_at => expires_at)}
    end
  end

  # Prune expired entries.  Typically called by a cron job.
  def self.delete_expired_entries(expired_prior_to = Time.zone.now)
    self.where("expires_at < ?", expired_prior_to).destroy_all
  end

end
于 2012-08-01T05:11:38.600 に答える