我正在尝试加速ruby算法。我有一个rails应用程序,它使用活动记录和nokogiri访问数据库中的URL列表,并从页面中抓取主图像并将其保存在与该URL关联的图像属性下。
这个rails任务通常需要大约2:30才能完成,我正在尝试将其加速作为学习练习。是否可以通过RubyInline和原始SQL代码使用C来实现所需的结果?我唯一的问题是,如果我使用C,我会丢失与ruby有活动记录的数据库连接,并且不知道如何与正确连接到我的数据库的C代码一起编写SQL查询。
有没有人有这方面的经验,甚至知道它是否可能?我这样做主要是一个学习练习,并想知道它是否有可能。如果您感兴趣,以下是我想要转换为C和SQL的代码:
task :getimg => :environment do
stories = FeedEntry.all
stories.each do |story|
if story.image.nil?
url = story.url
doc = Nokogiri::HTML(open(url))
if doc.at_css(".full-width img")
img = doc.at_css(".full-width img")[:src]
story.image = img
story.save!
elsif doc.at_css(".body-width img")
img = doc.at_css(".body-width img")[:src]
story.image = img
story.save!
elsif doc.at_css(".body-narrow-width img")
img = doc.at_css(".body-narrow-width img")[:src]
story.image = img
story.save!
elsif doc.at_css(".caption img")
img = doc.at_css(".caption img")[:src]
story.image = img
story.save!
elsif doc.at_css(".cnnArticleGalleryPhotoContainer img")
img = doc.at_css(".cnnArticleGalleryPhotoContainer img")[:src]
story.image = img
story.save!
elsif doc.at_css(".cnn_strylftcntnt div img")
img = doc.at_css(".cnn_strylftcntnt div img")[:src]
story.image = img
story.save!
elsif doc.at_css(".cnn_stryimg640captioned img")
img = doc.at_css(".cnn_stryimg640captioned img")[:src]
story.image = img
story.save!
end
else
#do nothing
end
end
end
我将非常感谢此事的所有帮助和见解。提前谢谢!!
答案 0 :(得分:1)
网址是否遥控?如果是这样,首先对其进行基准测试以查看网络延迟。如果这是瓶颈,我认为您与您的代码或您选择的语言无关。
您的数据库中有多少FeedEntry
个?我建议使用FeedEntry.find_each
而不是FeedEntry.all.each
,因为前者将1000个条目加载到内存中,处理它们,然后加载接下来的1000个条目......,而后者将所有条目加载到内存中然后迭代超过它们,这需要更多的内存并增加GC周期。
如果瓶颈既不是上述的瓶颈之一,那么可能是DOM节点搜索算法很慢。您可以找到(仅一个?)img
节点,然后在必要时检查其父节点或祖父节点,并相应地更新您的条目。
image_node = doc.at_css('img')
story.update image: image_node['src'] if needed?(image_node)
def needed?(image_node)
parent_node = image_node.parent
parent_class = image_node.parent['class']
return true if parent_class == 'full-width'
return true if parent_class == 'body-width'
return true if parent_class == 'body-narrow-width'
return true if parent_class == 'caption'
return true if parent_class == 'cnnArticleGalleryPhotoContainer'
return true if parent_class == 'cnn_stryimg640captioned'
return false unless parent_node.node_type == 'div'
return true if parent_node.parent['class'] == 'cnn_strylftcntnt'
false
end
答案 1 :(得分:1)
我在ruby中编写了一个Web爬虫,我发现可能影响性能的瓶颈之一是在数据库中实际创建了行。在提取所有网址结束时,单个质量insert
比单个插入更快(至少对于Postgres)更快。
因此,不要为您访问的每个网址调用YourModel.save!
,只需将每个网址推送到一个数组,该数组将跟踪您需要保存到数据库的网址。然后,一旦您完成了对所有链接的抓取,请通过sql命令对所有图像链接进行大量插入。
stories.each do |story|
url = story.url
doc = Nokogiri::HTML(open(url))
img_url = doc.at_css("img")[:src]
to_insert.push "(#{img_url})"
end
#notice the mass insert at the end
sql = "INSERT INTO your_table (img_url) VALUES #{to_insert.join(", ")}"
#CONN is a constant declared at the top of your file (CONN = ActiveRecord::Base.connection)
#that connects to the database
CONN.execute sql
下载链接也将成为瓶颈。因此,最好的选择是创建一个线程池,每个线程从数据库中分配一个URL分区来进行擦除。这样,在进行任何实际处理之前,您永远不会等待单个页面下载。
一些伪红宝石代码:
number_of_workers = 10
(1..number_of_workers).each do |worker|
Thread.new do
begin
urls_to_scrape_for_this_thread = [...list of urls to scrape...]
while urls_to_scrape > 0
url = take_one_url_from_list
scrape(url)
end
rescue => e
puts "========================================"
puts "Thread # #{i} error"
puts "#{e.message}"
puts "#{e.backtrace}"
puts "======================================="
raise e
end
end
end