2011-11-23 104 views
3

我有一個控制器,我試圖從遠程源獲取XML文件。一次多個Nokogiri請求

喜歡的東西:

@artist = Nokogiri.XML(open(url).read) 

不過,我想立刻獲得不同的數據來執行這些倍數。我能以某種方式使用線程嗎?

單獨執行需要400ms。所以當它們連續執行三次時,響應高達大約1s +。

+0

你可能想看看在百頭巨怪和水潤,如果你擔心並行加載網址。他們是經過充分測試的工具,而不是自己寫。 –

回答

5

是的,你可以使用線程:

named_urls = { 
    artist: 'http://foo.com/bar', 
    song: 'http://foo.com/jim', 
    # etc. 
} 
@named_xmls = {} 
one_at_a_time = Mutex.new 
named_urls.map do |name,url| 
    Thread.new do 
    doc = Nokogiri.XML(open(url).read) 
    one_at_a_time.synchronize{ @named_xmls[name] = doc } 
    end 
end.each(&:join) 

# At this point @named_xmls will be populated will all Nokogiri documents 

我不能肯定,如果在一個共享的哈希寫入不同的密鑰,需要一個互斥或沒有,但它不會傷害是安全的。

+0

太棒了。這工作很好!非常感謝。 – Jonovono

4

對於大量的網址,您無法打開大量的線程,因爲您將飽和連接帶寬,並且您將開始發生連接錯誤。對於我的特定電纜調制解調器和特定的服務器,我發現16個線程是一個很好的價值。

我使用了一個Mathematica來控制和改變我的ruby web scrapping程序的線程數並監視它的性能以獲得不同數量的線程。這是結果:

performance vs threads plot

,而不是直接使用Thread.new,我寫道,打開一個新的線程,只有當線程總數少於你的配置最大的包裝功能:

def maybe_new_thread 
    File.open('max_threads.cfg', 'r') { |file| @MAX_THREADS = file.gets.to_i } 
    if Thread.list.size < @MAX_THREADS 
    Thread.new { yield } 
    else 
    yield 
    end 
end 

請注意,所需線程的最大數量僅爲存儲在名爲max_threads.cfg的文件中的數字,並且每次調用函數時都會讀取此文件。這允許您在程序運行時更改此變量的值。

程序的一般結構是這樣的:

named_urls = [ 'http://foo.com/bar', (... hundreds of urls ...),'http://foo.com/jim'] 
named_urls.each do |url| 
    maybe_new_thread do 
    doc = Nokogiri.HTML(open(url)) 
    process_and_insert_in_database(doc) 
    end 
end 

注意,每個線程存儲其結果在數據庫中,所以我並不需要使用Mutex類來協調線程之間的任何東西。

當我插入到數據庫中時,我將包含每個結果插入時的精確時間的列。這是至關重要的,以便您可以計算您獲得的表現。確保你用毫秒支持來定義這個列(我使用MariaDB 5.3)。

這是我在用數學控制線程的最大數量,並實時繪製圖中的代碼:當它運行

named_urls = { 
    'http://foo.com/bar', (... hundreds of urls ...),'http://foo.com/jim', 
} 
named_urls.each do |url| 
    maybe_new_thread do 
    doc = Nokogiri.HTML(open(url)) 
    process_and_insert_in_database(doc) 
    end 
end 

setNumberOfThreads[n_] := Module[{}, 
    Put[n, "max_threads.cfg"]; 
    SQLExecute[conn,"DELETE FROM results"]] 

operationsPerSecond := SQLExecute[conn, 
    "SELECT 
    (SELECT COUNT(*) FROM results)/ 
    (SELECT TIME_TO_SEC(TIMEDIFF((SELECT fin FROM results ORDER BY finishTime DESC LIMIT 1), 
            (SELECT fin FROM results ORDER BY finishTime  LIMIT 1))))"][[1, 1]]; 
cops = {}; 
RunScheduledTask[AppendTo[cops, operationsPerSecond], 2]; 
Dynamic[ListLinePlot[cops]] 

,一旦你看到的是,性能穩定,您可以使用setNumberOfThreads[]更改線程數,並查看性能的影響。

最後一個評論。而不是直接使用開放式的URI的開法,我用這個包裝,所以這是錯誤的情況下,它會自動重試:

def reliable_open(uri) 
    max_retry = 10 
    try_counter = 1 
    while try_counter < max_retry 
    begin 
     result = open(uri) 
     return result 
    rescue 
     puts "Error when trying to open #{uri}" 
     try_counter += 1 
     sleep try_counter * 10 
    end 
    end 
    raise "Imposible to open after #{max_retry} retries" 
end