我目前使用Nokogiri从一个零售商网站上抓取产品。 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个零售商)。
在这种情况下,最适合的设计方法是什么?
答案 0 :(得分:1)
就像@James伍德沃德所说,你将要为每个零售商创建一个班级。我要发布的模式有三个部分:
ActiveRecord
类ActiveRecord
接口这一步非常简单。您已经有Site
和SiteDetail
课程。您可以保留它们以存储您从数据库中的网站获取的数据。
您告诉Site
和SiteDetail
课程如何从网站上抓取数据。我认为这是不合适的。现在你已经给了这两个职责:
我们将在第二步中创建新类来处理抓取责任。现在,您可以删除Site
和SiteDetail
类,以便它们仅充当数据库记录:
class Site < ActiveRecord::Base
has_many :site_details
end
class SiteDetail < ActiveRecord::Base
belongs_to :site
end
现在,我们将创建处理抓取责任的新类。如果这是一种支持抽象类或接口(如Java或C#)的语言,我们将继续这样做:
IScraper
或AbstractScraper
界面,用于处理抓取网站常见的任务。FooScraper
课程,每个网站都继承自AbstractScraper
或实施IScraper
。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
中的类,并为您需要抓取的700个网站中的每个网站实现get_product_urls
和get_product_details
。不幸的是,这是模式的繁琐部分:没有真正的方法可以为所有700个站点编写不同的抓取算法。
最后一步是创建擦除网站的cron作业。
every :day, at: '12:00am' do
ExampleScraper.new.get_data
FooBarScraper.new.get_data
# + 698 more lines
end