3

現在、Nokogiri を使用して単一の小売業者の Web サイトから製品をスクレイピングするためのこのクラスがあります。XPath、CSS パスの詳細は MySQL に保存されます。

ActiveRecord::Base.establish_connection( 
  :adapter => "mysql2",
  ...
)

class Site < ActiveRecord::Base
  has_many :site_details

  def create_product_links
    # http://www.example.com
    p = Nokogiri::HTML(open(url))
    p.xpath(total_products_path).each {|lnk| SiteDetail.find_or_create_by(url: url + "/" + lnk['href'], site_id: self.id)}
  end    
end

class SiteDetail < ActiveRecord::Base
  belongs_to :site   

  def get_product_data
    # http://www.example.com
    p = Nokogiri::HTML(open(url))
    title = p.css(site.title_path).text
    price = p.css(site.price_path).text
    description = p.css(site.description_path).text
    update_attributes!(title: title, price: price, description: description)
  end 
end

# Execution
@s = Site.first
@s.site_details.get_product_data

将来的にはさらに多くのサイト (約 700) を追加する予定です。サイトごとにページ構成が異なります。したがって、get_product_dataメソッドをそのまま使用することはできません。case or if statement関連するコードをジャンプして実行するために使用する必要がある場合があります。すぐに、このクラスは非常に分厚く醜いものになります (700 の小売業者)。

このシナリオに適した最適な設計アプローチは何ですか?

4

1 に答える 1

1

@James Woodwardが言ったように、小売業者ごとにクラスを作成したいと思うでしょう。投稿するパターンには 3 つの部分があります。

  1. ActiveRecord各サイトから記録するデータを格納するための共通インターフェイスを実装するいくつかのクラス
  2. スクレイピングするサイトごとに 1 つずつ、700 の異なるクラス。これらのクラスは、サイトをスクレイピングするためのアルゴリズムを実装していますが、データベースに情報を保存する方法を知りません。そのために、ステップ 1 の共通インターフェースに依存しています。
  3. ステップ 2 で作成した各スクレイピング アルゴリズムを実行して、すべてを結び付ける 1 つの最終クラス。

ステップ 1:ActiveRecordインターフェース

このステップはとても簡単です。すでにSiteandSiteDetailクラスがあります。ウェブサイトからスクレイピングしたデータをデータベースに保存するために、それらを保持できます。

SiteおよびSiteDetailクラスに、Web サイトからデータをスクレイピングする方法を説明しました。これは不適切であると私は主張します。これで、クラスに 2 つの責任を与えました。

  1. データベースにデータを永続化する
  2. ウェブサイトからデータをスクレイピングする

2番目のステップで、スクレイピングの責任を処理する新しいクラスを作成します。今のところ、クラスSiteSiteDetailクラスを削除して、データベース レコードとしてのみ機能するようにすることができます。

class Site < ActiveRecord::Base
  has_many :site_details
end

class SiteDetail < ActiveRecord::Base
  belongs_to :site
end

ステップ 2: スクレイパーを実装する

ここで、スクレイピングの責任を処理する新しいクラスを作成します。これが Java や C# のような抽象クラスまたはインターフェースをサポートする言語である場合、次のように進めます。

  1. Web サイトのスクレイピングに共通するタスクを処理するIScraperまたはインターフェイスを作成します。AbstractScraper
  2. FooScraperスクレイピングするサイトごとに異なるクラスを実装し、それぞれが から継承AbstractScraperまたは実装しIScraperます。

ただし、Ruby には抽象クラスがありません。それが持っているのは、ダックタイピングとモジュールのミックスインです。これは、次の非常によく似たパターンを使用することを意味します。

  1. SiteScraperWeb サイトのスクレイピングに共通するタスクを処理するモジュールを作成します。このモジュールは、それを拡張するクラスが呼び出すことができる特定のメソッドを持っていることを前提としています。
  2. FooScraperスクレイピングするサイトごとに異なるクラスを実装し、それぞれがモジュールに混在し、SiteScraperモジュールが期待するメソッドを実装します。

次のようになります。

module SiteScraper
  # Assumes that classes including the module
  # have get_products and get_product_details methods
  #
  # The get_product_urls method should return a list
  # of the URLs to visit to get scraped data
  #
  # The get_product_details the URL of the product to
  # scape as a string and return a SiteDetail with data
  # scraped from the given URL 
  def get_data
    site = Site.new
    product_urls = get_product_urls

    for product_url in product_urls
      site_detail = get_product_details product_url
      site_detail.site = site
      site_detail.save
    end
  end
end 

class ExampleScraper
  include 'SiteScraper'

  def get_product_urls
    urls = []
    p = Nokogiri::HTML(open('www.example.com/products'))
    p.xpath('//products').each {|lnk| urls.push lnk}
    return urls
  end

  def get_product_details(product_url)
    p = Nokogiri::HTML(open(product_url))
    title = p.css('//title').text
    price = p.css('//price').text
    description = p.css('//description').text

    site_detail = SiteDetail.new
    site_detail.title = title
    site_detail.price = price
    site_detail.description = description
    return site_detail
  end
end

class FooBarScraper
  include 'SiteScraper'

  def get_product_urls
    urls = []
    p = Nokogiri::HTML(open('www.foobar.com/foobars'))
    p.xpath('//foo/bar').each {|lnk| urls.push lnk}
    return urls
  end

  def get_product_details(product_url)
    p = Nokogiri::HTML(open(product_url))
    title = p.css('//foo').text
    price = p.css('//bar').text
    description = p.css('//foo/bar/iption').text

    site_detail = SiteDetail.new
    site_detail.title = title
    site_detail.price = price
    site_detail.description = description
    return site_detail
  end
end

...など、スクレイピングする必要がある 700 の Web サイトのそれぞれについて、混合して実装するクラスを作成しますSiteScraper。残念ながら、これはパターンの退屈な部分です。700 のサイトすべてに対して異なるスクレイピング アルゴリズムを作成することを回避する現実的な方法はありません。get_product_urlsget_product_details

ステップ 3: 各スクレーパーを実行する

最後のステップは、サイトをスクレイピングする cron ジョブを作成することです。

every :day, at: '12:00am' do
  ExampleScraper.new.get_data
  FooBarScraper.new.get_data
  # + 698 more lines
end
于 2013-09-15T21:05:11.370 に答える