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中廣泛使用),來控制收集作業或獲取信息,回調函數的調用順序如下:
- OnRequest 在發起請求前被調用
- OnError 請求過程中如果發生錯誤被調用
- OnResponse 收到回覆後被調用
- OnHTML 在OnResponse之後被調用,如果收到的內容是HTML
- 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...