在Golang中爬行而不在内存中加载

时间:2018-03-07 12:52:55

标签: go web-crawler

我试图在go中重写一个网络爬虫(最初用gevent编写的python)。但是我已经碰壁了,无论我做什么,我都能获得快速的高内存消耗。例如,以下简单代码:

package main

import (
     "bufio"
     "fmt"
     "os"
     "net/http"
     "io"
     "strings"
     "time"
)

func readLine(in *bufio.Reader, domains chan<- string) {
    for conc := 0; conc < 500; conc++ {
        input, err := in.ReadString('\n')
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Fprintf(os.Stderr, "read(stdin): %s\n", err)
            os.Exit(1)
        }

        input = strings.TrimSpace(input)
        if input == "" {
            continue
        }

        domain := input
        domains <- domain
    }
}

func get(domains <-chan string) {
   url := <-domains
   URLresp, err := http.Get(url)
   if err != nil {
       fmt.Println(err)
   }
   if err == nil {
       fmt.Println(url," OK")
       URLresp.Body.Close()
   }
}

func main() {
    domains := make(chan string, 500)

    inFile, _ := os.Open("F:\\PATH\\TO\\LIST_OF_URLS_SEPARATED_BY_NEWLINE.txt")
    in := bufio.NewReader(inFile)

    for {
        go readLine(in, domains)
        for i := 0; i < 500; i++ { go get(domains) }
        time.Sleep(100000000)
    }
}

我尝试过pprof,但它似乎只说我只使用50mb的堆空间,而资源监控的内存消耗却在暴涨。

我还尝试创建一个没有Keep Alive的自定义http传输,因为我发现net / http保存连接以供重用,但没有运气。

2 个答案:

答案 0 :(得分:3)

让我们考虑您的代码有什么问题,重点关注您的main()功能。

func main() {
    domains := make(chan string, 500)

这很好。您创建一个缓冲通道来处理域列表输入。没问题。

    inFile, _ := os.Open("F:\\PATH\\TO\\LIST_OF_URLS_SEPARATED_BY_NEWLINE.txt")

您打开输入文件。你永远不应该忽视错误,但我们暂时忽略它。

    in := bufio.NewReader(inFile)

    for {

在这里你开始一个无限循环。为什么呢?

        go readLine(in, domains)

在这里,您可以阅读in文件中接下来的500行,并将它们传递到domains频道,但是您在后台执行此操作,这意味着下一行将在{readLine之前执行1}}有机会完成。

        for i := 0; i < 500; i++ { go get(domains) }

在这里,您可以并行呼叫get(domains) 500次。但如上所述,您在readLine完成之前执行此操作,因此(至少在第一次通过外循环时),对get()的大多数调用都将失败,因为domains频道很可能是空的。 get()函数无法正确处理此案例,但我会留下您的考虑。

        time.Sleep(100000000)

然后在再次开始无限循环之前你睡了0.1秒。

    }
}

无限循环将再次尝试再次在后台读取文件中的下500个项目。如果第一次调用readLine需要超过0.1秒才能完成,那么您将同时尝试读取该文件的两个readLine副本,这可能会导致恐慌。

假设这个行为正如您所期望的那样(虽然它肯定而且显然不是这样),在读完文件中的所有URL后,程序将永远继续,每0.1生成一个额外的501个例程。一次性例程尝试从文件中读取更多行,发现没有更多行,并立即退出。其他500个例程将永远等待从domains频道读取不存在的结果。这是你的记忆&#34;泄漏&#34;。

答案 1 :(得分:-3)

问题是golang net Dial缺少默认超时。它会因为不让goroutines死亡而占用资源。以下作品:

c := &http.Client{
 Transport: &http.Transport{
     DisableKeepAlives: true,
     Dial: (&net.Dialer{
             Timeout:   30 * time.Second,
             KeepAlive: 30 * time.Second,
     }).Dial,
     TLSHandshakeTimeout:   10 * time.Second,
     ResponseHeaderTimeout: 10 * time.Second,
     ExpectContinueTimeout: 1 * time.Second,}}

URLresp, err := c.Get(url)