我想用 Go 编写一个简单的网络爬虫:
这是我的代码:
package main
import (
"encoding/csv"
"flag"
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"net/url"
"os"
"strings"
"sync"
)
type Enterprise struct {
name string
tax_code string
group string
capital string
}
var u, f string
var name, tax_code, group, capital string
func init() {
flag.StringVar(&u, "u", "", "Which URL to download from")
flag.StringVar(&f, "f", "", "Path to the csv file to write the output to")
}
func check(e error) {
if e != nil {
panic(e)
}
}
func findHrefs(u string) map[string]string {
resp, err := http.Get(u)
check(err)
doc, err := goquery.NewDocumentFromResponse(resp)
check(err)
e_hrefs := make(map[string]string)
doc.Find("td div a").Each(func(_ int, s *goquery.Selection) {
e_href, _ := s.Attr("href")
if strings.HasPrefix(e_href, "/Thong-tin-doanh-nghiep") && s.Text() != "" {
e_hrefs[e_href] = s.Text()
}
})
return e_hrefs
}
func fetch(url string, name string, file *os.File, wg *sync.WaitGroup, c chan Enterprise) {
defer wg.Done()
log.Println("Fetching URL", url)
resp, err := http.Get(url)
check(err)
doc, err := goquery.NewDocumentFromResponse(resp)
check(err)
e := new(Enterprise)
doc.Find("td").Each(func(_ int, s *goquery.Selection) {
if s.Text() == "Mã số thuế:" {
e.tax_code = s.Next().Text()
}
if s.Text() == "Tên ngành cấp 2:" {
e.group = s.Next().Text()
}
if s.Text() == "Sở hữu vốn:" {
e.capital = s.Next().Text()
}
})
w := csv.NewWriter(file)
w.Write([]string{name, "'" + e.tax_code, e.group, e.capital})
w.Flush()
c <- *e
}
func getDoc(u, f string) {
parsedUrl, err := url.Parse(u)
check(err)
file, err := os.Create(f)
check(err)
defer file.Close()
var wg sync.WaitGroup
c := make(chan Enterprise)
e_hrefs := findHrefs(u)
for e_href, name := range e_hrefs {
wg.Add(1)
go fetch(parsedUrl.Scheme+"://"+parsedUrl.Host+e_href, name, file, &wg, c)
}
wg.Wait()
}
func main() {
flag.Parse()
if u == "" || f == "" {
fmt.Println("-u=<URL to download from> -f=<Path to the CSV file>")
os.Exit(1)
}
getDoc(u, f)
}
问题是在所有 goroutine 完成后 channel 没有关闭,我必须按 control+C 来恢复我的 shell 提示:
2016/03/02 09:34:05 Fetching URL ...
2016/03/02 09:34:05 Fetching URL ...
2016/03/02 09:34:05 Fetching URL ...
^Csignal: interrupt
通过阅读this ,我将 getDoc 函数的最后一行更改为:
go func() {
wg.Wait()
close(c)
}()
现在我可以在运行时恢复我的 shell 提示,但是 channel 在所有 goroutine 完成并且没有写入 CSV 文件之前关闭。
我哪里出错了?
最佳答案
对我来说,它看起来不像是在从您的 channel 中读取数据,并且因为它是一个同步 channel (您从未在其上声明长度),所以如果它接收到一个值,它将被阻塞。所以你需要阅读你的 c通过 value <- c或者你的 fetch 函数将卡在 c <- *e
这会导致您的 sync.WaitGroup永远wg.Done()从不递减计数器,从不导致 wg.Wait()停止阻塞,这会导致你的 close(c)永远不会被调用
关于戈朗 : how to close the channel after all goroutines are finished?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35737796/
关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭3年前。Improvethisquestion告诉我如何使用Golang登录网站。下载xls文件是得到了,但是为了在Excel表格中有数据,需要登录网站。该站点位于公司的服务器上。如果你能告诉你怎么做。例如,我用来执行此操作的VBA代码。SetoFields=CreateObject("Scripting.Dictionary")WithoFields.Add"login","sdiscor".Add"password","sdiscor"EndWi
下面是我查询的代码:我有一个单维数组a当我打印a[0][0]时,我不明白为什么它返回字符a的ascii值:packagemainimport("fmt")funcmain(){a:=[3]string{"a","b","c"}fmt.Println(a[0][0])}输出:97 最佳答案 下面是如何打印ascii的代码示例a:=[3]string{"a","b","c"}for_,rune:=rangea{fmt.Println(rune)//Itwillprinta,b,c}因为你在你的代码中使用了[0][0],所以它是等价的fo
typepath[]bytefunc(ppath)ToUpper(){fori,b:=rangep{if'a'在上面(这个例子是从“TheGoBlog”复制过来的),如果ToUpper变成这样:func(ppath)ToUpper(){fori,_:=rangep{if'a'哪个会更有效率为什么?“TheGoBlog”对前一个说:“这里的ToUpper方法在forrange构造中使用两个变量来捕获索引和slice元素。这种形式的循环避免了在主体中多次写入p[i]。”什么意思? 最佳答案 前者有更多的内存操作,即在b上:它在循环的第一
我有一个用Golang编写的脚本,我不太明白。我想知道他为什么要在脚本里面写goserver.Start()?为什么不简单地编写server.Start?packagemainimport("github.com/miekg/dns""testing""time")constTEST_ADDR="127.0.0.1:9953"funcTestDNSResponse(t*testing.T){server:=NewDNSServer(&Config{dnsAddr:TEST_ADDR,})goserver.Start()//Allowsometimeforservertostarttim
我正在尝试在Go中做一些相对简单的事情——将字符串转换为整数,然后将其加倍:myInt,_:=strconv.Atoi(args[1])doubleArg:=myInt*2由于Atoi()返回两个参数(整数和err),我使用myInt,_:=来检索值的整数。我想将它加倍(因此是第二行)但不能在一行中完成所有操作:myInt,_:=strconv.Atoi(args[1])*2给我:multiple-valuestrconv.Atoi()insingle-valuecontext但是,根据我使用大多数其他语言的经验,必须在两行中执行此操作似乎有很多样板。这只是我必须处理的一个限制,还是有
我刚接触golang。尝试通过golang实现批量上传到Elasticsearch。我正在使用golang库->https://github.com/olivere/elastic用于与Elasticsearch通信。此外,我正在尝试一段示例代码,但出现以下错误...suresh@BLR-245:~/Desktop/tools/golang/src$goinstallgithub.com/crazyheart/elastic-bulk-upload#github.com/crazyheart/elastic-bulk-uploadgithub.com/crazyheart/elasti
我计划实现一个go-routine并有一个sync.WaitGroup同步创建的go-routine的结尾。我基本上使用go创建了一个线程.所以它是这样的:main(){varwgsync.WaitGroupfor{gomyThread(wg)wg.Add(1)}wg.wait()}myThread(wgsync.WaitGroup){deferwg.Done()}我之前曾与pthread_create合作过在某些情况下确实无法创建线程。在这种情况下,是否可能针对上述gomyThread(wg)无法启动和/或运行wg.Done()例程的其余部分是否正常运行?如果是这样,将报告什么以及如
Go的append()函数仅在给定slice的容量不足时分配新的slice数据(另请参见:https://stackoverflow.com/a/28143457/802833)。这可能会导致意外行为(至少对我这个golang新手来说):packagemainimport("fmt")funcmain(){a1:=make([][]int,3)a2:=make([][]int,3)b:=[][]int{{1,1,1},{2,2,2},{3,3,3}}common1:=make([]int,0)common2:=make([]int,0,12)//providesufficientcap
我需要编写一个测试来验证服务器响应。响应必须包含某些header和xml正文。首先,如何检查响应中是否存在所需的header。以及如何比较收到的XML和所需的XML。例如。响应必须包含header“Serv”。正文必须包含对象为“person”的xmlHTTP/1.1200OKConnection:Keep-AliveServ:"any-string"Content-Length:0Content-Type:text/xml;charset=UTF-8string-value我如何检查响应是否包含标题“Serv”并包含带有元素人和名称的正文xml 最佳答案
我四处搜索并没有找到另一个这样做的例子,但我无意中发现我能够通过简单地将另一个slice的片段传递给接受slice并返回它的函数来从另一个slice的片段创建一个sliceslice。例子:packagemainimport"fmt"funcmakeSliceFrom(s[]int)[]int{returns}funcmain(){s:=[]int{1,2,3,4,5,6,7,8,9,10}newS:=makeSliceFrom(s[1:7])fmt.Println(newS)}我不是在问这是否有效,因为我知道它有效并且似乎运作良好,我是在问这是否得到支持或有一些我不知道的不可预见的成