我一直在努力和修补Nokogiri,REXML& Ruby一个月了。我有这个巨大的数据库,我正在尝试抓取。我正在抓的东西是HTML链接和XML文件。
我想要抓取并存储在CSV文件中的正好有43612个XML文件。
如果抓取可能是500 xml文件,我的脚本可以正常工作,但是更大,需要花费太多时间并冻结或其他东西。
我已将代码分成几部分,因此很容易阅读,整个脚本/代码在这里:https://gist.github.com/1981074
我正在使用两个库,因为我无法在nokogiri找到一种方法。我个人觉得REXML更容易使用。
我的问题:怎么能解决这个问题所以一周我不能抓住这一切?如何让它运行得更快?
这是我的脚本:
需要必要的lib:
require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'rexml/document'
require 'csv'
include REXML
创建一堆数据来存储数据:
@urls = Array.new
@ID = Array.new
@titleSv = Array.new
@titleEn = Array.new
@identifier = Array.new
@typeOfLevel = Array.new
从规范网站获取所有xml链接并将其存储在名为@urls
的数组中htmldoc = Nokogiri::HTML(open('http://testnavet.skolverket.se/SusaNavExport/EmilExporter?GetEvent&EMILVersion=1.1&NotExpired&EEFormOfStudy=normal&EIAcademicType=UoH&SelectEI'))
htmldoc.xpath('//a/@href').each do |links|
@urls << links.content
end
循环抛出@urls数组,并使用xpath获取我想要抓取的每个元素节点。
@urls.each do |url|
# Loop throw the XML files and grab element nodes
xmldoc = REXML::Document.new(open(url).read)
# Root element
root = xmldoc.root
# Hämtar info-id
@ID << root.attributes["id"]
# TitleSv
xmldoc.elements.each("/educationInfo/titles/title[1] | /ns:educationInfo/ns:titles/ns:title[1]"){
|e| m = e.text
m = m.to_s
next if m.empty?
@titleSv << m
}
然后将它们存储在CSV文件中。
CSV.open("eduction_normal.csv", "wb") do |row|
(0..@ID.length - 1).each do |index|
row << [@ID[index], @titleSv[index], @titleEn[index], @identifier[index], @typeOfLevel[index], @typeOfResponsibleBody[index], @courseTyp[index], @credits[index], @degree[index], @preAcademic[index], @subjectCodeVhs[index], @descriptionSv[index], @lastedited[index], @expires[index]]
end
end
答案 0 :(得分:4)
由于代码的结构方式,很难确定确切的问题。以下是一些提高速度和构建程序的建议,以便更容易找到阻止你的内容。
你在这里使用了许多可能没有必要的库。
您同时使用REXML
和Nokogiri
。他们都做同样的工作。 Nokogiri
以外的情况要好得多(benchmark)。
不是将数据存储在15个数组中index
,而是使用一组哈希值。
例如,
items = Set.new
doc.xpath('//a/@href').each do |url|
item = {}
item[:url] = url.content
items << item
end
items.each do |item|
xml = Nokogiri::XML(open(item[:url]))
item[:id] = xml.root['id']
...
end
现在你已经设置了items
,你可以迭代它并写入文件。这比逐行进行要快得多。
在您的原始代码中,您重复了十几次相同的事情。而不是复制和粘贴,而是尝试抽象出公共代码。
xmldoc.elements.each("/educationInfo/titles/title[1] | /ns:educationInfo/ns:titles/ns:title[1]"){
|e| m = e.text
m = m.to_s
next if m.empty?
@titleSv << m
}
移动方法
的常见内容def get_value(xml, path)
str = ''
xml.elements.each(path) do |e|
str = e.text.to_s
next if str.empty?
end
str
end
将任何内容移动到另一个哈希
xml_paths = {
:title_sv => "/educationInfo/titles/title[1] | /ns:educationInfo/ns:titles/ns:title[1]",
:title_en => "/educationInfo/titles/title[2] | /ns:educationInfo/ns:titles/ns:title[2]",
...
}
现在,您可以结合使用这些技术来制作更清晰的代码
item[:title_sv] = get_value(xml, xml_paths[:title_sv])
item[:title_en] = get_value(xml, xml_paths[:title_en])
我希望这有帮助!
答案 1 :(得分:2)
没有你的修正,它将无法运作。而且我相信你应该像@Ian Bishop那样重构你的解析代码
require 'rubygems'
require 'pioneer'
require 'nokogiri'
require 'rexml/document'
require 'csv'
class Links < Pioneer::Base
include REXML
def locations
["http://testnavet.skolverket.se/SusaNavExport/EmilExporter?GetEvent&EMILVersion=1.1&NotExpired&EEFormOfStudy=normal&EIAcademicType=UoH&SelectEI"]
end
def processing(req)
doc = Nokogiri::HTML(req.response.response)
htmldoc.xpath('//a/@href').map do |links|
links.content
end
end
end
class Crawler < Pioneer::Base
include REXML
def locations
Links.new.start.flatten
end
def processing(req)
xmldoc = REXML::Document.new(req.respone.response)
root = xmldoc.root
id = root.attributes["id"]
xmldoc.elements.each("/educationInfo/titles/title[1] | /ns:educationInfo/ns:titles/ns:title[1]") do |e|
title = e.text.to_s
CSV.open("eduction_normal.csv", "a") do |f|
f << [id, title ...]
end
end
end
end
Crawler.start
# or you can run 100 concurrent processes
Crawler.start(concurrency: 100)
答案 2 :(得分:1)
如果你真的想加快速度,你将不得不同时进行。
最简单的方法之一是安装JRuby,然后通过一个小修改运行您的应用程序:安装'peach'或'pmap'宝石,然后将items.each
更改为items.peach(n)
(并行)每个),其中n
是线程数。每个CPU核心至少需要一个线程,但是如果你把I / O放在你的循环中,你就会想要更多。
另外,使用Nokogiri,它的很多更快。如果你需要解决Nokogiri特有的问题,请问一个单独的Nokogiri问题。我相信它可以做你需要的。