Go网络抓取工具卡住

时间:2017-03-29 10:29:34

标签: go concurrency web-crawler

我是Go的新手并尝试实施网络抓取工具。它应该异步解析网页并将其内容保存到文件中,每个新页面一个文件。但是在我添加

后它就会卡住
u, _ := url.Parse(uri)
fileName := u.Host + u.RawQuery + ".html"
body, err := ioutil.ReadAll(resp.Body)
writes <- writer{fileName: fileName, body: body}

任何人都可以帮我解决这个问题吗?基本上我想从响应主体获取数据,将其推送到通道,然后从通道获取数据并将其放入文件中。 看起来writes频道未初始化,并且在nil频道上发送永久阻止。

package main

import (
    "crypto/tls"
    "flag"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "os"
    "runtime"

    "./linksCollector"
)

type writer struct {
    fileName string
    body     []byte
}

var writes = make(chan writer)

func usage() {
    fmt.Fprintf(os.Stderr, "usage: crawl http://example.com/")
    flag.PrintDefaults()
    os.Exit(2)
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    runtime.GOMAXPROCS(8)
    flag.Usage = usage
    flag.Parse()

    args := flag.Args()
    fmt.Println(args)
    if len(args) < 1 {
        usage()
        fmt.Println("Please specify start page")
        os.Exit(1)
    }

    queue := make(chan string)
    filteredQueue := make(chan string)

    go func() { queue <- args[0] }()
    go filterQueue(queue, filteredQueue)

    for uri := range filteredQueue {
        go enqueue(uri, queue)
    }

    for {
        select {
        case data := <-writes:
            f, err := os.Create(data.fileName)
            check(err)
            defer f.Close()
            _, err = f.Write(data.body)
            check(err)
        }
    }
}

func filterQueue(in chan string, out chan string) {
    var seen = make(map[string]bool)
    for val := range in {
        if !seen[val] {
            seen[val] = true
            out <- val
        }
    }
}

func enqueue(uri string, queue chan string) {
    fmt.Println("fetching", uri)
    transport := &http.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true,
        },
    }
    client := http.Client{Transport: transport}
    resp, err := client.Get(uri)
    check(err)

    defer resp.Body.Close()

    u, _ := url.Parse(uri)
    fileName := u.Host + u.RawQuery + ".html"
    body, err := ioutil.ReadAll(resp.Body)
    writes <- writer{fileName: fileName, body: body}

    links := collectlinks.All(resp.Body)

    for _, link := range links {
        absolute := fixURL(link, uri)
        if uri != "" {
            go func() { queue <- absolute }()
        }
    }
}

func fixURL(href, base string) string {
    uri, err := url.Parse(href)
    if err != nil {
        return ""
    }
    baseURL, err := url.Parse(base)
    if err != nil {
        return ""
    }
    uri = baseURL.ResolveReference(uri)
    return uri.String()
}

1 个答案:

答案 0 :(得分:1)

for循环在go enqueue收到数据导致发送给select导致程序崩溃之前,不止一次调用writes,我认为,我对Go的并发性并不熟悉。

更新:对于之前的回答,我很抱歉,这是一个很难通知的尝试,解释一些我对此知之甚少的知识。仔细看后,我几乎两件事情中的某些事情。 1。您的writes频道不是nil,您可以依靠make来宣传您的频道。 2。通道上的range循环将会阻止,直到该频道关闭为止。所以你的

for uri := range filteredQueue {
    go enqueue(uri, queue)
}

正在阻止,因此您的程序永远不会到达select,因此无法从writes频道收到。您可以通过在新的goroutine中执行range循环来避免这种情况。

go func() {
    for uri := range filteredQueue {
        go enqueue(uri, queue)
    }
}()

您的程序仍然会因其他原因而中断,但您应该能够通过使用sync.WaitGroup进行一点同步来解决这个问题。 这是一个简化的例子:https://play.golang.org/p/o2Oj4g8c2y

相关问题