之前一直以為"爬蟲"是一門高大上的技術,但自從遇見goquery之后,發現爬取網站也可以這么簡單。
goquery是一個使用go語言寫的HTML解析庫,它最大的特點就是可以像使用jQuery那樣,來方便地操作DOM文檔,相信做過web開發的人員很快就能掌握其使用方法。
selector(選擇器)
我認為selector是這個框架的靈魂所在,就是因為實現了類似于jQuery的DOM選擇功能,才使得框架非常容易使用。
以下是幾個常用的選擇器,看著是不是很熟悉:
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() // 獲取對象的文本內容
s.Find("#Content").Html() // 獲取對象的html
s.Find("#Content").Attr("src") // 獲取對象的src屬性值
這里推薦一篇文章,非常詳細地介紹了goquery選擇器的各種用法。
實戰
介紹方面網上有寫的很好的文章,我也沒有什么新的內容補充,所以直接進入實戰部分了。
頁面分析
這里我用goquery爬了豆瓣電影(心疼豆瓣,好多人把豆瓣電影當爬蟲練手),通過對豆瓣電影主頁進行分析,發現電影列表是通過ajax獲取的,然而goquery針對的只是靜態的DOM文檔,對于動態的數據它就無能為力了。
通過觀察,找到獲取電影列表的url,發現是get方法獲取的,那么我們就可以編程構造get請求獲取電影列表進行處理了,其有type、tag、sort、page_limit、page_start這幾個參數,操作一下頁面很容易獲取這幾個參數值。
使用goquery爬取的是具體的電影詳情頁面,也沒有搞得多復雜,只獲取一些基本信息用于展示即可。
爬取電影詳情頁信息
其實文字上也沒什么好描述的,看代碼來的更直觀明了,先講一下步驟,首先自然是要get請求獲取頁面內容了,然后創建一個goquery解析器,最后使用選擇器獲取需要的數據即可。
func GetMovieInfo(url string) *MovieParam {
// get請求獲取頁面
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
}
// 創建解析器
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()) // 評論人數
star, _ := s.Find(".bigstar").Attr("class") // 星級值
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)
// 圖片轉換成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() // 導演
film_length, _ := ss.Find("span[property*=runtime]").Attr("content") // 時長
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\">制片國家/地區:</span>")
c_end := strings.Index(info, "<span class=\"pl\">語言")
param.Country = comhelper.TrimHtml(info[c_start+44 : c_end])
})
})
return ¶m
}
那些有id、class或者特殊屬性的字段最容易獲取了,比較麻煩的是那些沒有明顯特征的字段,只能通過字符串截取的方法獲取了,不過也都是些常規操作,整個流程下來沒什么難點,這也說明了goquery的簡單易用。
成果展示
成果展示以及源碼點擊這里(抱歉,服務器太貴了,已脫坑)
遇到的問題
頻繁訪問會導致ip被鎖住,不過我也只是練習,所以只是爬取了一點數據用來展示。
圖片會有訪問權限的問題,所以我轉換成了base64格式存到數據庫里,不過在頁面渲染的時候由于數據量過大導致頁面加載巨慢。