1

Nokogiri、REXML、Rubyを1か月間使っています。クロールしようとしているこの巨大なデータベースがあります。私がスクレイピングしているのは、HTMLリンクとXMLファイルです。

クロールしてCSVファイルに保存したいXMLファイルは正確に43612あります。

私のスクリプトは、おそらく500 xmlファイルをクロールする場合に機能しますが、それよりも大きいと時間がかかりすぎてフリーズするなどの問題が発生します。

ここではコードを分割して読みやすくしました。スクリプト/コード全体はここにあります:https ://gist.github.com/1981074

nokogiriでこれをすべて行う方法が見つからなかったため、2つのライブラリを使用しています。個人的にはREXMLの方が使いやすいと思います。

私の質問:これをすべてクロールするのに1週間もかからないように、どうすれば修正できますか?どうすれば高速に実行できますか?

これが私のスクリプトです:

必要な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
4

3 に答える 3

4

コードの構造が原因で、問題を正確に特定することは困難です。ここでは、速度を上げてプログラムを構成し、ブロックしているものを見つけやすくするためのいくつかの提案を示します。

ライブラリ

ここでは、おそらく必要ではない多くのライブラリを使用しています。

との両方REXMLを使用しNokogiriます。彼らは両方とも同じ仕事をします。ただしNokogiri、それははるかに優れています(ベンチマーク)。

ハッシュを使用する

15個の配列にデータを格納する代わりにindex、1セットのハッシュを用意します。

例えば、

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セットができたので、それを繰り返してファイルに書き込むことができます。これは、行ごとに実行するよりもはるかに高速です。

乾かす

元のコードでは、同じことが12回繰り返されています。コピーして貼り付ける代わりに、一般的なコードを抽象化してみてください。

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

これがお役に立てば幸いです。

于 2012-03-05T22:42:06.587 に答える
2

それはあなたの修正なしでは機能しません。そして、@IanBishopが解析コードをリファクタリングすると言ったようにすべきだと思います

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)
于 2012-03-05T22:46:22.583 に答える
1

あなたが本当にそれをスピードアップしたいのなら、あなたは同時に行かなければならないでしょう。

最も簡単な方法の1つは、JRubyをインストールしてから、アプリケーションを1つの小さな変更で実行することです。「peach」または「pmap」gemのいずれかをインストールしてから、スレッド数items.eachitems.peach(n)(それぞれ並列)に変更します。nCPUコアごとに少なくとも1つのスレッドが必要ですが、ループにI / Oを配置する場合は、さらに多くのスレッドが必要になります。

また、ノコギリを使用すると、はるかに高速です。のこぎりで特定の問題を解決する必要がある場合は、別ののこぎりの質問をしてください。私はそれがあなたが必要とすることをすることができると確信しています。

于 2012-03-06T02:58:45.447 に答える