乐趣区

关于golang:Go-爬虫框架-predator

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) 用来压缩响应的,毕竟有时,响应长度十分长,间接保留到数据库中会影响插入和查问时的性能。

这四个接口的应用办法示例:

// MySQL
c := NewCrawler(
    WithCache(&cache.MySQLCache{
        Host:     "127.0.0.1",
        Port:     "3306",
        Database: "predator",
        Username: "root",
        Password: "123456",
    }, false), // false 为敞开压缩,true 为开启压缩,下同
)

// PostgreSQL
c := NewCrawler(
    WithCache(&cache.PostgreSQLCache{
        Host:     "127.0.0.1",
        Port:     "54322",
        Database: "predator",
        Username: "postgres",
        Password: "123456",
    }, false),
)

// Redis
c := NewCrawler(
    WithCache(&cache.RedisCache{Addr: "localhost:6379",}, true),
)

// SQLite3
c := 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 办法中,依据须要应用,个别场景下够用,简单场景中依然须要本人重写数据库治理
  • [] 增加日志
  • [] 为 RequestResponse的申请体 Body 增加池治理,缩小 GC 次数
  • [] 减少对 robots.txt 的判断,默认恪守 robots.txt 规定,但能够抉择疏忽

我的项目地址

thep0y/predator: High-performance crawler framework based on fasthttp (github.com)

退出移动版