實現一個go語言的簡單爬蟲來爬取CSDN博文(一)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

如何?一個爬蟲系統或則簡單的小指令碼?一般是定義一個入口頁面,然後一個頁面會有其他頁面的URL,於是從當前頁面擷取到這些URL加入到爬蟲的抓取隊列中,然後進入到新頁面後再遞迴的進行上述的操作,其實說來就跟深度遍曆或廣度遍曆一樣。
golang由於其編譯速度很快,而且對並發(goroutine)的天然支援,配合chan的協程處理,可以很好地實現一個穩定高效的爬蟲系統.

用到的包

完全不藉助第三方的架構,通過go sdk的標準庫來實現一個爬蟲應用,主要用到的包

  • net/http 標準庫裡內建了對http協議的支援,實現了一個http client,可以直接通過其進行get,post等請求
  • strings 不像java的String是一個參考型別,go語言中的字串類型是一個內建的基礎類型, 而且go語言預設只支援UTF-8編碼,strings包實現了一些簡單的針對utf-8字串操作的函數
  • regexp go sdk中的Regex包
  • io/ioutil io處理的工具包
  • encoding/xml 解析xml的包

channel機制

Golang在並發設計方面參考了C.A.R Hoare的CSP,即Communicating Sequential Processes並行存取模型理論。 CSP模型的訊息傳遞在收發訊息進程間包含了一個交會點,即發送方只能在接收方準備好接收訊息時才能發送訊息。
golang在其並發實現中,主要是用channel來實現通訊的。其中channel包括兩種,緩衝的channel和非緩衝的channel.

  • 緩衝的channel:保證往緩衝中存資料先於對應的取資料,簡單說就是在取的時候裡面肯定有資料,否則就因取不到而阻塞.
  • 非緩衝的channel:保證取資料先於存資料,就是保證存的時候肯定有其他的goroutine在取,否則就因放不進去而阻塞。

Go Channel基本操作文法

Go Channel的基本操作文法如下:

c := make(chan bool) //建立一個無緩衝的bool型Channel
c <- x        //向一個Channel發送一個值<- c          //從一個Channel中接收一個值x = <- c      //從Channel c接收一個值並將其儲存到x中x, ok = <- c  //從Channel接收一個值,如果channel關閉了或沒有資料,那麼ok將被置為false

不帶緩衝的Channel兼具通訊和同步兩種特性,適合協調多個routines。

for/select的基本操作

我們在使用select時很少只是對其進行一次evaluation,我們常常將其與for {}結合在一起使用,並選擇適當時機從for{}中退出。

for {        select {        case x := <- somechan:            // … 使用x進行一些操作        case y, ok := <- someOtherchan:            // … 使用y進行一些操作,            // 檢查ok值判斷someOtherchan是否已經關閉        case outputChan <- z:            // … z值被成功發送到Channel上時        default:            // … 上面case均無法通訊時,執行此分支        }} 

range操作

Golang中的for range除了可以迭代一些集合類型還可以來迴圈從channel中取資料,當channel中無資料時便阻塞當前迴圈。

for url := range urlChannel {        fmt.Println("routines num = ", runtime.NumGoroutine(), "chan len = ", len(urlChannel))        go Spy(url)}

goroutine

Go語言通過goroutine提供了對於並發編程的非常清晰直接的支援,但goroutine是Go語言運行庫的功能,不是作業系統提供的功能,goroutine不是用線程實現的.goroutine就是一段代碼,一個函數入口,以及在堆上為其分配的一個堆棧。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所調度執行

除了被系統調用阻塞的線程外,Go運行庫最多會啟動$GOMAXPROCS個線程來運行goroutine

實現CSDN博文爬蟲

由於實現的爬蟲功能簡單,所以所有代碼均再main包下完成.
首先我們需要在main包下聲明一個全域的urlchannel用來同步開啟的多個routines在某個頁面擷取的<a> 標籤的href屬性

var urlChannel = make(chan string, 200) //chan中存入string類型的href屬性,緩衝200

聲明在html文檔中擷取<a> 的Regex

var atagRegExp = regexp.MustCompile(`<a[^>]+[(href)|(HREF)]\s*\t*\n*=\s*\t*\n*[(".+")|('.+')][^>]*>[^<]*</a>`) //以Must首碼的方法或函數都是必須保證一定能執行成功的,否則將引發一次panic

入口函數main

當進入main函數時,將啟動一個goroutine來從入口url=”http:/blog.csdn.net”開始爬取(Spy函數)頁面內容分析<a> 標籤
接下來通過for range urlChannel來迴圈取出爬取到的<a> 標籤中的href屬性,並再次開啟一個新的goroutine來爬取這個href屬性對應的html文檔內容

func main() {    go Spy("http:/blog.csdn.net")    //go Spy("http://www.iteye.com/")    for url := range urlChannel {        fmt.Println("routines num = ", runtime.NumGoroutine(), "chan len = ", len(urlChannel)) //通過runtime可以擷取當前運行時的一些相關參數等        go Spy(url)    }    fmt.Println("a")}

Spy函數

由於每個爬取goroutine都是調用Spy函數來分析一個url對應的html文檔,所以需要在函數開始就defer 一個匿名函數來處理(recover)可能出現的異常(panic),防止異常導致程式終止,defer執行的函數會在當前函數執行完成後結果返回前執行,無論該函數是panic的還是正常執行

    defer func() {        if r := recover(); r != nil {            log.Println("[E]", r)        }    }()

由於go內建了對http協議的支援,可以直接通過http包下的http.Get或則http.Post函數來請求url.但由於大部分網站對請求都有防範DDOS等的限制,需要自訂請求的header,設定Proxy 伺服器(CSDN好像對同一IP的請求平率限制並不嚴格,iteye親測很嚴格,每分鐘上萬會被封住IP)等操作,可以使用http包下的http.NewRequest(method, urlStr string, body io.Reader) (*Request, error)函數,然後通過Request的Header對象設定User-Agent,Host等,最後調用http包下內建的DefaultClient對象的Do方法完成請求.
當拿到伺服器響應後(*Response)通過ioutil包下的工具函數轉換為string,找出文檔中的<a>標籤 分析出href屬性,存入urlChannel中.

func Spy(url string) {    defer func() {        if r := recover(); r != nil {            log.Println("[E]", r)        }    }()    req, _ := http.NewRequest("GET", url, nil)    req.Header.Set("User-Agent", GetRandomUserAgent())    client := http.DefaultClient    res, e := client.Do(req)    if e != nil {        fmt.Errorf("Get請求%s返回錯誤:%s", url, e)        return    }    if res.StatusCode == 200 {        body := res.Body        defer body.Close()        bodyByte, _ := ioutil.ReadAll(body)        resStr := string(bodyByte)        atag := atagRegExp.FindAllString(resStr, -1)        for _, a := range atag {            href,_ := GetHref(a)            if strings.Contains(href, "article/details/") {                fmt.Println("☆", href)            }else {                fmt.Println("□", href)            }            urlChannel <- href        }    }}

隨機偽造User-Agent

var userAgent = [...]string{"Mozilla/5.0 (compatible, MSIE 10.0, Windows NT, DigExt)",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, 360SE)",    "Mozilla/4.0 (compatible, MSIE 8.0, Windows NT 6.0, Trident/4.0)",    "Mozilla/5.0 (compatible, MSIE 9.0, Windows NT 6.1, Trident/5.0,",    "Opera/9.80 (Windows NT 6.1, U, en) Presto/2.8.131 Version/11.11",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, TencentTraveler 4.0)",    "Mozilla/5.0 (Windows, U, Windows NT 6.1, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",    "Mozilla/5.0 (Macintosh, Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",    "Mozilla/5.0 (Macintosh, U, Intel Mac OS X 10_6_8, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",    "Mozilla/5.0 (Linux, U, Android 3.0, en-us, Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",    "Mozilla/5.0 (iPad, U, CPU OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, Trident/4.0, SE 2.X MetaSr 1.0, SE 2.X MetaSr 1.0, .NET CLR 2.0.50727, SE 2.X MetaSr 1.0)",    "Mozilla/5.0 (iPhone, U, CPU iPhone OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",    "MQQBrowser/26 Mozilla/5.0 (Linux, U, Android 2.3.7, zh-cn, MB200 Build/GRJ22, CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"}var r = rand.New(rand.NewSource(time.Now().UnixNano()))func GetRandomUserAgent() string {    return userAgent[r.Intn(len(userAgent))]}

解析<a> 元素

<a> 可以當做一份xml文檔(只有一個a為根節點的簡單xml)來解析出href/HREF屬性,通過go標準庫中xml.NewDecoder來完成

func GetHref(atag string) (href,content string) {    inputReader := strings.NewReader(atag)    decoder := xml.NewDecoder(inputReader)    for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {        switch token := t.(type) {        // 處理元素開始(標籤)        case xml.StartElement:            for _, attr := range token.Attr {                attrName := attr.Name.Local                attrValue := attr.Value                if(strings.EqualFold(attrName,"href") || strings.EqualFold(attrName,"HREF")){                    href = attrValue                }            }        // 處理元素結束(標籤)        case xml.EndElement:        // 處理字元資料(這裡就是元素的文本)        case xml.CharData:            content = string([]byte(token))        default:            href = ""            content = ""        }    }    return href, content}

總結

通過以上代碼,一個簡單的網路爬蟲就實現了.而且對goroutine和range的配合使用基本就瞭解了.但如你所見,goroutine的運行機制和chan的設計原理絕非以上寥寥數句代碼就可窺見其真面目.
go語言實現的簡單爬蟲來爬取CSDN博文

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.