Goquery 从明显不是空的响应中加载空文档
Goquery loads empty document from distinctly not empty response
我一直在尝试将响应加载到 goquery 文档中,但它似乎失败了(尽管它没有抛出任何错误)。
我尝试加载的响应来自:
https://www.bbcgoodfood.com/search_api_ajax/search/recipes?sort=created&order=desc&page=4
虽然它没有抛出任何错误,但当我调用 fmt.Println(goquery.OuterHtml(doc.Contents()))
时,我得到了输出:
<html><head></head><body></body></html>
与此同时,如果我不尝试将其加载到 goquery 文档中,而是调用
s, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(s))
我得到:
<!doctype html>
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8 no-touch" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9 no-touch" lang="en"> <![endif]-->
<!--[if gt IE 8]> <html class="no-js gt-ie-8 no-touch" lang="en"> <![endif]-->
<!--[if !IE]><!-->
<html class="no-js no-touch" lang="en">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>Search | BBC Good Food</title>
<!--[if IE]><![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="prev" href="https://www.bbcgoodfood.com/search/recipes?page=3&sort=created&order=desc" />
<link rel="next" href="https://www.bbcgoodfood.com/search/recipes?page=5&sort=created&order=desc" />
<meta name="robots" content="noindex" />
<style>
.async-hide {
opacity: 0 !important
}
... etc
我做的基本逻辑如下:
package main
import (
"fmt"
"net/http"
"github.com/PuerkitoBio/goquery"
"io/ioutil"
)
func main() {
baseUrl := "https://www.bbcgoodfood.com/search_api_ajax/search/recipes?sort=created&order=desc&page="
i := 4
// Make a request
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s%d", baseUrl, i), nil)
// Create a new HTTP client and execute the request
client := &http.Client{}
resp, _ := client.Do(req)
// Print out response
s, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(s))
// Load into goquery doc
doc, _ := goquery.NewDocumentFromReader(resp.Body)
fmt.Println(goquery.OuterHtml(doc.Contents()))
}
可以找到完整的回复 here。有什么特殊原因导致无法加载吗?
Go 的 html 解析器似乎不喜欢你得到的 html - <html>
标签都在注释中,所以我认为它永远不会继续下去解析。
如果您在文档前添加 <html>
,一切正常。一种方法是使用 reader-wrapper,如下所示,它在第一次调用 Read
时写入 html 标记并委托给 resp.Body
后续调用。
import "io"
var htmlTag string = "<html>\n"
type htmlAddingReader struct {
sentHtml bool
source io.Reader
}
func (r *htmlAddingReader) Read(b []byte) (n int, err error) {
if !r.sentHtml {
copy(b, htmlTag)
r.sentHtml = true
return len(htmlTag), nil
} else {
return r.source.Read(b)
}
}
要在您的示例代码中使用它,请像这样更改最后一部分:
// Load into goquery doc
wrapped := &htmlAddingReader{}
wrapped.source = resp.Body
doc, _ := goquery.NewDocumentFromReader(wrapped)
fmt.Println(goquery.OuterHtml(doc.Contents()))
代码有两个问题:
(1) resp.Body
is an io.ReadCloser
流。
ioutil.ReadAll(resp.Body)
reads the whole stream, so there is nothing left for goquery.NewDocumentFromReader(resp.Body)
阅读,所以它 returns 一个空文档。
相反,您可以使用 NewReader(s)
从保存的正文字符串创建新流。
(2) doc.Contents()
returns 恰好是 <!DOCTYPE html>
的顶级元素的子元素。如果你想要整个文档,那么你可能想要使用 doc.Selection
.
像这样的东西应该可以工作:
// Read entire resp.Body into raw
raw, _ := io.ReadAll(resp.Body)
s := string(raw)
// Print out response
fmt.Println(s)
// Create a new readable stream with NewReader(s)
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(s))
// Use doc.Selection to get the whole doc
fmt.Println(doc.Selection.Html())
我一直在尝试将响应加载到 goquery 文档中,但它似乎失败了(尽管它没有抛出任何错误)。
我尝试加载的响应来自:
https://www.bbcgoodfood.com/search_api_ajax/search/recipes?sort=created&order=desc&page=4
虽然它没有抛出任何错误,但当我调用 fmt.Println(goquery.OuterHtml(doc.Contents()))
时,我得到了输出:
<html><head></head><body></body></html>
与此同时,如果我不尝试将其加载到 goquery 文档中,而是调用
s, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(s))
我得到:
<!doctype html>
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8 no-touch" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9 no-touch" lang="en"> <![endif]-->
<!--[if gt IE 8]> <html class="no-js gt-ie-8 no-touch" lang="en"> <![endif]-->
<!--[if !IE]><!-->
<html class="no-js no-touch" lang="en">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>Search | BBC Good Food</title>
<!--[if IE]><![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="prev" href="https://www.bbcgoodfood.com/search/recipes?page=3&sort=created&order=desc" />
<link rel="next" href="https://www.bbcgoodfood.com/search/recipes?page=5&sort=created&order=desc" />
<meta name="robots" content="noindex" />
<style>
.async-hide {
opacity: 0 !important
}
... etc
我做的基本逻辑如下:
package main
import (
"fmt"
"net/http"
"github.com/PuerkitoBio/goquery"
"io/ioutil"
)
func main() {
baseUrl := "https://www.bbcgoodfood.com/search_api_ajax/search/recipes?sort=created&order=desc&page="
i := 4
// Make a request
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s%d", baseUrl, i), nil)
// Create a new HTTP client and execute the request
client := &http.Client{}
resp, _ := client.Do(req)
// Print out response
s, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(s))
// Load into goquery doc
doc, _ := goquery.NewDocumentFromReader(resp.Body)
fmt.Println(goquery.OuterHtml(doc.Contents()))
}
可以找到完整的回复 here。有什么特殊原因导致无法加载吗?
Go 的 html 解析器似乎不喜欢你得到的 html - <html>
标签都在注释中,所以我认为它永远不会继续下去解析。
如果您在文档前添加 <html>
,一切正常。一种方法是使用 reader-wrapper,如下所示,它在第一次调用 Read
时写入 html 标记并委托给 resp.Body
后续调用。
import "io"
var htmlTag string = "<html>\n"
type htmlAddingReader struct {
sentHtml bool
source io.Reader
}
func (r *htmlAddingReader) Read(b []byte) (n int, err error) {
if !r.sentHtml {
copy(b, htmlTag)
r.sentHtml = true
return len(htmlTag), nil
} else {
return r.source.Read(b)
}
}
要在您的示例代码中使用它,请像这样更改最后一部分:
// Load into goquery doc
wrapped := &htmlAddingReader{}
wrapped.source = resp.Body
doc, _ := goquery.NewDocumentFromReader(wrapped)
fmt.Println(goquery.OuterHtml(doc.Contents()))
代码有两个问题:
(1) resp.Body
is an io.ReadCloser
流。
ioutil.ReadAll(resp.Body)
reads the whole stream, so there is nothing left for goquery.NewDocumentFromReader(resp.Body)
阅读,所以它 returns 一个空文档。
相反,您可以使用 NewReader(s)
从保存的正文字符串创建新流。
(2) doc.Contents()
returns 恰好是 <!DOCTYPE html>
的顶级元素的子元素。如果你想要整个文档,那么你可能想要使用 doc.Selection
.
像这样的东西应该可以工作:
// Read entire resp.Body into raw
raw, _ := io.ReadAll(resp.Body)
s := string(raw)
// Print out response
fmt.Println(s)
// Create a new readable stream with NewReader(s)
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(s))
// Use doc.Selection to get the whole doc
fmt.Println(doc.Selection.Html())