个人对该项目比较感兴趣,争取做到时刻保持关注。因为自己也用go实现了类似的爬虫功能,但是一对比就有点相形见绌,故可以从中学到很多东西。目前本文基于katana-0.0.1(2022-11.7)进行分析,同时会关注后续版本。
参数解析
参数解析,常用/必备参数已加粗
- input:
- -u:指定爬取url,支持string,string[]
- configuration:基本配置
- -d:指定爬取深度,从0开始,默认是2
- -jc:解析js和css标签,经测试默认解析了
- -ct:指定抓取时间,单位为s
- -kf:指定是否抓取robots.txt或sitemap.xml文件中的链接
- -mrs:解析的最大响应数据大小,默认为mb
- -timeout:超时时间
- -aff:启用可选表单自动填写,暂不清楚有什么作用
- -retry:请求重试次数:默认为1
- -proxy:设置代理,支持http和socks5
- -H:添加自定义请求头,格式为string[]
- -config:指定配置文件
- -fc:指定自定义表单配置文件
- headless:无头浏览器模式,linux暂时有点问题(Running as root without –no-sandbox is not supported.)
- -hl:
- -sc:
- sb
- scope:根据某种规则指定要爬取的url的范围
- -cs:根据正则匹配范围内的url
- -cos:根据正则匹配范围外的url
- -fs:预定义范围字段,默认为”rdn”,(dn,rdn,fqdn)
- -ns:禁用基于主机的默认作用域(暂不不知道作用)
- -do:显示作用域内爬取的外部端点
- filter:
- -f:定义在输出中展示的字段(url,path,fqdn,rdn,rurl,qurl,qpath,file,key,value,kv,dir,udir)
- -sf:定义在存储是保存的字段(url,path,fqdn,rdn,rurl,qurl,qpath,file,key,value,kv,dir,udir)
- -em:根据扩展名进行匹配,如php,html,js,理解为include
- -ef:根据扩展名过滤输出,如png, css,理解为exclude
- rate-limit:
- -c:并发数,默认为10
- -p:并行数,默认为10
- -rd:每个请求之间的请求延迟
- -rl:每秒最大请求数,默认为150
- -rlm:每分钟发送的最大请求数
- output:
- -o:结果写入到指定文件
- -j:以json格式写入
- -nc:禁用输出内容着色
- -silent:仅显示输出
- -v:显示详细输出
- -version:显示项目版本号
目录文件说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86├─cmd
│ ├─katana
│ │ main.go
│ │
│ └─tools
│ └─crawl-maze-score
│ main.go
│
├─internal
│ └─runner
│ banner.go 启动时展示的自定义信息
│ executer.go 根据并行度(parallelism)调用核心crawl
│ options.go 解析命令行参数,进行相应的配置
│ runner.go 最外层运行结构体Runner,包含属性crawlerOptions, stdin, crawler(核心), options
│
└─pkg
├─engine
│ │ engine.go
│ │
│ ├─common
│ │ http.go 封装httpclient,配置transport,timeout,以及checkRedirect(如果绑定了回调,会执行该回调函数)
│ │
│ ├─hybrid 两种模式之一(headless)
│ │ crawl.go
│ │ doc.go
│ │ hijack.go
│ │ hybrid.go
│ │
│ ├─parser
│ │ │ parser.go
│ │ │ parser_test.go
│ │ │
│ │ └─files
│ │ request.go 抓取robots.txt或sitemap.xml的入口
│ │ robotstxt.go
│ │ robotstxt_test.go
│ │ sitemapxml.go
│ │ sitemapxml_test.go
│ │
│ └─standard 两种模式之一(standard)
│ crawl.go 实际发起请求调用httpclient.Do的地方
│ doc.go
│ standard.go 核心Crawl
│
├─navigation
│ request.go 处理请求url
│ response.go 处理响应
│
├─output
│ fields.go 输出字段格式处理
│ fields_test.go
│ file_writer.go 写文件
│ format_json.go 处理成json格式
│ format_screen.go 终端输出格式整理
│ output.go write核心,定义了Writer接口
│
├─types
│ crawler_options.go 定义crawler的一些配置,包括公用options
│ options.go 定义公用options
│
└─utils 工具'类'
│ formfill.go
│ formfill_test.go
│ regex.go
│ utils.go
│ utils_test.go
│
├─extensions
│ extensions.go
│ extensions_test.go
│
├─filters
│ filters.go
│ filters_test.go
│ simple.go
│
├─queue 广度优先(优先级队列[最小堆])和深度优先(栈[双向链表]),对应两种模式
│ priority_queue.go 优先级队列实现 "container/heap"
│ priority_queue_test.go
│ queue.go 封装了两种模式,统一对外接口"VarietyQueue"
│ stack.go 栈实现 "container/list"
│ stack_test.go
│
└─scope
scope.go
scope_test.go源码分析
首先看下项目的依赖:从最简单的命令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23require (
github.com/PuerkitoBio/goquery v1.8.0 // 页面解析库
github.com/go-rod/rod v0.112.0 // 操作浏览器的工具
github.com/json-iterator/go v1.1.12 // json解析器
github.com/logrusorgru/aurora v2.0.3+incompatible // 终端输出着色
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3 // 解析HTML5 srcset
github.com/pkg/errors v0.9.1 // 第三方异常处理包
github.com/projectdiscovery/fastdialer v0.0.17 // 快速发起请求
github.com/projectdiscovery/fileutil v0.0.3 // 文件处理工具
github.com/projectdiscovery/goflags v0.1.3 // 命令行参数解决
github.com/projectdiscovery/gologger v1.1.4 // 日志处理
github.com/projectdiscovery/hmap v0.0.2-0.20210917080408-0fd7bd286bfa
github.com/projectdiscovery/ratelimit v0.0.1 // 并发控制(时间间隔)
github.com/projectdiscovery/retryablehttp-go v1.0.2 // 可自动重试的http client
github.com/projectdiscovery/stringsutil v0.0.2 // 字符串处理
github.com/remeh/sizedwaitgroup v1.0.0 // 并发控制(同一时间)
github.com/rs/xid v1.4.0 // 生成唯一id
github.com/shirou/gopsutil/v3 v3.22.10 // 跨平台进程和系统监控
github.com/stretchr/testify v1.8.1 // 测试框架
go.uber.org/multierr v1.8.0 // 多错误处理
golang.org/x/net v0.1.0 // 补充go网络库
gopkg.in/yaml.v3 v3.0.1 // 解析生成yaml数据
)./katana -u https://tesla.com -d 1
开始入手,分析代码的运行流程。程序入口
cmd/katana/main.go
接下来看看1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37var (
cfgFile string // 配置文件
options = &types.Options{} // 基本的设置
)
func main() {
// 1. 读取命令行参数
if err := readFlags(); err != nil {
gologger.Fatal().Msgf("Could not read flags: %s\n", err)
}
// 2. 新建一个runner对象, 包含爬虫配置, 基本配置, 输入, 及爬虫核心
runner, err := runner.New(options)
if err != nil || runner == nil {
gologger.Fatal().Msgf("could not create runner: %s\n", err)
}
defer runner.Close()
// 3. 接收退出信号关闭整个channel
// close handler
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
gologger.DefaultLogger.Info().Msg("- Ctrl+C pressed in Terminal")
runner.Close()
os.Exit(0)
}()
}()
// 4. 爬虫执行ExecuteCrawling
if err := runner.ExecuteCrawling(); err != nil {
gologger.Fatal().Msgf("could not execute crawling: %s", err)
}
}runner
的New
和Close
做了什么,在internal/runner/runner.go
下:注:之后只会贴出核心逻辑,其他代码将会省去。
1 | // 先熟悉Runner结构体 |
爬虫核心
接下来就到了核心部分,只要实现了文件pkg/engine/engine.go
中的接口Engine
就可以嵌入自己的爬虫,之后主要分析标准模式下的实现。
1 | // Engine的实现如下, 所处文件位于pkg/engine/engine.go |
现在来看看标准模式下的Crawl
的具体实现,位于文件pkg/engine/standard/standard.go
下:
1 | func (c *Crawler) Crawl(rootURL string) error { |
最后再来看看发起请求makeRequest
与解析响应ParseResponse
都做了哪些事吧。
1 | // makeRequest 位于文件pkg/engine/standard/crawl.go下 |
结果输出
写结果是在makeParseResponseCallback
中调用的,正好上面只是一笔带过,下面详细分析下,该方法位于文件pkg/engine/standard/standard.go
下:
1 | func (c *Crawler) makeParseResponseCallback(queue *queue.VarietyQueue) func(nr navigation.Request) { |
总结
目前只是过了一下standard模式下程序的运行流程,之后再分析下headless下所做的工作,初次之外项目里还做了很多其他的操作,比如选择url范围,过滤,解析等等,要深入了解可以自己参照着实现一个,也是学习效率最快的方式了吧。顺便一提我个人运行headless模式时,报了个Running as root without --no-sandbox is not supported
的错误,当时想着之后有时间再折腾折腾,没想到官方发布了0.0.2
版本把这个问题修复了,不得不感叹效率之高。