Как правильно ползти? - PullRequest
1 голос
/ 06 марта 2012

Я уже месяц работаю с Nokogiri, REXML & Ruby. У меня есть гигантская база данных, которую я пытаюсь сканировать. Вещи, которые я очищаю, это HTML-ссылки и XML-файлы.

Есть ровно 43612 файлов XML, которые я хочу сканировать и хранить в файле CSV.

Мой сценарий работает, если сканировать, может быть, 500 XML-файлов, но больше, что занимает слишком много времени и он зависает или что-то в этом роде.

Я разделил код на части, чтобы его было легко прочитать, весь сценарий / код здесь: https://gist.github.com/1981074

Я использую две библиотеки, потому что я не смог найти способ сделать все это в нокогири. Лично мне REXML проще в использовании.

Мой вопрос: как это можно исправить, чтобы мне не пришлось ползти всю неделю? Как мне заставить его работать быстрее?

ЗДЕСЬ МОЙ СКРИПТ:

Требуется необходимая библиотека:

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

Ответы [ 3 ]

4 голосов
/ 06 марта 2012

Трудно точно определить точную проблему из-за структуры кода.Вот несколько предложений по увеличению скорости и структуризации программы, чтобы было легче найти то, что вас блокирует.

Библиотеки

Вы используете здесь много библиотек, которые, вероятно, не подходят.

Вы используете REXML и Nokogiri.Они оба делают одну и ту же работу.За исключением того, что Nokogiri намного лучше ( тест ).

Использование хэшей

Вместо хранения данных в index в 15 массивах, используйте один набор хэшей.

Например,

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])

Надеюсь, это поможет!

2 голосов
/ 06 марта 2012

Это не будет работать без ваших исправлений.И я верю, что вы должны поступить так, как сказал @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)
1 голос
/ 06 марта 2012

Если вы действительно хотите ускорить его, вам придется идти одновременно.

Один из самых простых способов - установить JRuby, а затем запустить ваше приложение с одной небольшой модификацией: установить гемы 'peach' или 'pmap', а затем изменить items.each на items.peach(n) (параллельно каждому), где n - количество потоков.Вам понадобится как минимум один поток на каждое ядро ​​процессора, но если вы добавите ввод / вывод в цикл, вам понадобится больше.

Кроме того, используйте Nokogiri, это на намного быстрее.Задайте отдельный вопрос Nokogiri, если вам нужно решить что-то конкретное с Nokogiri.Я уверен, что он может делать то, что вам нужно.

...