2017-10-18 166 views
0

我正在構建一個抓取器,它需要一個URL,從中提取鏈接,並將它們中的每一個訪問到一定深度;在特定的網站上製作路徑樹。處理「打開文件過多」的最佳方法是什麼?

我實現並行這個爬蟲的方式是,我儘快訪問每個新發現的URL,因爲它的發現是這樣的:

func main() { 
    link := "https://example.com" 

    wg := new(sync.WaitGroup) 
    wg.Add(1) 

    q := make(chan string) 
    go deduplicate(q, wg) 
    q <- link 
    wg.Wait() 
} 

func deduplicate(ch chan string, wg *sync.WaitGroup) { 
    for link := range ch { 
     // seen is a global variable that holds all seen URLs 
     if seen[link] { 
      wg.Done() 
      continue 
     } 
     seen[link] = true 
     go crawl(link, ch, wg) 
    } 
} 

func crawl(link string, q chan string, wg *sync.WaitGroup) { 
    // handle the link and create a variable "links" containing the links found inside the page 
    wg.Add(len(links)) 
    for _, l := range links { 
     q <- l} 
    } 
} 

這對於相對較小的站點工作正常,但是當我在一個運行大的鏈接到處都有很多鏈接,我開始在一些請求中獲得這兩個錯誤中的一個:socket: too many open filesno such host(主機確實存在)。

處理這個問題的最佳方法是什麼?我是否應該檢查這些錯誤並暫停執行,直到其他請求完成爲止?或者在特定時間指定可能請求的最大數量? (這對我更有意義,但不知道如何精確地編碼)

+0

您正面臨與操作系統控制的每個用戶打開文件的限制有關的問題。如果您使用Linux/Unix,則可以使用ulimit -n 4096命令來增加限制。該命令有一個閾值,它不能設置你想要打開的文件的數量。所以如果你想進一步推動它,那麼你需要修改/etc/security/limits.conf文件並設置硬性限制和軟限制。 –

+2

另外,你正在爲每個環節啓動一個配置程序,如果存在的話,那麼在某些時候它們中的許多人會失敗goroutines的目的,而且實際上需要更長的時間才能完成任務。你應該嘗試使用固定數量的goroutine來完成處理並從頻道讀取,而不是爲每個鏈接啓動一個新的。看看https://blog.golang.org/pipelines – Topo

+4

或者可能是這樣的模式:https://gobyexample.com/worker-pools? (順便說一下,你的'WaitGroup'的用法很奇怪,爲每個goroutine加1,並且在每個goroutine中延遲'Done'。其他任何東西都是要求bug的) – JimB

回答

0

錯誤socket: too many open files中引用的文件包括線程和套接字(http請求來加載被抓取的網頁)。 看到這個question

由於無法創建文件,DNS查詢也很可能失敗,但是報告的錯誤是no such host

這個問題可以固定在兩個方面:

1) Increase the maximum number of open file handles 
2) Limit the maximum number of concurrent `crawl` calls 

1)是最簡單的解決方案,但可能並不理想,因爲它只是直到找到一個網站,有更多的鏈接推遲的問題,新的限制。對於Linux使用可以設置此限制ulimit -n

2)更多的是設計問題。我們需要限制可以併發的http請求的數量。我修改了一些代碼。最重要的變化是maxGoRoutines。隨着每個開始一個值的刮取通話被插入到通道中。一旦通道滿了,下一個通話將被阻塞,直到通道中的值被刪除。每次刮叫完成後,該值將從頻道中刪除。

package main 

import (
    "fmt" 
    "sync" 
    "time" 
) 

func main() { 
    link := "https://example.com" 

    wg := new(sync.WaitGroup) 
    wg.Add(1) 

    q := make(chan string) 
    go deduplicate(q, wg) 
    q <- link 
    fmt.Println("waiting") 
    wg.Wait() 
} 

//This is the maximum number of concurrent scraping calls running 
var MaxCount = 100 
var maxGoRoutines = make(chan struct{}, MaxCount) 

func deduplicate(ch chan string, wg *sync.WaitGroup) { 
    seen := make(map[string]bool) 
    for link := range ch { 
     // seen is a global variable that holds all seen URLs 
     if seen[link] { 
      wg.Done() 
      continue 
     } 
     seen[link] = true 
     wg.Add(1) 
     go crawl(link, ch, wg) 
    } 
} 

func crawl(link string, q chan string, wg *sync.WaitGroup) { 
    //This allows us to know when all the requests are done, so that we can quit 
    defer wg.Done() 

    links := doCrawl(link) 

    for _, l := range links { 
     q <- l 
    } 
} 

func doCrawl(link string) []string { 
    //This limits the maximum number of concurrent scraping requests 
    maxGoRoutines <- struct{}{} 
    defer func() { <-maxGoRoutines }() 

    // handle the link and create a variable "links" containing the links found inside the page 
    time.Sleep(time.Second) 
    return []string{link + "a", link + "b"} 
} 
相關問題