predator 是一款基于 fasthttp 开发的高性能爬虫框架。
以后版本尽管尚未实现全副性能,但已可应用。
应用
上面是一个示例,根本蕴含了以后已实现的所有性能,应用办法能够参考正文。
1 创立一个 Crawler
import "github.com/thep0y/predator"func main() { crawler := predator.NewCrawler( predator.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0"), predator.WithCookies(map[string]string{"JSESSIONID": cookie}), predator.WithProxy(ip), // 或者应用代理池 predator.WithProxyPool([]string) )}
创立Crawler
时有一些可选项用来性能加强。所有可选项参考predator/options.go。
2 发送 Get 申请
crawler.Get("http://www.baidu.com")
对申请和响应的解决参考的是 colly,我感觉 colly 的解决形式十分难受。
// BeforeRequest 能够在发送申请前,对申请进行一些修补crawler.BeforeRequest(func(r *predator.Request) { headers := map[string]string{ "Accept": "*/*", "Accept-Language": "zh-CN", "Accept-Encoding": "gzip, deflate", "X-Requested-With": "XMLHttpRequest", "Origin": "http://example.com", } r.SetHeaders(headers) // 申请和响应之间的上下文传递,上下文见上面的上下文示例 r.Ctx.Put("id", 10) r.Ctx.Put("name", "tom")})crawler.AfterResponse(func(r *predator.Response) { // 从申请发送的上下文中取值 id := r.Ctx.GetAny("id").(int) name := r.Ctx.Get("name") // 对于 json 响应,倡议应用 gjson 进行解决 body := gjson.ParseBytes(r.Body) amount := body.Get("amount").Int() types := body.Get("types").Array()})// 申请语句要在 BeforeRequest 和 AfterResponse 前面调用crawler.Get("http://www.baidu.com")
3 发送 Post 申请
与 Get 申请有一点不同,通常每个 Post 的申请的参数是不同的,而这些参数都在申请体中,在BeforeRequest
中解决申请体尽管能够,但绝非最佳抉择,所以在结构 Post 申请时,能够间接传入上下文,用以解决与响应的信息传递。
// BeforeRequest 能够在发送申请前,对申请进行一些修补crawler.BeforeRequest(func(r *predator.Request) { headers := map[string]string{ "Accept": "*/*", "Accept-Language": "zh-CN", "Accept-Encoding": "gzip, deflate", "X-Requested-With": "XMLHttpRequest", "Origin": "http://example.com", } r.SetHeaders(headers)})crawler.AfterResponse(func(r *predator.Response) { // 从申请发送的上下文中取值 id := r.Ctx.GetAny("id").(int) name := r.Ctx.Get("name") // 对于 json 响应,倡议应用 gjson 进行解决 body := gjson.ParseBytes(r.Body) amount := body.Get("amount").Int() types := body.Get("types").Array()})body := map[string]string{"foo": "bar"}// 在 Post 申请中,应该将要害参数用这种形式放进上下文ctx, _ := context.AcquireCtx()ctx.Put("id", 10)ctx.Put("name", "tom")crawler.Post("http://www.baidu.com", body, ctx)
如果不须要传入上下文,能够间接用nil
代替:
crawler.Post("http://www.baidu.com", body, nil)
4 发送 multipart/form-data 申请
multipart/form-data
办法须要应用专门的PostMultipart
办法,只是以后申请体只反对map[string]string
,没有别的起因,因为我只用到这种类型,如果当前有别的需要,再改成map[string]interface{}
。
参考示例:https://github.com/thep0y/pre...
5 上下文
上下文是一个接口,我实现了两种上下文:
- ReadOp:基于
sync.Map
实现,实用于读取上下文较多的场景 - WriteOp:用
map
实现,实用于读写频率相差不大或写多于读的场景,这是默认采纳的上下文
爬虫中如果遇到了读远多于写时就应该换ReadOp
了,如下代码所示:
ctx, err := AcquireCtx(context.ReadOp)
6 解决 HTML
爬虫的后果大体可分为两种,一是 HTML 响应,另一种是 JSON 格局的响应。
与 JSON 相比,HTML 须要更多的代码解决。
本框架对 HTML 解决进行了一些函数封装,能不便地通过 css selector 进行元素的查找,能够提取元素中的属性和文本等。
crawl := NewCrawler()crawl.ParseHTML("body", func(he *html.HTMLElement) { // 元素外部 HTML h, err := he.InnerHTML() // 元素整体 HTML h, err := he.OuterHTML() // 元素内的文本(包含子元素的文本) he.Text() // 元素的属性 he.Attr("class") // 第一个匹配的子元素 he.FirstChild("p") // 最初一个匹配的子元素 he.LastChild("p") // 第 2 个匹配的子元素 he.Child("p", 2) // 第一个匹配的子元素的属性 he.ChildAttr("p", "class") // 所有匹配到的子元素的属性切片 he.ChildrenAttr("p", "class")}
7 异步 / 多协程申请
c := NewCrawler( // 应用此 option 时主动应用指定数量的协程池发出请求,不应用此 option 则默认应用同步形式申请 // 设置的数量不宜过少,也不宜过多,请自行测试设置不同数量时的效率 WithConcurrency(30),)c.AfterResponse(func(r *predator.Response) { // handle response})for i := 0; i < 10; i++ { c.Post(ts.URL+"/post", map[string]string{ "id": fmt.Sprint(i + 1), }, nil)}c.Wait()
8 应用缓存
默认状况下,缓存是不启用的,所有的申请都间接放行。
曾经实现的缓存:
- MySQL
- PostgreSQL
- Redis
- SQLite3
缓存接口中有一个办法Compressed(yes bool)
用来压缩响应的,毕竟有时,响应长度十分长,间接保留到数据库中会影响插入和查问时的性能。
这四个接口的应用办法示例:
// MySQLc := NewCrawler( WithCache(&cache.MySQLCache{ Host: "127.0.0.1", Port: "3306", Database: "predator", Username: "root", Password: "123456", }, false), // false 为敞开压缩,true 为开启压缩,下同)// PostgreSQLc := NewCrawler( WithCache(&cache.PostgreSQLCache{ Host: "127.0.0.1", Port: "54322", Database: "predator", Username: "postgres", Password: "123456", }, false),)// Redisc := NewCrawler( WithCache(&cache.RedisCache{ Addr: "localhost:6379", }, true),)// SQLite3c := NewCrawler( WithCache(&cache.SQLiteCache{ URI: uri, // uri 为数据库寄存的地位,尽量加上后缀名 .sqlite }, true),)// 也能够应用默认值。WithCache 的第一个为 nil 时,// 默认应用 SQLite 作为缓存,且会将缓存保留在以后// 目录下的 predator-cache.sqlite 中c := NewCrawler(WithCache(nil, true))
9 代理
反对 HTTP 代理和 Socks5 代理。
应用代理时须要加上协定,如:
WithProxyPool([]string{"http://ip:port", "socks5://ip:port"})
10 对于 JSON
原本想着封装一个 JSON 包用来疾速解决 JSON 响应,然而想了一两天也没想出个好方法来,因为我能想到的,gjson都曾经解决了。
对于 JSON 响应,能用gjson
解决就不要老想着反序列化了。对于爬虫而言,反序列化是不明智的抉择。
当然,如果你的确有反序列化的需要,也不要用规范库,应用封装的 JSON 包中的序列化和反序列化办法比规范库性能高。
import "github.com/thep0y/predator/json"json.Marshal()json.Unmarshal()json.UnmarshalFromString()
凑合 JSON 响应,以后足够用了。
指标
- [x] 实现对失败响应的从新申请,直到重试了传入的重试次数时才算最终申请失败
[x] 辨认因代理生效而造成的申请失败。当应用代理池时,代理池中剔除此代理;代理池为空时,终止整个爬虫程序
- 思考到应用代理必然是因为不想将本地 ip 裸露给指标网站或服务器,所以在应用代理后,当所有代理都生效时,不再持续发出请求
- [x] HTML 页面解析。不便定位查找元素
[x] json 扩大,用来解决、筛选 json 响应的数据,原生 json 库不适宜用在爬虫上
- 临时没想到如何封装便捷好用的 json ,以后 json 包中只能算是应用示例
- [x] 协程池,实现在多协程时对每个 goroutine 的复用,防止反复创立
[x] 定义缓存接口,并实现一种或多种缓存。因为长期缓存在爬虫中并不实用,所以 predator 采纳长久化缓存。
- 默认应用 sqlite3 进行缓存,能够应用已实现的其余缓存数据库,也能够本人实现缓存接口
- 可用缓存存储有 SQLite3、MySQL、PostgreSQL、Redis
- 因为采纳长久化缓存,所以不实现以内存作为缓存,如果须要请自行依据缓存接口实现
[x] 数据库治理接口,用来保留爬虫数据,并实现一种或多种数据库的治理
- SQL 数据库接口已实现了,NoSQL 接口与 SQL 差异较大,就不实现了,如果有应用 NoSQL 的需要,请本人实现
- 数据库接口没有封装在 Crawler 办法中,依据须要应用,个别场景下够用,简单场景中依然须要本人重写数据库治理
- [ ] 增加日志
- [ ] 为
Request
和Response
的申请体Body
增加池治理,缩小 GC 次数 - [ ] 减少对 robots.txt 的判断,默认恪守 robots.txt 规定,但能够抉择疏忽
我的项目地址
thep0y/predator: High-performance crawler framework based on fasthttp (github.com)