@James Woodwardが言ったように、小売業者ごとにクラスを作成したいと思うでしょう。投稿するパターンには 3 つの部分があります。
ActiveRecord
各サイトから記録するデータを格納するための共通インターフェイスを実装するいくつかのクラス
- スクレイピングするサイトごとに 1 つずつ、700 の異なるクラス。これらのクラスは、サイトをスクレイピングするためのアルゴリズムを実装していますが、データベースに情報を保存する方法を知りません。そのために、ステップ 1 の共通インターフェースに依存しています。
- ステップ 2 で作成した各スクレイピング アルゴリズムを実行して、すべてを結び付ける 1 つの最終クラス。
ステップ 1:ActiveRecord
インターフェース
このステップはとても簡単です。すでにSite
andSiteDetail
クラスがあります。ウェブサイトからスクレイピングしたデータをデータベースに保存するために、それらを保持できます。
Site
およびSiteDetail
クラスに、Web サイトからデータをスクレイピングする方法を説明しました。これは不適切であると私は主張します。これで、クラスに 2 つの責任を与えました。
- データベースにデータを永続化する
- ウェブサイトからデータをスクレイピングする
2番目のステップで、スクレイピングの責任を処理する新しいクラスを作成します。今のところ、クラスSite
とSiteDetail
クラスを削除して、データベース レコードとしてのみ機能するようにすることができます。
class Site < ActiveRecord::Base
has_many :site_details
end
class SiteDetail < ActiveRecord::Base
belongs_to :site
end
ステップ 2: スクレイパーを実装する
ここで、スクレイピングの責任を処理する新しいクラスを作成します。これが Java や C# のような抽象クラスまたはインターフェースをサポートする言語である場合、次のように進めます。
- Web サイトのスクレイピングに共通するタスクを処理する
IScraper
またはインターフェイスを作成します。AbstractScraper
FooScraper
スクレイピングするサイトごとに異なるクラスを実装し、それぞれが から継承AbstractScraper
または実装しIScraper
ます。
ただし、Ruby には抽象クラスがありません。それが持っているのは、ダックタイピングとモジュールのミックスインです。これは、次の非常によく似たパターンを使用することを意味します。
SiteScraper
Web サイトのスクレイピングに共通するタスクを処理するモジュールを作成します。このモジュールは、それを拡張するクラスが呼び出すことができる特定のメソッドを持っていることを前提としています。
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_urls
get_product_details
ステップ 3: 各スクレーパーを実行する
最後のステップは、サイトをスクレイピングする cron ジョブを作成することです。
every :day, at: '12:00am' do
ExampleScraper.new.get_data
FooBarScraper.new.get_data
# + 698 more lines
end