用golang寫爬蟲


我是在windows系統上安裝的go,使用goland編輯。

Hello world:

package main

import "fmt"

func main() {
	fmt.Println("Hello, world")
}

ctrl+alt+f10運行


下載網頁

這里先從Golang原生http庫開始,直接使用 net/http 包內的函數請求

import "net/http"
...
resp, err := http.Get("http://wwww.baidu.com")

所以代碼可以這樣寫

package main import ( "fmt" "io/ioutil" "net/http" ) func main() { fmt.Println("Hello, world") resp, err := http.Get("http://www.baidu.com/") if err != nil { fmt.Println("http get error", err) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("read error", err) return } fmt.Println(string(body)) }

Golang的錯誤處理就是這樣的,習慣就好。

這里更好的做法是把下載方法封裝為函數。

package main import ( "fmt" "io/ioutil" "net/http" ) func main() { fmt.Println("Hello, world") url := "http://www.baidu.com/" download(url) } func download(urlstring) { client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定義Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { fmt.Println("http get error", err) return } //函數結束后關閉相關鏈接 defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("read error", err) return } fmt.Println(string(body)) }


解析網頁

go常見的解析器xpath、 jquery 、正則都有,直接搜索即可,我這里偷懶,直接用別人寫好的輪子 collectlinks ,可以提取網頁中所有的鏈接,下載方法 go get -u github.com/jackdanger/collectlinks

package main import ( "fmt" "github.com/jackdanger/collectlinks" "net/http" ) func main() { fmt.Println("Hello, world") url := "http://www.baidu.com/" download(url) } func download(urlstring) { client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定義Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { fmt.Println("http get error", err) return } //函數結束后關閉相關鏈接 defer resp.Body.Close() links := collectlinks.All(resp.Body) for _, link := range links { fmt.Println("parse url", link) } }



並發

Golang使用關鍵字 go 即可開啟一個新的 go 程,也叫 goroutine ,使用 go 語句開啟一個新的 goroutine 之后,go 語句之后的函數調用將在新的 goroutine 中執行,而不會阻塞當前的程序執行。所以使用Golang可以很容易寫成異步IO。

package main import ( "fmt" "github.com/jackdanger/collectlinks" "net/http" ) func main() { fmt.Println("Hello, world") url := "http://www.baidu.com/" queue := make(chan string) go func() { queue <- url }() for uri := range queue { download(uri, queue) } } func download(urlstring, queuechan string) { client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定義Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { fmt.Println("http get error", err) return } //函數結束后關閉相關鏈接 defer resp.Body.Close() links := collectlinks.All(resp.Body) for _, link := range links { fmt.Println("parse url", link) go func() { queue <- link }() } }
 

現在的流程是main有一個for循環讀取來自名為queue的通道,download下載網頁和鏈接解析,將發現的鏈接放入main使用的同一隊列中,並再開啟一個新的goroutine去抓取形成無限循環。

這里對於新手來說真的不好理解,涉及到Golang的兩個比較重要的東西:goroutine和channels,這個我也不大懂,這里也不多講了,以后有機會細說。

官方:A goroutine is a lightweight thread managed by the Go runtime.翻譯過來就是:Goroutine是由Go運行時管理的輕量級線程。channels是連接並發goroutine的管道,可以理解為goroutine通信的管道。 可以將值從一個goroutine發送到通道,並將這些值接收到另一個goroutine中。對這部分有興趣的可以去看文檔。

好了,到這里爬蟲基本上已經完成了,但是還有兩個問題:去重、鏈接是否有效。

 

鏈接轉為絕對路徑

 
package main import ( "fmt" "github.com/jackdanger/collectlinks" "net/http" "net/url" ) func main() { fmt.Println("Hello, world") url := "http://www.baidu.com/" queue := make(chan string) go func() { queue <- url }() for uri := range queue { download(uri, queue) } } func download(urlstring, queuechan string) { client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定義Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { fmt.Println("http get error", err) return } //函數結束后關閉相關鏈接 defer resp.Body.Close() links := collectlinks.All(resp.Body) for _, link := range links { absolute := urlJoin(link, url) if url != " " { fmt.Println("parse url", absolute) go func() { queue <- absolute }() } } } func urlJoin(href, basestring)string { uri, err := url.Parse(href) if err != nil { return " " } baseUrl, err := url.Parse(base) if err != nil { return " " } return baseUrl.ResolveReference(uri).String() }

這里新寫了一個 urlJoin 函數,功能和 Python 中的 urllib.parse.urljoin 一樣。

 

去重

 

我們維護一個map用來記錄,那些是已經訪問過的。

package main import ( "fmt" "github.com/jackdanger/collectlinks" "net/http" "net/url" ) var visited = make(map[string]bool) func main() { fmt.Println("Hello, world") url := "http://www.baidu.com/" queue := make(chan string) go func() { queue <- url }() for uri := range queue { download(uri, queue) } } func download(urlstring, queuechan string) { visited[url] = true client := &http.Client{} req, _ := http.NewRequest("GET", url, nil) // 自定義Header req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") resp, err := client.Do(req) if err != nil { fmt.Println("http get error", err) return } //函數結束后關閉相關鏈接 defer resp.Body.Close() links := collectlinks.All(resp.Body) for _, link := range links { absolute := urlJoin(link, url) if url != " " { if !visited[absolute] { fmt.Println("parse url", absolute) go func() { queue <- absolute }() } } } } func urlJoin(href, basestring)string { uri, err := url.Parse(href) if err != nil { return " " } baseUrl, err := url.Parse(base) if err != nil { return " " } return baseUrl.ResolveReference(uri).String() }

好了大功告成,運行程序,會像一張網鋪開一直不停的抓下去。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM