之前一直以為"爬蟲"是一門高大上的技術(shù),但自從遇見goquery之后,發(fā)現(xiàn)爬取網(wǎng)站也可以這么簡(jiǎn)單。
goquery是一個(gè)使用go語(yǔ)言寫的HTML解析庫(kù),它最大的特點(diǎn)就是可以像使用jQuery那樣,來(lái)方便地操作DOM文檔,相信做過(guò)web開發(fā)的人員很快就能掌握其使用方法。
selector(選擇器)
我認(rèn)為selector是這個(gè)框架的靈魂所在,就是因?yàn)閷?shí)現(xiàn)了類似于jQuery的DOM選擇功能,才使得框架非常容易使用。
以下是幾個(gè)常用的選擇器,看著是不是很熟悉:
s.Find("div") // 元素選擇
s.Find("#Content") // id選擇
s.Find(".content") // class選擇
s.Find("div[id=Content]") // 屬性選擇
s.Find("div>p") // 子元素選擇
s.Find("div+p") // 相鄰元素選擇
s.Find("div~p") // 兄弟元素選擇
s.Find("#Content").Text() // 獲取對(duì)象的文本內(nèi)容
s.Find("#Content").Html() // 獲取對(duì)象的html
s.Find("#Content").Attr("src") // 獲取對(duì)象的src屬性值
這里推薦一篇文章,非常詳細(xì)地介紹了goquery選擇器的各種用法。
實(shí)戰(zhàn)
介紹方面網(wǎng)上有寫的很好的文章,我也沒(méi)有什么新的內(nèi)容補(bǔ)充,所以直接進(jìn)入實(shí)戰(zhàn)部分了。
頁(yè)面分析
這里我用goquery爬了豆瓣電影(心疼豆瓣,好多人把豆瓣電影當(dāng)爬蟲練手),通過(guò)對(duì)豆瓣電影主頁(yè)進(jìn)行分析,發(fā)現(xiàn)電影列表是通過(guò)ajax獲取的,然而goquery針對(duì)的只是靜態(tài)的DOM文檔,對(duì)于動(dòng)態(tài)的數(shù)據(jù)它就無(wú)能為力了。
通過(guò)觀察,找到獲取電影列表的url,發(fā)現(xiàn)是get方法獲取的,那么我們就可以編程構(gòu)造get請(qǐng)求獲取電影列表進(jìn)行處理了,其有type、tag、sort、page_limit、page_start這幾個(gè)參數(shù),操作一下頁(yè)面很容易獲取這幾個(gè)參數(shù)值。

使用goquery爬取的是具體的電影詳情頁(yè)面,也沒(méi)有搞得多復(fù)雜,只獲取一些基本信息用于展示即可。
爬取電影詳情頁(yè)信息
其實(shí)文字上也沒(méi)什么好描述的,看代碼來(lái)的更直觀明了,先講一下步驟,首先自然是要get請(qǐng)求獲取頁(yè)面內(nèi)容了,然后創(chuàng)建一個(gè)goquery解析器,最后使用選擇器獲取需要的數(shù)據(jù)即可。
func GetMovieInfo(url string) *MovieParam {
// get請(qǐng)求獲取頁(yè)面
res, err := http.Get(url)
if err != nil {
log.Println(err)
return nil
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Printf("status code error: %d %s", res.StatusCode, res.Status)
return nil
}
// 創(chuàng)建解析器
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Println(err)
return nil
}
param := MovieParam{}
doc.Find("#content").Each(func(i int, s *goquery.Selection) {
param.Year = s.Find("h1 .year").Text() // 年份
param.Img, _ = s.Find("#mainpic img").Attr("src") // 圖片
param.Summary, _ = s.Find("#link-report span[property]").Html() // 摘要
param.Rating_people = comhelper.StringToInt(s.Find(".rating_people span[property]").Text()) // 評(píng)論人數(shù)
star, _ := s.Find(".bigstar").Attr("class") // 星級(jí)值
param.Bigstar = comhelper.StringToInt(star[len(star)-2 : len(star)])
stars_five := s.Find(".stars5+div+span").Text() // 5星的比例值
param.Stars_five = comhelper.StringToFloat(stars_five[0:len(stars_five)-1], 64)
stars_four := s.Find(".stars4+div+span").Text() // 4星的比例值
param.Stars_four = comhelper.StringToFloat(stars_four[0:len(stars_four)-1], 64)
stars_three := s.Find(".stars3+div+span").Text() // 3星的比例值
param.Stars_three = comhelper.StringToFloat(stars_three[0:len(stars_three)-1], 64)
stars_two := s.Find(".stars2+div+span").Text() // 2星的比例值
param.Stars_two = comhelper.StringToFloat(stars_two[0:len(stars_two)-1], 64)
stars_one := s.Find(".stars1+div+span").Text() // 1星的比例值
param.Stars_one = comhelper.StringToFloat(stars_one[0:len(stars_one)-1], 64)
// 圖片轉(zhuǎn)換成base64
img_url, _ := _download_img(param.Img)
new_img, err := comhelper.ImgToBase64(img_url)
if err == nil && new_img != "" {
param.Img = new_img
}
s.Find("#info").Each(func(ii int, ss *goquery.Selection) {
info, _ := ss.Html()
param.Director = ss.Find("a[rel*=directedBy]").Text() // 導(dǎo)演
film_length, _ := ss.Find("span[property*=runtime]").Attr("content") // 時(shí)長(zhǎng)
param.Film_length = comhelper.StringToInt(film_length)
param.Release_date = ss.Find("span[property*=initialReleaseDate]").Text() // 上映日期
// 獲取類型
tags := ""
ss.Find("span[property*=genre]").Each(func(i int, s *goquery.Selection) {
if tags == "" {
tags += s.Text()
} else {
tags += "/" + s.Text()
}
})
param.Tags = tags
// 獲取主演
actor := ""
ss.Find("a[rel*=starring]").Each(func(i int, s *goquery.Selection) {
if actor == "" {
actor += s.Text()
} else {
actor += "/" + s.Text()
}
})
param.Actor = actor
c_start := strings.Index(info, "<span class=\"pl\">制片國(guó)家/地區(qū):</span>")
c_end := strings.Index(info, "<span class=\"pl\">語(yǔ)言")
param.Country = comhelper.TrimHtml(info[c_start+44 : c_end])
})
})
return ¶m
}
那些有id、class或者特殊屬性的字段最容易獲取了,比較麻煩的是那些沒(méi)有明顯特征的字段,只能通過(guò)字符串截取的方法獲取了,不過(guò)也都是些常規(guī)操作,整個(gè)流程下來(lái)沒(méi)什么難點(diǎn),這也說(shuō)明了goquery的簡(jiǎn)單易用。
成果展示
成果展示以及源碼點(diǎn)擊這里(抱歉,服務(wù)器太貴了,已脫坑)

遇到的問(wèn)題
頻繁訪問(wèn)會(huì)導(dǎo)致ip被鎖住,不過(guò)我也只是練習(xí),所以只是爬取了一點(diǎn)數(shù)據(jù)用來(lái)展示。
圖片會(huì)有訪問(wèn)權(quán)限的問(wèn)題,所以我轉(zhuǎn)換成了base64格式存到數(shù)據(jù)庫(kù)里,不過(guò)在頁(yè)面渲染的時(shí)候由于數(shù)據(jù)量過(guò)大導(dǎo)致頁(yè)面加載巨慢。