colly + otto 爬蟲解析網頁內容
關於colly
colly 是 Go 官方套件裡的一個爬蟲工具,具備完整且豐富的方法提供開發者以簡易的方式來進行爬蟲相關的應用,在其官方文件裡也針對各個方法、型別提供了非常詳細的詳細說明。
安裝
在 module 中進行安裝
$ go get github.com/gocolly/colly/v2
建立基礎架構
colly 的基礎運用方式是透過初始化一個 NewCollector 物件,就可以進行後續的操作。而對於請求和回應兩個部分,則可以分別使用 OnRequest 和 OnResponse 兩個方法來處理,整體架構上區隔得非常清楚,最後再使用 Vist 方法即可對目標網址發起請求。
package main import ( "fmt" "github.com/gocolly/colly/v2" ) func main() { cly := colly.NewCollector() cly.OnRequest(func(req *colly.Request) { // OnRequest 處理在發送請求前做的事情 // e.g. 設定 Headers 參數等 fmt.Println("set some parameters") }) cly.OnResponse(func(res *colly.Response) { // OnResponse 處理在接收到回應後做的事情 fmt.Println("do something") }) if err:= cly.Visit(url); err != nil { fmt.Println("Error: ", err) } // Visit 放在最後 }
HTML 元素解析
元素擷取
若是要進一步針對 HTML 內部的元素進行剖析,可以利用 OnHTML 這個函式,並搭配 CSS selector 進行元素的篩選擷取,要注意的是 OnHTML 本身以迴圈的方式反覆進行,若有多個符合條件的目標會持續地進行解析直到找不到為止。
package main import ( "fmt" "github.com/gocolly/colly/v2" ) func main() { cly := colly.NewCollector() cly.OnRequest(func(req *colly.Request) { // OnRequest 處理在發送請求前做的事情 // e.g. 設定 Headers 參數等 fmt.Println("set some parameters") }) // 此處的 selector 和 CSS 的規則相同 cly.OnHTML("your-selector", func(res *colly.HTMLElement) { fmt.Println("find element: ", res) }) if err:= cly.Visit(url); err!= nil { fmt.Println("Error: ", err) } // Visit 放在最後 }
因此如果想要找到特定的元素,除了使用更精準的 selector 來搜尋之外,在不得已的情況下也可以自定義計數器來找到指定位置的元素。
count := 0 targetPosition := 2 // 想找到第二個符合條件的元素 cly.OnHTML("your-selector", func(res *colly.HTMLElement) { count++ // 每找到一個就加在記數器上 if count == targetPosition { fmt.Println("find target element: ", res) } })
傳出結果
在 OnHTML 函式裡面處理所得到的內容是沒有辦法直接回傳出來的,我們可以利用在外面宣告變數並在裡面賦值的做法進行操作,不過若是要考慮到併發可能產生的問題的話,會推薦使用 channel 的性質來儲存找到的結果,在結束之後於 OnHTML 函式外進行調用。
package main import ( "fmt" "github.com/gocolly/colly/v2" ) func main() { cly := colly.NewCollector() resChan := make(chan string) cly.OnRequest(func(req *colly.Request) { // OnRequest 處理在發送請求前做的事情 // e.g. 設定 Headers 參數等 fmt.Println("set some parameters") }) // 此處的 selector 和 CSS 的規則相同 cly.OnHTML("your-selector", func(res *colly.HTMLElement) { resChan <- res.Text }) if err:= cly.Visit(url); err!= nil { fmt.Println("Error: ", err) } // Visit 放在最後 close(resChan) for value := resChan{ fmt.Println(value) } }
關於 otto
一般我們在解析網頁時主要是針對 body 內部的元素、文字內容或是附帶的 attribute、link 或 data-* 等資訊進行查找,不過也有少部分時候需要取得文本中 script 的內容。
利用 colly 我們一樣可以使用 selector 的方式來取得相對應的 script,但 colly 並沒有辦法直接將解析到的內容轉換成 JavaScript 的程式碼,這時候我們可以使用另一個套件 otto 來協助。
otto 是一個可以在 Go 裡面解析並支援 JavaScript 語法的套件,透過它我們可以在的程式中將 JavaScript 程式碼字串進行解析並執行,詳細的說明可以參考官方文件。
使用方式
在使用方式上,可以透過 otto.New() 來創造新的物件以進行後續的操作,並呼叫 Run() 這個方法來執行指定的 JavaScript code (內部的參數型別未必要是 string)。
scriptText = ` const x = 1 + 2 const y = "Hello" console.log(y.length) ` vm := otto.New() vm.Run(scriptText)
若想要取出該段程式中的變數或其他物件,可以使用 Get() 函式:
value, err := vm.Get("x") if err != nil { // handle error }
這邊所得到的 value 的型別為 otto.Value ,而針對不同型別的變數,otto 也提供了對應的方法來轉換:
result, err := ToString(value) result, err := ToInteger(value) result, err := ToFloat(value) result, err := ToBoolean(value) result, err := MarshalJSON(value) if err != nil { // handle error }