d
,
e
:=
goquery
.
NewDocumentFromReader
(
reader io
.
Reader
)
d
,
e
:=
goquery
.
NewDocument
(
url
string
)
2、查找内容
ele
.
Find
(
"#title"
)
//根据id查找
ele
.
Find
(
".title"
)
//根据class查找
ele
.
Find
(
"h2"
).
Find
(
"a"
)
//链式调用
3、获取内容
ele
.
Html
()
ele
.
Text
()
4、获取属性
ele
.
Attr
(
"href"
)
ele
.
AttrOr
(
"href"
,
""
)
ele
.
Find
(
".item"
).
Each
(
func
(
index
int
,
ele
*
goquery
.
Selection
){
更多api请参考
官方文档
http://liyangliang.me/posts/2016/03/zhihu-go-insight-parsing-html-with-goquery/
zhihu-go 源码解析:用 goquery 解析 HTML
上一篇博客
简单介绍了
zhihu-go
项目的缘起,本篇简单介绍一下关于处理 HTML 的细节。
因为知乎没有开发 API,所以只能通过模拟浏览器操作的方式获取数据,这些数据有两种格式:普通的 HTML 文档和某些 Ajax 接口返回的 JSON(返回的数据实际上也是 HTML)。其实也就是爬虫了,抓取网页,然后提取数据。一般来说从 HTML 文档提取数据有这些做法:正则、XPath、CSS 选择器等。对我来说,正则写起来比较复杂,代码可读性差而且维护起来麻烦;XPath 没有详细了解,不过用起来应该不难,而且 Chrome 浏览器可以直接提取 XPath. zhihu-go 里用的是选择器的方式,使用了
goquery
.
goquery 是 “a little like that j-thing, only in Go”,也就是用 jQuery 的方式去操作 DOM. jQuery 大家都很熟,API 也很简单明了。本文不详细介绍 goquery,下面选几个场景(API)讲讲在 zhihu-go 里的应用。
创建 Document 对象
goquery 暴露了两个结构体:
Document
和
Selection
.
Document
表示一个 HTML 文档,
Selection
用于像 jQuery 一样操作,支持链式调用。goquery 需要指定一个 HTML 文档才能继续后续的操作,有以下几个构造方式:
NewDocumentFromNode(root *html.Node) *Document
: 传入
*html.Node
对象,也就是根节点。
NewDocument(url string) (*Document, error)
: 传入 URL,内部用
http.Get
获取网页。
NewDocumentFromReader(r io.Reader) (*Document, error)
: 传入
io.Reader
,内部从 reader 中读取内容并解析。
NewDocumentFromResponse(res *http.Response) (*Document, error)
: 传入 HTTP 响应,内部拿到
res.Body
(实现了
io.Reader
) 后的处理方式类似
NewDocumentFromReader
.
因为知乎的页面需要登录才能访问(还需要伪造请求头),而且我们并不想手动解析 HTML 来获取
*html.Node
,最后用到了另外两个构造方法。大致的使用场景是:
请求 HTML 页面(如问题页面),调用
NewDocumentFromResponse
请求 Ajax 接口,返回的 JSON 数据里是一些 HTML 片段,用
NewDocumentFromReader
,其中
r = strings.NewReader(html)
为了方便举例说明,下文采用这个定义:
var doc *goquery.Document
.
查找到指定节点
Selection
有一系列类似 jQuery 的方法,
Document
结构体内嵌了
*Selection
,因此也能直接调用这些方法。主要的方法是
Selection.Find(selector string)
,传入一个选择器,返回一个新的,匹配到的
*Selection
,所以能够链式调用。
比如在用户主页(如
黄继新
),要获取用户的 BIO. 首先用 Chrome 定位到对应的 HTML:
<span class="bio" title="和知乎在一起">和知乎在一起</span>
对应的 go 代码就是:
doc.Find("span.bio")
如果一个选择器对应多个结果,可以使用 First()
, Last()
, Eq(index int)
, Slice(start, end int)
这些方法进一步定位。
还是在用户主页,在用户资料栏的底下,从左往右展示了提问数、回答数、文章数、收藏数和公共编辑的次数。查看 HTML 源码后发现这几项的 class 是一样的,所以只能通过下标索引来区分。
先看 HTML 源码:
<div class="profile-navbar clearfix">
<a class="item " href="/people/jixin/asks">提问<span class="num">1336</span></a>
<a class="item " href="/people/jixin/answers">回答<span class="num">785</span></a>
<a class="item " href="/people/jixin/posts">文章<span class="num">91</span></a>
<a class="item " href="/people/jixin/collections">收藏<span class="num">44</span></a>
<a class="item " href="/people/jixin/logs">公共编辑<span class="num">51648</span></a>
</div>
如果要定位找到回答数,对应的 go 代码是:
doc.Find("div.profile-navbar").Find("span.num").Eq(1)
经常需要获取一个标签的内容和某些属性值,使用 goquery 可以很容易做到。
继续上面获取回答数的例子,用 Text() string
方法可以获取标签内的文本内容,其中包含所有子标签。
text := doc.Find("div.profile-navbar").Find("span.num").Eq(1).Text()