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 - 仰望星空 如若信息变更请以连接内版本为准。

相关文章
相关标签/搜索