【发布时间】:2017-10-18 20:25:54
【问题描述】:
我正在构建一个爬虫,它获取一个 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 files 和 no such host(主机是确实存在)。
处理此问题的最佳方法是什么?我是否应该检查这些错误并在收到它们一段时间后暂停执行,直到其他请求完成?或者在特定时间指定最大可能请求数? (这对我来说更有意义,但不确定如何准确编码)
【问题讨论】:
-
您面临的问题与操作系统控制的每个用户打开文件的限制有关。如果您使用的是 Linux/Unix,您可能可以使用 ulimit -n 4096 命令增加限制。这个命令有一个阈值,它不能设置你想要的打开文件的数量。因此,如果您想进一步推动它,则需要修改 /etc/security/limits.conf 文件并设置硬限制和软限制。
-
另外,你正在为你遇到的每个链接启动一个 goroutine,如果有的话,在某些时候它们中的许多会破坏 goroutines 的目的,并且实际上需要更长的时间来完成任务。您应该尝试使用固定数量的 goroutine 来进行处理并从通道读取,而不是为每个链接启动一个新的。看看blog.golang.org/pipelines
-
或者可能是这样的模式:gobyexample.com/worker-pools? (顺便说一句,您的
WaitGroup用法很奇怪。为每个 goroutine 添加 1,并在每个 goroutine 中延迟Done。其他任何东西都在要求错误) -
处理它的最佳方法取决于您想做什么。这是一个设计决策,而不是技术问题。
标签: go concurrency parallel-processing