我是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()
}
答案 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。