就像@James Woodward 所说,您将要为每个零售商创建一个类。我要发布的模式包含三个部分:
- 几个
ActiveRecord
实现通用接口的类,用于存储要从每个站点记录的数据
- 700 个不同的类,一个用于您要抓取的每个站点。这些类实现了抓取网站的算法,但不知道如何将信息存储在数据库中。为此,它们依赖于步骤 1 中的通用接口。
- 最后一个类将它们联系在一起,运行您在步骤 2 中编写的每个抓取算法。
第 1 步:ActiveRecord
接口
这一步非常简单。你已经有一个Site
和SiteDetail
类。您可以保留它们以将您从网站上抓取的数据存储在数据库中。
你告诉Site
和SiteDetail
类如何从网站上抓取数据。我认为这是不合适的。现在你已经赋予类两个职责:
- 将数据持久化到数据库中
- 从网站上抓取数据
我们将在第二步创建新的类来处理抓取责任。现在,您可以删除Site
和SiteDetail
类,以便它们仅充当数据库记录:
class Site < ActiveRecord::Base
has_many :site_details
end
class SiteDetail < ActiveRecord::Base
belongs_to :site
end
第 2 步:实施刮板
现在,我们将创建处理抓取责任的新类。如果这是一种支持抽象类或接口(如 Java 或 C#)的语言,我们将这样进行:
- 创建一个
IScraper
或AbstractScraper
界面来处理抓取网站的常见任务。
FooScraper
为您要抓取的每个站点实现一个不同的类,每个站点都继承AbstractScraper
或实现IScraper
.
不过,Ruby 没有抽象类。它所拥有的是鸭子类型和模块混合。这意味着我们将使用这种非常相似的模式:
- 创建一个
SiteScraper
模块来处理抓取网站的常见任务。该模块将假定扩展它的类具有可以调用的某些方法。
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
...等等,创建一个混合SiteScraper
并实现的类,get_product_urls
并get_product_details
为您需要抓取的 700 个网站中的每个网站创建一个类。不幸的是,这是模式中乏味的部分:没有真正的方法可以为所有 700 个站点编写不同的抓取算法。
第 3 步:运行每个 Scraper
最后一步是创建抓取站点的 cron 作业。
every :day, at: '12:00am' do
ExampleScraper.new.get_data
FooBarScraper.new.get_data
# + 698 more lines
end