《Go語言四十二章經》第四十一章 網絡爬蟲

Colly網絡爬蟲框架

Colly是用Go實現的網絡爬蟲框架。Colly快速優雅,在單核上每秒可以發起1K以上請求;以回調函數的形式提供了一組接口,可以實現任意類型的爬蟲。

Colly 特性:

清晰的API 快速(單個內核上的請求數大於1k) 管理每個域的請求延遲和最大併發數 自動cookie 和會話處理 同步/異步/並行抓取 高速緩存 自動處理非Unicode的編碼 支持Robots.txt 定製Agent信息 定製抓取頻次

特性如此多,引無數程序員競折腰。下面開始我們的Colly之旅:

首先,下載安裝第三方包:go get -u github.com/gocolly/colly/...

接下來在代碼中導入包:

import "github.com/gocolly/colly"

準備工作已經完成,接下來就看看Colly的使用方法和主要的用途。

colly的主體是Collector對象,管理網絡通信和負責在作業運行時執行附加的回調函數。使用colly需要先初始化Collector:

c := colly.NewCollector() 

我們看看NewCollector,它也是變參函數,參數類型為函數類型func(*Collector),主要是用來初始化一個&Collector{}對象。

而在Colly中有好些函數都返回這個函數類型func(*Collector),如UserAgent(us string)用來設置UA。所以,這裡其實是一種初始化對象,設置對象屬性的一種模式。相比使用方法(set方法)這種傳統方式來初始設置對象屬性,採用回調函數的形式在Go語言中更靈活更方便:

NewCollector(options ...func(*Collector)) *Collector
UserAgent(ua string) func(*Collector)

一旦得到一個colly對象,可以向colly附加各種不同類型的回調函數(回調函數在Colly中廣泛使用),來控制收集作業或獲取信息,回調函數的調用順序如下:

  1. OnRequest 在發起請求前被調用
  2. OnError 請求過程中如果發生錯誤被調用
  3. OnResponse 收到回覆後被調用
  4. OnHTML 在OnResponse之後被調用,如果收到的內容是HTML
  5. OnScraped 在OnHTML之後被調用

下面我們看一個例子:

package main
import (
	"fmt"
	"github.com/gocolly/colly"
)
func main() {
	// NewCollector(options ...func(*Collector)) *Collector
	// 聲明初始化NewCollector對象時可以指定Agent,連接遞歸深度,URL過濾以及domain限制等
	c := colly.NewCollector(
		//colly.AllowedDomains("news.baidu.com"),
		colly.UserAgent("Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50"))
	// 發出請求時附的回調
	c.OnRequest(func(r *colly.Request) {
		// Request頭部設定
		r.Headers.Set("Host", "baidu.com")
		r.Headers.Set("Connection", "keep-alive")
		r.Headers.Set("Accept", "*/*")
		r.Headers.Set("Origin", "")
		r.Headers.Set("Referer", "http://www.baidu.com")
		r.Headers.Set("Accept-Encoding", "gzip, deflate")
		r.Headers.Set("Accept-Language", "zh-CN, zh;q=0.9")
		fmt.Println("Visiting", r.URL)
	})
	// 對響應的HTML元素處理
	c.OnHTML("title", func(e *colly.HTMLElement) {
		//e.Request.Visit(e.Attr("href"))
		fmt.Println("title:", e.Text)
	})
	c.OnHTML("body", func(e *colly.HTMLElement) {
		// 

下所有的a解析 e.ForEach(".hotnews a", func(i int, el *colly.HTMLElement) { band := el.Attr("href") title := el.Text fmt.Printf("新聞 %d : %s - %s\n", i, title, band) // e.Request.Visit(band) }) }) // 發現並訪問下一個連接 //c.OnHTML(`.next a[href]`, func(e *colly.HTMLElement) { // e.Request.Visit(e.Attr("href")) //}) // extract status code c.OnResponse(func(r *colly.Response) { fmt.Println("response received", r.StatusCode) // 設置context // fmt.Println(r.Ctx.Get("url")) }) // 對visit的線程數做限制,visit可以同時運行多個 c.Limit(&colly.LimitRule{ Parallelism: 2, //Delay: 5 * time.Second, }) c.Visit("http://news.baidu.com") }

上面代碼在開始處對Colly做了簡單的初始化,增加UserAgent和域名限制,其他的設置可根據實際情況來設置,Url過濾,抓取深度等等都可以在此設置,也可以後運行時在具體設置。

該例只是簡單說明了Colly在爬蟲抓取,調度管理方面的優勢,對此如有興趣可更深入瞭解。大家在深入學習Colly時,可自行選擇更合適的URL。

程序運行後,開始根據news.baidu.com抓取頁面結果,通過OnHTML回調函數分析首頁中的熱點新聞標題及鏈接,並可不斷地抓取更深層次的新鏈接進行訪問,每個鏈接的訪問結果我們可以通過OnHTML來進行分析,也可通過OnResponse來進行處理,例子中沒有進一步展示深層鏈接的內容,有興趣的朋友可以繼續進一步研究。

我們來看看OnHTML這個方法的定義:

func (c *Collector) OnHTML(goquerySelector string, f HTMLCallback)

直接在參數中標明瞭 goquerySelector ,上例中我們有簡單嘗試。這和我們下面要介紹的goquery HTML解析框架有一定聯繫,我們也可以使用goquery,通過goquery 來更輕鬆分析HTML代碼。

goquery HTML解析

Colly框架可以快速發起請求,接收服務器響應。但如果我們需要分析返回的HTML代碼,這時候僅僅使用Colly就有點吃力。而goquery庫是一個使用Go語言寫成的HTML解析庫,功能更加強大。

goquery將jQuery的語法和特性引入進來,所以可以更靈活地選擇採集內容的數據項,就像jQuery那樣的方式來操作DOM文檔,使用起來非常的簡便。

goquery主要的結構:

type Document struct {
	*Selection
	Url *url.URL
	rootNode *html.Node
}

Document 嵌入了Selection 類型,因此,Document 可以直接使用 Selection 類型的方法。我們可以通過下面四種方式來初始化得到*Document對象。

func NewDocumentFromNode(root *html.Node) *Document 
func NewDocument(url string) (*Document, error) 
func NewDocumentFromReader(r io.Reader) (*Document, error) 
func NewDocumentFromResponse(res *http.Response) (*Document, error)

Selection 是重要的一個結構體,解析中最重要,最核心的方法方法都由它提供。

type Selection struct {
	Nodes []*html.Node
	document *Document
	prevSel *Selection
}

下面我們開始瞭解下怎麼使用goquery:

首先,要確定已經下載安裝這個第三方包:

go get github.com/PuerkitoBio/goquery

接下來在代碼中導入包:

import "github.com/PuerkitoBio/goquery"

goquery的主要用法是選擇器,需要借鑑jQuery的特性,多加練習就能很快掌握。限於篇幅,這裡只能簡單介紹了goquery的大概情況。

goquery可以直接發送url請求,獲得響應後得到HTML代碼。但goquery主要擅長於HTML代碼分析,而Colly在爬蟲抓取管理調度上有優勢,所以下面以Colly作為爬蟲框架,goquery作為HTML分析器,看看怎麼抓取並分析頁面內容:

package main
import (
	"bytes"
	"fmt"
	"log"
	"net/url"
	"time"
	"github.com/PuerkitoBio/goquery"
	"github.com/gocolly/colly"
)
func main() {
	urlstr := "https://news.baidu.com"
	u, err := url.Parse(urlstr)
	if err != nil {
		log.Fatal(err)
	}
	c := colly.NewCollector()
	// 超時設定
	c.SetRequestTimeout(100 * time.Second)
	// 指定Agent信息
	c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36"
	c.OnRequest(func(r *colly.Request) {
		// Request頭部設定
		r.Headers.Set("Host", u.Host)
		r.Headers.Set("Connection", "keep-alive")
		r.Headers.Set("Accept", "*/*")
		r.Headers.Set("Origin", u.Host)
		r.Headers.Set("Referer", urlstr)
		r.Headers.Set("Accept-Encoding", "gzip, deflate")
		r.Headers.Set("Accept-Language", "zh-CN, zh;q=0.9")
	})
	c.OnHTML("title", func(e *colly.HTMLElement) {
		fmt.Println("title:", e.Text)
	})
	c.OnResponse(func(resp *colly.Response) {
		fmt.Println("response received", resp.StatusCode)
		// goquery直接讀取resp.Body的內容
		htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Body))
		// 讀取url再傳給goquery,訪問url讀取內容,此處不建議使用
		// htmlDoc, err := goquery.NewDocument(resp.Request.URL.String())
		if err != nil {
			log.Fatal(err)
		}
		// 找到抓取項 

下所有的a解析 htmlDoc.Find(".hotnews a").Each(func(i int, s *goquery.Selection) { band, _ := s.Attr("href") title := s.Text() fmt.Printf("熱點新聞 %d: %s - %s\n", i, title, band) c.Visit(band) }) }) c.OnError(func(resp *colly.Response, errHttp error) { err = errHttp }) err = c.Visit(urlstr) }

上面代碼中,goquery先通過 goquery.NewDocumentFromReader生成文檔對象htmlDoc。有了htmlDoc就可以使用選擇器,而選擇器的目的主要是定位:htmlDoc.Find(".hotnews a").Each(func(i int, s *goquery.Selection),找到文檔中的

有關選擇器Find()方法的使用語法,是不是有些熟悉的感覺,沒錯就是jQuery的樣子。

在goquery中,常用大概有以下選擇器:

  • HTML Element 元素的選擇器Find("a")
  • Element ID 選擇器 Find(element#id)
  • Class選擇器 Find(".class")
屬性選擇器 
Find(“div[lang]“) 篩選含有lang屬性的div元素 
Find(“div[lang=zh]“) 篩選lang屬性為zh的div元素 
Find(“div[lang!=zh]“) 篩選lang屬性不等於zh的div元素 
Find(“div[lang¦=zh]“) 篩選lang屬性為zh或者zh-開頭的div元素 
Find(“div[lang*=zh]“) 篩選lang屬性包含zh這個字符串的div元素 
Find(“div[lang~=zh]“) 篩選lang屬性包含zh這個單詞的div元素,單詞以空格分開的 
Find(“div[lang$=zh]“) 篩選lang屬性以zh結尾的div元素,區分大小寫 
Find(“div[lang^=zh]“)篩選lang屬性以zh開頭的div元素,區分大小寫

parent>child選擇器 如果我們想篩選出某個元素下符合條件的子元素,我們就可以使用子元素篩選器,它的語法為Find("parent>child"),表示篩選parent這個父元素下,符合child這個條件的最直接(一級)的子元素。

prev+next相鄰選擇器 假設我們要篩選的元素沒有規律,但是該元素的上一個元素有規律,我們就可以使用這種下一個相鄰選擇器來進行選擇。

prev~next選擇器 有相鄰就有兄弟,兄弟選擇器就不一定要求相鄰了,只要他們共有一個父元素就可以。

Colly + goquery 是抓取網絡內容的利器,使用上極其方便。如今動態渲染的頁面越來越多,爬蟲們或多或少都需要用到headless browser來渲染待爬取的頁面,這裡推薦chromedp,開源網址:https://github.com/chromedp/chromedp

收藏

舉報

神奇探馬Loading...


分享到:


相關文章: