3行写爬虫 - 使用 Goribot 快速构建 Golang 爬虫
source link: https://www.tuicool.com/articles/iqqEru3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
zhshch2002/goribot: [Crawler/Scraper for Golang]Make a Golang spider in 3 lines 是我的一个业余项目,目的是能尽可能简洁的使用Golang开发爬虫应用。
注意:这个项目正处于beta版本,不建议直接使用在重要项目上。Goribot的功能都经过测试,如果有问题欢迎来提issues。
安装
go get -u github.com/zhshch2002/goribot
访问网络
不需要冗长的初始化和配置过程,使用 goribot
的基本功能只需要三步。
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
执行的基本单位是 Task
, Task
是一个回调函数和请求参数的包装。 s.NewTask()
创建了一个 Task
并作为种子地址添加到任务队列里。
type Task struct { Request *Request onRespHandlers []func(ctx *Context) Meta map[string]interface{} }
Spider
有一个 ThreadPoolSize
参数,大意是 Spider
会根据创建一个虚拟线程池,也就是维护 ThreadPoolSize
个 goroutine
。
每个 goroutine
都会从创建开始依次执行 获取新的 Task
->发送网络请求并获取 Response
->顺序执行 Task
里的回调函数(也就是 onRespHandlers
)->收集Context中新的 Task
和 Item
->结束。
关于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
,如果成功的话就可以通过 Html
和 Json
参数获取到。
像之前使用 spider.NewTask()
向蜘蛛任务队列添加新任务,在回调函数里应该使用 ctx.NewTask()
创建新的任务。蜘蛛会在所有回调函数执行结束后将 ctx
里保存的新任务收集起来添加到队列里。
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.NewTaskWithMeta
和 ctx.NewTaskWithMeta
,由此可以设置创建的 Task
的 Meta
数据,即一个 map[string]interface{}
字典。之后在任务执行过程中创建的 Context
也会携带这个 Meta
参数,以此作为新老 Task
之间的数据传递。
Context
的 Meta
参数同时可以用作数个回调函数和钩子函数之间的数据传递。
钩子函数 与 扩展插件
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 - 仰望星空 如若信息变动请以链接内版本为准。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK