3行寫爬蟲 - 使用 Goribot 快速構建 Golang 爬蟲

zhshch2002/goribot: [Crawler/Scraper for Golang]Make a Golang spider in 3 lines是個人一個業餘項目,目的是能儘量簡潔的使用Golang開發爬蟲應用。html

注意:這個項目正處於beta版本,不建議直接使用在重要項目上。Goribot的功能都通過測試,若是有問題歡迎來提issues。git

安裝

go get -u github.com/zhshch2002/goribot
複製代碼

訪問網絡

不須要冗長的初始化和配置過程,使用goribot的基本功能只須要三步。github

package main

import (
    "fmt"
    "github.com/zhshch2002/goribot"
)

func main() {
    s := goribot.NewSpider() // 1 建立蜘蛛
    s.NewTask( // 2 添加任務
        goribot.MustNewGetReq("https://httpbin.org/get?hello=world"),
        func(ctx *goribot.Context) {
            fmt.Println("got resp data", ctx.Text)
        })
    s.Run() // 3 運行
}
複製代碼

goribot執行的基本單位是TaskTask是一個回調函數和請求參數的包裝。s.NewTask()建立了一個Task並做爲種子地址添加到任務隊列裏。golang

type Task struct {
    Request        *Request
    onRespHandlers []func(ctx *Context) Meta map[string]interface{}
}
複製代碼

Spider有一個ThreadPoolSize參數,大意是Spider會根據建立一個虛擬線程池,也就是維護ThreadPoolSizegoroutineshell

每一個goroutine都會從建立開始依次執行 獲取新的Task->發送網絡請求並獲取Response->順序執行Task裏的回調函數(也就是onRespHandlers)->收集Context中新的TaskItem->結束。json

關於Context

由剛纔的例子,回調函數收到的數據是ctx *goribot.Context,這是對網絡響應數據和一些操做的包裝。網絡

type Context struct {
    Text string                 // the response text
    Html *goquery.Document      // spider will try to parse the response as html
    Json map[string]interface{} // spider will try to parse the response as json

    Request  *Request  // origin request
    Response *Response // a response object

    Tasks []*Task                // the new request task which will send to the spider
    Items []interface{}          // the new result data which will send to the spider,use to store
    Meta  map[string]interface{} // the request task created by NewTaskWithMeta func will have a k-y pair

    drop bool // in handlers chain,you can use ctx.Drop() to break the handler chain and stop handling
}
複製代碼

在這裏蜘蛛會試着把收到的數據轉換爲字符串也就是Text屬性,以後會試着將其解析爲HTML或者JSON,若是成功的話就能夠經過HtmlJson參數獲取到。dom

像以前使用spider.NewTask()向蜘蛛任務隊列添加新任務,在回調函數裏應該使用ctx.NewTask()建立新的任務。蜘蛛會在全部回調函數執行結束後將ctx裏保存的新任務收集起來添加到隊列裏。ide

s := goribot.NewSpider()

var getNewLinkHandler func(ctx *goribot.Context) // 這樣聲明的回調函數能夠在函數內將本身做爲參數 getNewLinkHandler = func(ctx *goribot.Context) {
    ctx.Html.Find("a[href]").Each(func(i int, selection *goquery.Selection) {
        rawurl, _ := selection.Attr("href")
        u, err := ctx.Request.Url.Parse(rawurl)
        if err != nil {
            return
        }
        if r, err := goribot.NewGetReq(u.String()); err == nil {
            // 在回調函數內建立新的任務
            // 而且使用本身做爲新任務的回調函數
            ctx.NewTask(r, getNewLinkHandler)
        }
    })
}

// 種子任務
s.NewTask(goribot.MustNewGetReq("https://www.bilibili.com/video/av66703342"), getNewLinkHandler)
s.Run()
複製代碼

添加新任務時可使用spider.NewTaskWithMetactx.NewTaskWithMeta,由此能夠設置建立的TaskMeta數據,即一個map[string]interface{}字典。以後在任務執行過程當中建立的Context也會攜帶這個Meta參數,以此做爲新老Task之間的數據傳遞。函數

ContextMeta參數同時能夠用做數個回調函數和鉤子函數之間的數據傳遞。

鉤子函數 與 擴展插件

spider提供一系列鉤子函數的掛載點,能夠在一個任務執行的不一樣時間進行處理。

s := NewSpider()
s.OnTask(func(ctx *goribot.Context, k *goribot.Task) *goribot.Task { // 當有新任務提交的時候執行,能夠返回nil來拋棄任務
    fmt.Println("on task", k)
    return k
})
s.OnResp(func(ctx *goribot.Context) { // 當下載完一個請求後執行的函數,先於Task的回調函數執行
    fmt.Println("on resp")
})
s.OnItem(func(ctx *goribot.Context, i interface{}) interface{} { // 當有新結果數據提交的時候執行,用做數據的存儲(稍後講到),能夠返回nil來拋棄
    fmt.Println("on item", i)
    return i
})
s.OnError(func(ctx *goribot.Context, err error) { // 當出現下載器出現錯誤時執行
    fmt.Println("on error", err)
})
複製代碼

Tip:這些鉤子函數並不是是一個而是一列,能夠經過屢次調用上述函數來設置多個鉤子。鉤子函數的執行順序也會按照其被註冊的順序執行。

插件或者叫擴展指的是在執行s := goribot.NewSpider()時能夠傳入的一種函數參數。這個函數在建立蜘蛛時被執行,用來配置蜘蛛的參數或者增長鉤子函數。例如內建的HostFilter擴展源碼以下。

// 使用時能夠調用 s := goribot.NewSpider(HostFilter("www.bilibili.com"))
// 由此建立出的蜘蛛會自動忽略www.bilibili.com之外的連接
func HostFilter(h ...string) func(s *Spider) {
    WhiteList := map[string]struct{}{}
    for _, i := range h {
        WhiteList[i] = struct{}{}
    }
    return func(s *Spider) {
        s.OnTask(func(ctx *Context, k *Task) *Task {
            if _, ok := WhiteList[k.Request.Url.Host]; ok {
                return k
            }
            return nil
        })
    }
}
複製代碼

存儲

不建議在回調函數內存儲數據,因此ctx提供ctx.AddItem函數用於添加一些數據到ctx中保存,執行到最後spider會收集他們並調用OnItem鉤子函數。

s := goribot.NewSpider()
s.NewTask(goribot.MustNewGetReq("https://httpbin.org/"), func(ctx *goribot.Context) {
    ctx.AddItem(ctx.Text)
})

s.OnItem(func(ctx *goribot.Context, i interface{}) interface{} {
    fmt.Println("get item", i) // 在此能夠統一的對收集到的數據進行存儲
    return i
})
s.Run()
複製代碼

複雜一些的例子——嗶哩嗶哩爬蟲

這是一個用於爬取嗶哩嗶哩視頻的蜘蛛。

package main

import (
    "github.com/PuerkitoBio/goquery"
    "github.com/zhshch2002/goribot"
    "log"
    "strings"
)

type BiliVideoItem struct {
    Title, Url string
}

func main() {
    s := goribot.NewSpider(goribot.HostFilter("www.bilibili.com"), goribot.ReqDeduplicate(), goribot.RandomUserAgent())

    var biliVideoHandler, getNewLinkHandler func(ctx *goribot.Context) // 獲取新連接 getNewLinkHandler = func(ctx *goribot.Context) {
        ctx.Html.Find("a[href]").Each(func(i int, selection *goquery.Selection) {
            rawurl, _ := selection.Attr("href")
            if !strings.HasPrefix(rawurl, "/video/av") {
                return
            }
            u, err := ctx.Request.Url.Parse(rawurl)
            if err != nil {
                return
            }
            u.RawQuery = ""
            if strings.HasSuffix(u.Path, "/") {
                u.Path = u.Path[0 : len(u.Path)-1]
            }
            //log.Println(u.String())
            if r, err := goribot.NewGetReq(u.String()); err == nil {
                ctx.NewTask(r, getNewLinkHandler, biliVideoHandler)
            }
        })
    }

    // 將數據提取出來
    biliVideoHandler = func(ctx *goribot.Context) {
        ctx.AddItem(BiliVideoItem{
            Title: ctx.Html.Find("title").Text(),
            Url:   ctx.Request.Url.String(),
        })
    }

    // 抓取種子連接
    s.NewTask(goribot.MustNewGetReq("https://www.bilibili.com/video/av66703342"), getNewLinkHandler, biliVideoHandler)
    

    s.OnItem(func(ctx *goribot.Context, i interface{}) interface{} {
        log.Println(i) // 能夠作一些數據存儲工做
        return i
    })

    s.Run()
}
複製代碼

本文原始發佈於 使用 Goribot 快速構建 Golang 爬蟲 - AthorX - 仰望星空 如若信息變更請以連接內版本爲準。

相關文章
相關標籤/搜索