2017-07-30 21 views
1

我正在玩一個簡單的多線程網絡爬蟲。我看到很多消息來源都將抓取工具稱爲「明顯並行」,因爲您可以從不同的URL進行抓取,但我從來沒有看到他們討論過抓取工具如何處理他們以前見過的URL。看起來,某種全球地圖對於避免重複搜索同一頁面至關重要,但是關鍵部分將如何構建?爲了最大限度地提高性能,鎖具有多細密?我只想看到一個不太密集,不太簡單的例子。併發的Web爬蟲是否通常將訪問的URL存儲在併發映射中,或者使用同步來避免同一頁面爬行兩次?

回答

1

如果你堅持只使用Java併發框架做,那麼ConcurrentHashMap可能是要走的路。其中有趣的方法是ConcurrentHashMap.putIfAbsent方法,它會給你非常好的效率,並且如何使用它的想法是:

你會有一些來自爬網頁面的「多線程來源URL地址」 - 你可以使用一些併發隊列來存儲它們,或者只是創建一個帶有(無界?)隊列的ExecutorService,在該隊列中放置可以抓取URL的Runnables。

爬行內可運行您應該有已經抓取網頁這種常見的ConcurrentHashMap的引用,並在很開始run方法做:

private final ConcurrentHashMap<String, Long> crawledPages = new ConcurrentHashMap<String, Long>(); 
... 

private class Crawler implements Runnable { 
    private String urlToBeCrawled; 

    public void Crawler(String urlToBeCrawled) { 
    this.urlToBeCrawled = urlToBeCrawled; 
    } 

    public void run() { 
    if (crawledPages.putIfAbsent(urlToBeCrawled, System.currentTimeMillis())==null) { 
     doCrawlPage(urlToBeCrawled); 
    } 
    } 
} 

如果crawledPages.putIfAbsent(urlToBeCrawled)將返回null給你,然後你知道這個網頁沒有被任何人抓取,因爲這個方法原子地放置了你爬行這個頁面的進展值 - 你是幸運線程,如果它會返回一個非空值,那麼你知道有人已經照顧關於這個URL,所以你的runnable應該完成了,並且線程返回到下一個Runnable使用的池。

0

履帶不使用的ConcurrentHashMap,而使用DATABSE

visisted URL的數量會增長得非常快,所以它不是將它們存儲在內存中的好東西,更好的使用databese,商店網址和上次抓取的日期,然後只檢查該網址是否已存在於數據庫中或有資格進行刷新。我在嵌入模式下使用了一個Derby數據庫,它對我的​​網絡爬蟲非常有用。我不建議像H2一樣在內存數據庫中使用,因爲隨着爬網頁數的增加,你最終會得到OutOfMemoryException。

您很少會在同一時間內多次抓取同一頁面,因此檢查數據庫是否最近已經被抓取,足以避免浪費大量資源「重新抓取相同的頁面並結束「。我相信這是「一個很好的解決方案,不是太密集,也不是太簡單」

另外,使用Databse和url的「上次訪問日期」,您可以在需要時停止並繼續工作,使用ConcurrentHashMap當應用程序退出時,會丟失所有結果您可以使用「上次訪問日期」來確定網址是否需要重新抓取。

+0

由於您只是想插入一組訪問過的網址,並檢查該網址是否存在,那麼設置redis可能是最好的解決方案。您也可以存儲一個散列,其中密鑰將成爲訪問的網址,而值則是上次訪問的日期。在任何情況下,幾乎任何類型的數據庫都比內存中的結構更好。 – Vasil

+0

這很好,但我仍然想確定避免競爭條件所需的最小交易保證。我很欣賞數據庫是要走的路,但是這個項目本質上主要是自學的,如果我只是把它扔到數據庫中,我就會失去它。將問題限制爲使用數據結構可以讓我看到引擎蓋下的所有事情,即關鍵部分。 –

1

您可以使用ConcurrentHashMap來存儲以查找重複的網址。 ConcurrentHashMap也使用分離鎖定機制,而不是使用全局鎖定。

或者您可以使用您自己的實現,您可以將所有數據拆分爲不同的密鑰。

番石榴API

Striped<ReadWriteLock> rwLockStripes = Striped.readWriteLock(10); 
String key = "taskA"; 
ReadWriteLock rwLock = rwLockStripes.get(key); 
try{ 
    rwLock.lock(); 
    ..... 
}finally{ 
    rwLock.unLock(); 
} 

的ConcurrentHashMap實例的實例

private Set<String> urls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); 
1

特定域的用例:使用內存

如果是特定於域說abc.com那麼最好是有vistedURL設置或內存並行哈希映射,在內存中會更快,檢查訪問狀態,內存消耗會相對較少。數據庫將有IO開銷並且代價高昂,訪問狀態檢查將非常頻繁。它會大大地影響你的表現。根據您的使用情況,您可以在內存或數據庫中使用。我的用例特定於訪問URL不會被再次訪問的域,所以我使用了Concurrent哈希映射。