问题

还是从代码开始吧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func fetch(url string) {
    tlsConfig := &tls.Config{
        InsecureSkipVerify: true,
    }

    transport := &http.Transport{
        TLSClientConfig: tlsConfig,
    }
    client := http.Client{Transport: transport}
    resp, err := client.Get(url)
    if err != nil {
        fmt.Println(err)
        <-time.After(300 * time.Second)
        go fetch(url)
        return
    }

    buf, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("fetchyh:", err)
        return
    }

    save(buf) //保存html到文件
    
    defer resp.Body.Close()

    // http to doc
    doc, err := goquery.NewDocumentFromResponse(resp)
    if err != nil {
        fmt.Println(err, "http resp to doc failed")
        return
    }

    /*
        <div class="datanowin" id="myCont2">
    */
    datanowin := doc.Find("div.datanowin")
    fmt.Println("datanowin:", datanowin.Length())
    tables := datanowin.Find("table")

    tablelen := tables.Length()
    fmt.Println("tablelen:", tablelen)
    for i := 0; i < tablelen; i++ {
        item := tables.Eq(i)
        tableDo(item)
    }
    fmt.Println(doc.Find(".table").Length())

}

调用上面的代码后,html网页上没有找到自己所需要的内容,上面的代码问题在哪里? 假想一下,一般有以下原因选项:

  1. http请求失败
  2. html网页没有对应内容
  3. goquery使用错误导致未找到对应内容

自己排查一下,发现原因不是上面三个选项上,那又是什么原因呢? 原因是对于http response实例只能读一次,只有第一次才是从0开始读,下一次是上一次读到位置开始的

代码分析

层层调用与跳转就此略过,只看读取buf内容最终实现代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// A Reader implements the io.Reader, io.ReaderAt, io.WriterTo, io.Seeker,
// io.ByteScanner, and io.RuneScanner interfaces by reading from
// a byte slice.
// Unlike a Buffer, a Reader is read-only and supports seeking.
type Reader struct {
    s        []byte
    i        int64 // current reading index
    prevRune int   // index of previous rune; or < 0
}

1
2
3
4
5
6
7
8
9
func (r *Reader) Read(b []byte) (n int, err error) {
    if r.i >= int64(len(r.s)) {
        return 0, io.EOF
    }
    r.prevRune = -1
    n = copy(b, r.s[r.i:])
    r.i += int64(n) // 读index向后移动
    return
}

看到这些代码问题就很清楚了,第一次读取resp.Body时ioutil.ReadAll将所有内容都完了,第二次读调用goquery.NewDocumentFromResponse没有读到任何内容。

其实问题不止上面这个:看了ioutil.ReadAll的流程后,又看了goquery.NewDocumentFromResponse的实现,发现还有一个问题:代码还存在一个问题,代码存在两次调用defer resp.Body.Close(),这是goquery.NewDocumentFromResponse接管了resp.Body,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func NewDocumentFromResponse(res *http.Response) (*Document, error) {
    if res == nil {
        return nil, errors.New("Response is nil")
    }
    defer res.Body.Close() // 接管了resp.Body
    if res.Request == nil {
        return nil, errors.New("Response.Request is nil")
    }

    // Parse the HTML into nodes
    root, e := html.Parse(res.Body)
    if e != nil {
        return nil, e
    }

    // Create and fill the document
    return newDocument(root, res.Request.URL), nil
}

一种简单解决上述问题代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func fetch(url string) {
    tlsConfig := &tls.Config{
        InsecureSkipVerify: true,
    }

    transport := &http.Transport{
        TLSClientConfig: tlsConfig,
    }
    client := http.Client{Transport: transport}
    resp, err := client.Get(url)
    if err != nil {
        fmt.Println(err)
        <-time.After(300 * time.Second)
        go fetch(url)
        return
    }
/*
    buf, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("fetchyh:", err)
        return
    }

    save(buf) //保存html到文件
*/    
    // defer resp.Body.Close()

    // http to doc
    doc, err := goquery.NewDocumentFromResponse(resp)
    if err != nil {
        fmt.Println(err, "http resp to doc failed")
        return
    }

    /*
        <div class="datanowin" id="myCont2">
    */
    datanowin := doc.Find("div.datanowin")
    fmt.Println("datanowin:", datanowin.Length())
    tables := datanowin.Find("table")

    tablelen := tables.Length()
    fmt.Println("tablelen:", tablelen)
    for i := 0; i < tablelen; i++ {
        item := tables.Eq(i)
        tableDo(item)
    }
    fmt.Println(doc.Find(".table").Length())

}

总结

第一次遇到这个问题,我就想到是先调用ioutil.ReadAll读取resp.Body的内容,后面调用goquery.NewDocumentFromResponse就没有读到东西。于是我把ioutil.ReadAll相关代码去掉,问题解决了。后面我就没有深入追究原因(还是给自己找个理由:忙)。

本来我是抗拒写这篇文章的,想想自己在这个问题老是不长记性,还是读一下相关源码,简单记录下来,加深印象。

读源码是最有收益的解决问题方式

最后推荐一下这篇文章:50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

(end)