GCrawler爬虫框架
切记:本框架仅适用于学习目的,不具有生产级代码质量。
一、介绍(废话)
什么是爬虫框架?
爬虫框架是指为了简化和加速网络爬虫(Web Crawler)开发而设计的一种软件框架或库。网络爬虫是一类用于自动抓取互联网上信息的程序,常用于数据采集、搜索引擎建设、监测和分析等领域。
为什么需要爬虫框架?
简单来说,框架提供了一系列已经封装好的功能和工具,使开发者可以更专注于业务逻辑和数据处理,而不必过多关注底层网络请求和数据解析的细节,从而更快地构建爬虫程序。
为什么要从0自己实现?
没什么,就是玩。并且想要自己更方便的定制一些需要的功能。
GCrawler是什么?
如上,是一个简单的爬虫框架(极大程度上参考/抄了Crawlee框架),仅仅是为了简化个人开发。
二、功能
数据抓取(不支持JS处理)
只需要调用AddReq或AddReqs,框架会自动将爬取任务加入内部队列。可以通过WithLabel来指定该请求的处理器,未指定则默认采用default处理器。
s.AddReq("https://cs.mnnu.edu.cn/xwgg.htm").WithLabel("default")
// 或
s.AddReqs([]string{
"https://a.com",
"https://b.com",
"https://c.com",
})
自定义处理器
通过SetDefaultHandler可以指定请求的默认处理器,如果请求未指定处理器,则会默认使用此处理器。
s.SetDefaultHandler(func(s *spider.Spider, ctx *spider.TaskContext, d *goquery.Document) (interface{}, error) {
log.Info().Msgf("default callback: %s", ctx.GetUrl())
d.Find(`tr[id^='line_u7_'] table a`).Each(func(i int, doc *goquery.Selection) {
t := doc.Parent().Parent().Find("td").Last().Text()
link := doc.AttrOr("href", "")
s.AddReq(ctx.MergeUrl(link)).WithLabel("detail").WithExtras(map[string]interface{}{
"time": t,
})
})
return nil, nil
})
通过AddHandler可以添加特定的命名处理器。
s.AddHandler("detail", func(s *spider.Spider, ctx *spider.TaskContext, d *goquery.Document) (interface{}, error) {
log.Info().Msgf("detail callback: %s", ctx.GetUrl())
data := make(map[string]interface{})
data["title"] = d.Find(".Article_Title").Text()
h, _ := d.Find("div[id^=vsb_content]").Html()
data["content"] = h
data["time"] = ctx.GetExtra("time")
return data, nil
})
无限分页抓取
在需要处理分页的处理器中,添加如下代码即可。
s.AutoFollowPaginationLinks(ctx, "span[class^='p_no'] a", "href")
数据管道
在处理器中,如果第一个返回值不为nil,则会将其作为数据,丢到持久化处理器链中。并且,如果持久化处理器的第一个返回值不为nil,则会继续向下一个持久化处理器传递。
s.AddPersistHandler(func(s *spider.Spider, ctx *spider.TaskContext, items interface{}) (interface{}, error) {
o, err := sonic.Marshal(&items)
if err != nil {
return nil, err
}
log.Info().Msgf("persist callback: %s", string(o))
return items, nil
})
s.AddPersistHandler(func(s *spider.Spider, ctx *spider.TaskContext, items interface{}) (interface{}, error) {
// 持久化到数据库等操作
return nil, nil
})
图片下载
可以指定图片保存路径。
s.SetDefaultImgPath("/temp")
简单调用如下代码,即可抓取指定Document下的所有图片(这是我的需求,不需要精细化控制)。
s.ProcessImageLinks(ctx, d.Find("div[id^=vsb_content]"))
每一个图片下载成功后会执行imageDownloadCallback,可以通过SetImageDownloadCallback指定。这个回调用来进行图片链接的处理,图片下载下来后,可以在此上传到oss,拿到新的图片资源链接。第一个返回值指定新的资源链接,第二个是否执行替换(替换原图片链接)。
s.SetImageDownloadCallback(func(spider *Spider, context *TaskContext, old string, new string) (string, bool, error) {
return new, true, nil
})
执行报告
通过如下代码获取报告。
report := s.GenerateReport()
println(report)
三、完整示例
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
retryOptions := spider.RetryOptions{
MaxRetries: 5,
BackoffFactor: 5 * time.Second,
Timeout: time.Second * 10,
}
s := spider.NewSpider(retryOptions, 30)
s.SetDefaultImgPath("/img")
s.SetDefaultHandler(func(s *spider.Spider, ctx *spider.TaskContext, d *goquery.Document) (interface{}, error) {
log.Info().Msgf("default callback: %s", ctx.GetUrl())
d.Find(`tr[id^='line_u7_'] table a`).Each(func(i int, doc *goquery.Selection) {
t := doc.Parent().Parent().Find("td").Last().Text()
link := doc.AttrOr("href", "")
s.AddReq(ctx.MergeUrl(link)).WithLabel("detail").WithExtras(map[string]interface{}{
"time": t,
})
})
s.AutoFollowPaginationLinks(ctx, "span[class^='p_no'] a", "href")
return nil, nil
})
s.AddHandler("detail", func(s *spider.Spider, ctx *spider.TaskContext, d *goquery.Document) (interface{}, error) {
log.Info().Msgf("detail callback: %s", ctx.GetUrl())
s.ProcessImageLinks(ctx, d.Find("div[id^=vsb_content]"))
data := make(map[string]interface{})
data["title"] = d.Find(".Article_Title").Text()
h, _ := d.Find("div[id^=vsb_content]").Html()
data["content"] = h
data["time"] = ctx.GetExtra("time")
return data, nil
})
s.AddPersistHandler(func(s *spider.Spider, ctx *spider.TaskContext, items interface{}) (interface{}, error) {
o, err := sonic.Marshal(&items)
if err != nil {
return nil, err
}
log.Info().Msgf("persist callback: %s", string(o))
return items, nil
})
s.AddPersistHandler(func(s *spider.Spider, ctx *spider.TaskContext, items interface{}) (interface{}, error) {
// 数据持久化
return nil, nil
})
s.AddReqs([]string{
"https://example.com/a.htm",
"https://example.com/b.htm",
"https://example.com/c.htm",
})
s.Start()
report := s.GenerateReport()
println(report)
}
四、总结
功能简单,但满足我需求了。后面的篇章会从需求出发,一点点实现这个简单的框架,大约300行代码左右。 Github参考:PLin2023/gcrawler (github.com)