前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >predatorpredato详解

predatorpredato详解

原创
作者头像
ruochen
修改2021-11-24 14:10:53
6340
修改2021-11-24 14:10:53
举报
1 创建一个 Crawler
代码语言:txt
复制
import "github.com/thep0y/predator"
代码语言:txt
复制
func main() {
代码语言:txt
复制
    crawler := predator.NewCrawler(
代码语言:txt
复制
        predator.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0"),
代码语言:txt
复制
        predator.WithCookies(map[string]string{"JSESSIONID": cookie}),
代码语言:txt
复制
        predator.WithProxy(ip), // 或者使用代理池 predator.WithProxyPool([]string)
代码语言:txt
复制
    )
代码语言:txt
复制
}

创建Crawler时有一些可选项用来功能增强。所有可选项参考predator/options.go

2 发送 Get 请求
代码语言:txt
复制
crawler.Get("http://www.baidu.com")

对请求和响应的处理参考的是 colly,我觉得 colly 的处理方式非常舒服。

代码语言:txt
复制
// BeforeRequest 可以在发送请求前,对请求进行一些修补
代码语言:txt
复制
crawler.BeforeRequest(func(r *predator.Request) {
代码语言:txt
复制
    headers := map[string]string{
代码语言:txt
复制
        "Accept":           "*/*",
代码语言:txt
复制
        "Accept-Language":  "zh-CN",
代码语言:txt
复制
        "Accept-Encoding":  "gzip, deflate",
代码语言:txt
复制
        "X-Requested-With": "XMLHttpRequest",
代码语言:txt
复制
        "Origin":           "http://example.com",
代码语言:txt
复制
    }
代码语言:txt
复制
    r.SetHeaders(headers)
代码语言:txt
复制
    // 请求和响应之间的上下文传递,上下文见下面的上下文示例
代码语言:txt
复制
    r.Ctx.Put("id", 10)
代码语言:txt
复制
    r.Ctx.Put("name", "tom")
代码语言:txt
复制
})
代码语言:txt
复制
crawler.AfterResponse(func(r *predator.Response) {
代码语言:txt
复制
    // 从请求发送的上下文中取值
代码语言:txt
复制
    id := r.Ctx.GetAny("id").(int)
代码语言:txt
复制
    name := r.Ctx.Get("name")
代码语言:txt
复制
    // 对于 json 响应,建议使用 gjson 进行处理
代码语言:txt
复制
    body := gjson.ParseBytes(r.Body)
代码语言:txt
复制
    amount := body.Get("amount").Int()
代码语言:txt
复制
    types := body.Get("types").Array()
代码语言:txt
复制
})
代码语言:txt
复制
// 请求语句要在 BeforeRequest 和 AfterResponse 后面调用
代码语言:txt
复制
crawler.Get("http://www.baidu.com")
3 发送 Post 请求

与 Get 请求有一点不同,通常每个 Post

的请求的参数是不同的,而这些参数都在请求体中,在BeforeRequest中处理请求体虽然可以,但绝非最佳选择,所以在构造 Post

请求时,可以直接传入上下文,用以解决与响应的信息传递。

代码语言:txt
复制
// BeforeRequest 可以在发送请求前,对请求进行一些修补
代码语言:txt
复制
crawler.BeforeRequest(func(r *predator.Request) {
代码语言:txt
复制
    headers := map[string]string{
代码语言:txt
复制
        "Accept":           "*/*",
代码语言:txt
复制
        "Accept-Language":  "zh-CN",
代码语言:txt
复制
        "Accept-Encoding":  "gzip, deflate",
代码语言:txt
复制
        "X-Requested-With": "XMLHttpRequest",
代码语言:txt
复制
        "Origin":           "http://example.com",
代码语言:txt
复制
    }
代码语言:txt
复制
    r.SetHeaders(headers)
代码语言:txt
复制
})
代码语言:txt
复制
crawler.AfterResponse(func(r *predator.Response) {
代码语言:txt
复制
    // 从请求发送的上下文中取值
代码语言:txt
复制
    id := r.Ctx.GetAny("id").(int)
代码语言:txt
复制
    name := r.Ctx.Get("name")
代码语言:txt
复制
    // 对于 json 响应,建议使用 gjson 进行处理
代码语言:txt
复制
    body := gjson.ParseBytes(r.Body)
代码语言:txt
复制
    amount := body.Get("amount").Int()
代码语言:txt
复制
    types := body.Get("types").Array()
代码语言:txt
复制
})
代码语言:txt
复制
body := map[string]string{"foo": "bar"}
代码语言:txt
复制
// 在 Post 请求中,应该将关键参数用这种方式放进上下文
代码语言:txt
复制
ctx, _ := context.AcquireCtx()
代码语言:txt
复制
ctx.Put("id", 10)
代码语言:txt
复制
ctx.Put("name", "tom")
代码语言:txt
复制
crawler.Post("http://www.baidu.com", body, ctx)

如果不需要传入上下文,可以直接用nil代替:

代码语言:txt
复制
crawler.Post("http://www.baidu.com", body, nil)
4 发送 multipart/form-data 请求

`multipart/form-

data方法需要使用专门的PostMultipart方法,只是当前请求体只支持mapstringstring,没有别的原因,因为我只用到这种类型,如果以后有别的需求,再改成mapstringinterface{}`。

参考示例:https://github.com/thep0y/predator/blob/main/example/multipart/main.go

5 上下文

上下文是一个接口,我实现了两种上下文:

  • ReadOp :基于sync.Map实现,适用于读取上下文较多的场景
  • WriteOp :用map实现,适用于读写频率相差不大或写多于读的场景,这是默认采用的上下文

爬虫中如果遇到了读远多于写时就应该换ReadOp了,如下代码所示:

代码语言:txt
复制
ctx, err := AcquireCtx(context.ReadOp)
6 处理 HTML

爬虫的结果大体可分为两种,一是 HTML 响应,另一种是 JSON 格式的响应。

与 JSON 相比,HTML 需要更多的代码处理。

本框架对 HTML 处理进行了一些函数封装,能方便地通过 css selector 进行元素的查找,可以提取元素中的属性和文本等。

代码语言:txt
复制
crawl := NewCrawler()
代码语言:txt
复制
crawl.ParseHTML("body", func(he *html.HTMLElement) {
代码语言:txt
复制
    // 元素内部 HTML
代码语言:txt
复制
    h, err := he.InnerHTML()
代码语言:txt
复制
    // 元素整体 HTML
代码语言:txt
复制
    h, err := he.OuterHTML()
代码语言:txt
复制
    // 元素内的文本(包括子元素的文本)
代码语言:txt
复制
    he.Text()
代码语言:txt
复制
    // 元素的属性
代码语言:txt
复制
    he.Attr("class")
代码语言:txt
复制
    // 第一个匹配的子元素
代码语言:txt
复制
    he.FirstChild("p")
代码语言:txt
复制
    // 最后一个匹配的子元素
代码语言:txt
复制
    he.LastChild("p")
代码语言:txt
复制
    // 第 2 个匹配的子元素
代码语言:txt
复制
    he.Child("p", 2)
代码语言:txt
复制
    // 第一个匹配的子元素的属性
代码语言:txt
复制
    he.ChildAttr("p", "class")
代码语言:txt
复制
    // 所有匹配到的子元素的属性切片
代码语言:txt
复制
    he.ChildrenAttr("p", "class")
代码语言:txt
复制
}
7 异步 / 多协程请求
代码语言:txt
复制
c := NewCrawler(
代码语言:txt
复制
    // 使用此 option 时自动使用指定数量的协程池发出请求,不使用此 option 则默认使用同步方式请求
代码语言:txt
复制
    // 设置的数量不宜过少,也不宜过多,请自行测试设置不同数量时的效率
代码语言:txt
复制
    WithConcurrency(30),
代码语言:txt
复制
)
代码语言:txt
复制
c.AfterResponse(func(r *predator.Response) {
代码语言:txt
复制
    // handle response
代码语言:txt
复制
})
代码语言:txt
复制
for i := 0; i < 10; i++ {
代码语言:txt
复制
    c.Post(ts.URL+"/post", map[string]string{
代码语言:txt
复制
        "id": fmt.Sprint(i + 1),
代码语言:txt
复制
    }, nil)
代码语言:txt
复制
}
代码语言:txt
复制
c.Wait()
8 使用缓存

默认情况下,缓存是不启用的,所有的请求都直接放行。

已经实现的缓存:

  • MySQL
  • PostgreSQL
  • Redis
  • SQLite3

缓存接口中有一个方法Compressed(yes bool)用来压缩响应的,毕竟有时,响应长度非常长,直接保存到数据库中会影响插入和查询时的性能。

这四个接口的使用方法示例:

代码语言:txt
复制
// MySQL
代码语言:txt
复制
c := NewCrawler(
代码语言:txt
复制
    WithCache(&cache.MySQLCache{
代码语言:txt
复制
        Host:     "127.0.0.1",
代码语言:txt
复制
        Port:     "3306",
代码语言:txt
复制
        Database: "predator",
代码语言:txt
复制
        Username: "root",
代码语言:txt
复制
        Password: "123456",
代码语言:txt
复制
    }, false), // false 为关闭压缩,true 为开启压缩,下同
代码语言:txt
复制
)
代码语言:txt
复制
// PostgreSQL
代码语言:txt
复制
c := NewCrawler(
代码语言:txt
复制
    WithCache(&cache.PostgreSQLCache{
代码语言:txt
复制
        Host:     "127.0.0.1",
代码语言:txt
复制
        Port:     "54322",
代码语言:txt
复制
        Database: "predator",
代码语言:txt
复制
        Username: "postgres",
代码语言:txt
复制
        Password: "123456",
代码语言:txt
复制
    }, false),
代码语言:txt
复制
)
代码语言:txt
复制
// Redis
代码语言:txt
复制
c := NewCrawler(
代码语言:txt
复制
    WithCache(&cache.RedisCache{
代码语言:txt
复制
        Addr: "localhost:6379",
代码语言:txt
复制
    }, true),
代码语言:txt
复制
)
代码语言:txt
复制
// SQLite3
代码语言:txt
复制
c := NewCrawler(
代码语言:txt
复制
    WithCache(&cache.SQLiteCache{
代码语言:txt
复制
        URI: uri,  // uri 为数据库存放的位置,尽量加上后缀名 .sqlite
代码语言:txt
复制
    }, true),
代码语言:txt
复制
)
代码语言:txt
复制
// 也可以使用默认值。WithCache 的第一个为 nil 时,
代码语言:txt
复制
// 默认使用 SQLite 作为缓存,且会将缓存保存在当前
代码语言:txt
复制
// 目录下的 predator-cache.sqlite 中
代码语言:txt
复制
c := NewCrawler(WithCache(nil, true))
9 代理

支持 HTTP 代理和 Socks5 代理。

使用代理时需要加上协议,如:

代码语言:txt
复制
WithProxyPool([]string{"http://ip:port", "socks5://ip:port"})
10 日志

日志使用的是流行日志库zerolog

默认情况下,日志是不开启的,需要手动开启。

WithLogger选项需要填入一个参数*predator.LogOp,当填入nil时,默认会以INFO等级从终端美化输出。

代码语言:txt
复制
    crawler := predator.NewCrawler(
代码语言:txt
复制
        predator.WithLogger(nil),
代码语言:txt
复制
    )

predator.LogOp对外公开四个方法:

  • SetLevel :设置日志等级。等级可选:DEBUGINFOWARNINGERRORFATAL
代码语言:txt
复制
    logOp := new(predator.LogOp)
代码语言:txt
复制
// 设置为 INFO
代码语言:txt
复制
logOp.SetLevel(log.INFO)
  • ToConsole :美化输出到终端。
  • ToFile :JSON 格式输出到文件。
  • ToConsoleAndFile :既美化输出到终端,同时以 JSON 格式输出到文件。

日志的完整示例:

代码语言:txt
复制
import "github.com/thep0y/predator/log"
代码语言:txt
复制
func main() {
代码语言:txt
复制
    logOp := new(predator.LogOp)
代码语言:txt
复制
    logOp.SetLevel(log.INFO)
代码语言:txt
复制
    logOp.ToConsoleAndFile("test.log")
代码语言:txt
复制
    crawler := predator.NewCrawler(
代码语言:txt
复制
        predator.WithLogger(logOp),
代码语言:txt
复制
    )
代码语言:txt
复制
}
11 关于 JSON

本来想着封装一个 JSON 包用来快速处理 JSON

响应,但是想了一两天也没想出个好办法来,因为我能想到的,gjson都已经解决了。

对于 JSON 响应,能用gjson处理就不要老想着反序列化了。对于爬虫而言,反序列化是不明智的选择。

当然,如果你确实有反序列化的需求,也不要用标准库,使用封装的 JSON 包中的序列化和反序列化方法比标准库性能高。

代码语言:txt
复制
import "github.com/thep0y/predator/json"
代码语言:txt
复制
json.Marshal()
代码语言:txt
复制
json.Unmarshal()
代码语言:txt
复制
json.UnmarshalFromString()

对付 JSON 响应,当前足够用了。

目标

  • 完成对失败响应的重新请求,直到重试了传入的重试次数时才算最终请求失败
  • 识别因代理失效而造成的请求失败。当使用代理池时,代理池中剔除此代理;代理池为空时,终止整个爬虫程序
    • 考虑到使用代理必然是因为不想将本地 ip 暴露给目标网站或服务器,所以在使用代理后,当所有代理都失效时,不再继续发出请求
  • HTML 页面解析。方便定位查找元素
  • json 扩展,用来处理、筛选 json 响应的数据,原生 json 库不适合用在爬虫上
    • 暂时没想到如何封装便捷好用的 json ,当前 json 包中只能算是使用示例
  • 协程池,实现在多协程时对每个 goroutine 的复用,避免重复创建
  • 定义缓存接口,并完成一种或多种缓存。因为临时缓存在爬虫中并不实用,所以 predator 采用持久化缓存。
    • 默认使用 sqlite3 进行缓存,可以使用已实现的其他缓存数据库,也可以自己实现缓存接口
    • 可用缓存存储有 SQLite3、MySQL、PostgreSQL、Redis
    • 因为采用持久化缓存,所以不实现以内存作为缓存,如果需要请自行根据缓存接口实现
  • 数据库管理接口,用来保存爬虫数据,并完成一种或多种数据库的管理
    • SQL 数据库接口已实现了,NoSQL 接口与 SQL 差别较大,就不实现了,如果有使用 NoSQL 的需求,请自己实现
    • 数据库接口没有封装在 Crawler 方法中,根据需要使用,一般场景下够用,复杂场景中仍然需要自己重写数据库管理
  • 添加日志
    • 可能还不完善
  • RequestResponse的请求体Body添加池管理,减少 GC 次数
  • 增加对 robots.txt 的判断,默认遵守 robots.txt 规则,但可以选择忽略

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 创建一个 Crawler
  • 2 发送 Get 请求
  • 3 发送 Post 请求
  • 4 发送 multipart/form-data 请求
  • 5 上下文
  • 6 处理 HTML
  • 7 异步 / 多协程请求
  • 8 使用缓存
  • 9 代理
  • 10 日志
  • 11 关于 JSON
  • 目标
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档