最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose

來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 16:38:39
文檔

zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose

zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose:上一篇博客 簡單介紹了 zhihu-go項目的緣起,本篇簡單介紹一下關(guān)于處理 HTML 的細節(jié)。 因為知乎沒有開發(fā) API,所以只能通過模擬瀏覽器操作的方式獲取數(shù)據(jù),這些數(shù)據(jù)有兩種格式:普通的 HTML 文檔和某些 Ajax 接口返回的 JSON(返回的數(shù)據(jù)實際上也是 H
推薦度:
導(dǎo)讀zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose:上一篇博客 簡單介紹了 zhihu-go項目的緣起,本篇簡單介紹一下關(guān)于處理 HTML 的細節(jié)。 因為知乎沒有開發(fā) API,所以只能通過模擬瀏覽器操作的方式獲取數(shù)據(jù),這些數(shù)據(jù)有兩種格式:普通的 HTML 文檔和某些 Ajax 接口返回的 JSON(返回的數(shù)據(jù)實際上也是 H

上一篇博客 簡單介紹了 zhihu-go項目的緣起,本篇簡單介紹一下關(guān)于處理 HTML 的細節(jié)。

因為知乎沒有開發(fā) API,所以只能通過模擬瀏覽器操作的方式獲取數(shù)據(jù),這些數(shù)據(jù)有兩種格式:普通的 HTML 文檔和某些 Ajax 接口返回的 JSON(返回的數(shù)據(jù)實際上也是 HTML)。其實也就是爬蟲了,抓取網(wǎng)頁,然后提取數(shù)據(jù)。一般來說從 HTML 文檔提取數(shù)據(jù)有這些做法:正則、XPath、CSS 選擇器等。對我來說,正則寫起來比較復(fù)雜,代碼可讀性差而且維護起來麻煩;XPath 沒有詳細了解,不過用起來應(yīng)該不難,而且 Chrome 瀏覽器可以直接提取 XPath. zhihu-go 里用的是選擇器的方式,使用了 goquery.

goquery 是 “a little like that j-thing, only in Go”,也就是用 jQuery 的方式去操作 DOM. jQuery 大家都很熟,API 也很簡單明了。本文不詳細介紹 goquery,下面選幾個場景(API)講講在 zhihu-go 里的應(yīng)用。

創(chuàng)建 Document 對象

goquery 暴露了兩個結(jié)構(gòu)體: Document和 Selection. Document表示一個 HTML 文檔, Selection用于像 jQuery 一樣操作,支持鏈?zhǔn)秸{(diào)用。goquery 需要指定一個 HTML 文檔才能繼續(xù)后續(xù)的操作,有以下幾個構(gòu)造方式:

  • NewDocumentFromNode(root *html.Node) *Document: 傳入 *html.Node對象,也就是根節(jié)點。
  • NewDocument(url string) (*Document, error): 傳入 URL,內(nèi)部用 http.Get獲取網(wǎng)頁。
  • NewDocumentFromReader(r io.Reader) (*Document, error): 傳入 io.Reader,內(nèi)部從 reader 中讀取內(nèi)容并解析。
  • NewDocumentFromResponse(res *http.Response) (*Document, error): 傳入 HTTP 響應(yīng),內(nèi)部拿到 res.Body(實現(xiàn)了 io.Reader) 后的處理方式類似 NewDocumentFromReader.
  • 因為知乎的頁面需要登錄才能訪問(還需要偽造請求頭),而且我們并不想手動解析 HTML 來獲取 *html.Node,最后用到了另外兩個構(gòu)造方法。大致的使用場景是:

  • 請求 HTML 頁面(如問題頁面),調(diào)用 NewDocumentFromResponse
  • 請求 Ajax 接口,返回的 JSON 數(shù)據(jù)里是一些 HTML 片段,用 NewDocumentFromReader,其中 r = strings.NewReader(html)
  • 為了方便舉例說明,下文采用這個定義: var doc *goquery.Document.

    查找到指定節(jié)點

    Selection有一系列類似 jQuery 的方法, Document結(jié)構(gòu)體內(nèi)嵌了 *Selection,因此也能直接調(diào)用這些方法。主要的方法是 Selection.Find(selector string),傳入一個選擇器,返回一個新的,匹配到的 *Selection,所以能夠鏈?zhǔn)秸{(diào)用。

    比如在用戶主頁(如 黃繼新),要獲取用戶的 BIO. 首先用 Chrome 定位到對應(yīng)的 HTML:

    和知乎在一起

    對應(yīng)的 go 代碼就是:

    doc.Find("span.bio")

    如果一個選擇器對應(yīng)多個結(jié)果,可以使用 First(), Last(), Eq(index int), Slice(start, end int)這些方法進一步定位。

    還是在用戶主頁,在用戶資料欄的底下,從左往右展示了提問數(shù)、回答數(shù)、文章數(shù)、收藏數(shù)和公共編輯的次數(shù)。查看 HTML 源碼后發(fā)現(xiàn)這幾項的 class 是一樣的,所以只能通過下標(biāo)索引來區(qū)分。

    先看 HTML 源碼:

    提問1336回答785文章91收藏44公共編輯51648

    如果要定位找到回答數(shù),對應(yīng)的 go 代碼是:

    doc.Find("div.profile-navbar").Find("span.num").Eq(1)

    屬性操作

    經(jīng)常需要獲取一個標(biāo)簽的內(nèi)容和某些屬性值,使用 goquery 可以很容易做到。

    繼續(xù)上面獲取回答數(shù)的例子,用 Text() string方法可以獲取標(biāo)簽內(nèi)的文本內(nèi)容,其中包含所有子標(biāo)簽。

    text := doc.Find("div.profile-navbar").Find("span.num").Eq(1).Text() // "785"

    需要注意的是, Text()方法返回的字符串,可能前后有很多空白字符,可以視情況做清除。

    獲取屬性值也很容易,有兩個方法:

  • Attr(attrName string) (val string, exists bool): 返回屬性值和該屬性是否存在,類似從 map中取值
  • AttrOr(attrName, defaultValue string) string: 和上一個方法類似,區(qū)別在于如果屬性不存在,則返回給定的默認值
  • 常見的使用場景就是獲取一個 a 標(biāo)簽的鏈接。繼續(xù)上面獲取回答的例子,如果想要得到用戶回答的主頁,可以這么做:

    href, _ := doc.Find("div.profile-navbar").Find("a.item").Eq(1).Attr("href")

    還有其他設(shè)置屬性、操作 class 的方法,就不展開討論了。

    迭代

    很多場景需要返回列表數(shù)據(jù),比如問題的關(guān)注者列表、所有回答,某個答案的點贊的用戶列表等。這種情況下一般需要用到迭代,遍歷所有的同類節(jié)點,做某些操作。

    goquery 提供了三個用于迭代的方法,都接受一個匿名函數(shù)作為參數(shù):

  • Each(f func(int, *Selection)) *Selection: 其中函數(shù) f的第一個參數(shù)是當(dāng)前的下標(biāo),第二個參數(shù)是當(dāng)前的節(jié)點
  • EachWithBreak(f func(int, *Selection) bool) *Selection: 和 Each類似,增加了中途跳出循環(huán)的能力,當(dāng) f返回 false時結(jié)束迭代
  • Map(f func(int, *Selection) string) (result []string): f的參數(shù)與上面一樣,返回一個 string 類型,最終返回 []string.
  • 比如獲取一個收藏夾(如 黃繼新的收藏:關(guān)于知乎的思考)下所有的問題,可以這么做(見 zhihu-go/collections.go):

    func getQuestionsFromDoc(doc *goquery.Document) []*Question {	questions := make([]*Question, 0, pageSize)	items := doc.Find("div#zh-list-answer-wrap").Find("h2.zm-item-title")	items.Each(func(index int, sel *goquery.Selection) {	a := sel.Find("a")	qTitle := strip(a.Text())	qHref, _ := a.Attr("href")	thisQuestion := NewQuestion(makeZhihuLink(qHref), qTitle)	questions = append(questions, thisQuestion)	})	return questions}

    EachWithBreak在 zhihu-go 中也有用到,可以參見 Answer.GetVotersN 方法: zhihu-go/answer.go.

    刪除節(jié)點、插入 HTML、導(dǎo)出 HTML

    有一個需求是把回答內(nèi)容輸出到 HTML,說白了其實就是修復(fù)和清洗 HTML,具體的細節(jié)可以看 answer.go 里的 answerSelectionToHtml 函數(shù). 其中用到了一些需要修改文檔的操作。

    比如,調(diào)用 Remove()方法把一個節(jié)點刪掉:

    sel.Find("noscript").Each(func(_ int, tag *goquery.Selection) { tag.Remove() // 把無用的 noscript 去掉})

    在節(jié)點后插入一段 HTML:

    sel.Find("img").Each(func(_ int, tag *goquery.Selection) { var src string if tag.HasClass("origin_image") { src, _ = tag.Attr("data-original") } else { src, _ = tag.Attr("data-actualsrc") } tag.SetAttr("src", src) if tag.Next().Size() == 0 { tag.AfterHtml("
    ") // 在 img 標(biāo)簽后插入一個換行 }})

    在標(biāo)簽尾部 append 一段內(nèi)容:

    wrapper := ``doc, _ := goquery.NewDocumentFromReader(strings.NewReader(wrapper))doc.Find("body").AppendSelection(sel)

    最終輸出為 html 文檔:

    html, err := doc.Html()

    總結(jié)

    上面的例子基本涵蓋了 zhihu-go 中關(guān)于 HTML 操作的場景,得益于 goquery 和 jQuery 的 API 風(fēng)格,實現(xiàn)起來還是非常簡單的。

    聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose

    zhihu-go源碼解析:用goquery解析HTML_html/css_WEB-ITnose:上一篇博客 簡單介紹了 zhihu-go項目的緣起,本篇簡單介紹一下關(guān)于處理 HTML 的細節(jié)。 因為知乎沒有開發(fā) API,所以只能通過模擬瀏覽器操作的方式獲取數(shù)據(jù),這些數(shù)據(jù)有兩種格式:普通的 HTML 文檔和某些 Ajax 接口返回的 JSON(返回的數(shù)據(jù)實際上也是 H
    推薦度:
    標(biāo)簽: 使用 知乎 解析
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top