crawlergo源码
目录
目录
├─cmd
│ └─main.go # 程序主入口
└─pkg
├─config # 一些配置相关
├─engine # chrome相关程序
├─filter # 去重相关
├─js # 一些注入的js
├─logger # 日志
├─model # url和请求相关的库
└─tools # 一些通用类库
└─requests
爬虫生命周期
上述标记了每个大步骤标记了a、b、c… 便于后面分析功能时说明在哪个步骤实现的
-
初始化全局参数(所有的url请求都依赖这些参数): 设置关键字过滤
IgnoreKeywords
; 设置表单参数,若没有则使用默认值:default= config.go->DefaultInputText
-
初始化爬虫任务
- 初始化参数(一些chrome的配置): 最大并发数、最大深度、url请求超时时间等
- 初始化浏览器
InitBrowser()
:忽略证书错误、不请求图片url、不展示gui等 - 初始化过滤器
smartFilter.Init()
:所有url在爬取前都要经过smartFilter.DoFilter(*model.Request)
进行过滤 - 创建协成池(根据参数
max-tab-count
):同时开启最多max-tab-count
个tab页,也就是并发爬取数量限制
-
运行爬虫任务
- 若配置了
--robots-path true
(默认false)则爬取robot.txt中的url - 若配置了
--fuzz-path-dict path
则开启目录爆破,爬取文件中所有关键字组成的url;也可以使用--fuzz-path true
开启后会爬取path_expansion.go->pathStr
变量中的内容 - 开始爬取: 每个url请求都会调用
addTask2Pool(req *model.Request)
- 若配置了
-
每个url的爬取流程
- url会调用
addTask2Pool
,并且每个url都会是一个独立的goroutine - 每个url执行会创建一个tab页来运行: 每个tab页都会监听多种事件,便于在不同时刻处理不同的请求逻辑: 可以参考 https://chromedevtools.github.io/devtools-protocol/tot/Network/
- 重要的事件有: 请求前的拦截、解析js中的url、重定向、40x请求、表单填充等
- 开始爬取
(tab *Tab) Start()
: 到这一步才真正的开始爬取 - 每个tab请求都遵循
InitBrowser()
时的参数,并且每个tab页也可以在chromedp.Run
中添加参数 - 爬取ing:爬取过程中事件会经过刚才注册过的
- 爬取后:收集所有url结果添加到
func (tab *Tab) AddResultUrl
- 爬取的url结果会再次过滤后添加到
addTask2Pool
,进行下一阶段爬取 - 从
1
开始重复流程,直至爬取不到任何结果或符合终止条件(最大深度、最大时间等)
- url会调用
-
输出结果,终止浏览器
过滤去重
使用方式是: spiderman --filter-mode=smart http://127.0.0.1
// taskconfig.go:
type TaskConfig struct {
// ...
FilterMode string // simple、smart、strict
// ...
}
// config.go:
const (
// 默认-简单过滤: 其他过滤条件都包含简单过滤
// 对req做md5操作,比较md5值,并且过滤掉一些无用url:config.go:StaticSuffix 参数中的比如 png、jpg等
SimpleFilterMode = "simple"
// 智能过滤
SmartFilterMode = "smart"
// 严格智能过滤: 在智能过滤基础上增加了一点逻辑,对大小写、下划线等更敏感
StrictFilterMode = "strict"
)
此功能流程:
- 在
步骤c
进行初始化设置 - 在
步骤c
创建初始url时 进行一次过滤 - 在
步骤g
产生新的url后 进行一次过滤 - 在
步骤h
将最终产出的url req 做最简单的md5 过滤
因此如果需要增加新的过滤方式,或调整过滤流程,需要注意上述流程,另外可以以接口的形式来解耦filter模块,这是我提的pr:https://github.com/Qianlitp/crawlergo/pull/136
智能过滤
根据正则将匹配结果进行标记
比如:
www.baidu.com/page/1 --> www.baidu.com/page/{int}
因此www.baidu.com/page/1 和www.baidu.com/page/2 最终处理后都是www.baidu.com/page/{int} 表示相同url
关于进程超时
目前的超时只能根据并发数量-m
、tab-run-timeout
、--max-crawled-count
大致的进行时间上的控制 参考。我这里增加了
最大的执行时间控制。但无法达到精准的控制,只能在秒级别内,比如设置20秒,会在20-23秒左右停止
根据上图,可以知道d
和f
两个步骤,一个是加入新的url,一个是tab创建设置tab的超时时间。因此可以在这两个地方进行限制。
- 增加参数,并记录Crawlergo启动时间
type TaskConfig struct{
//..
MaxRunTime int64
Start time.Time
}
func (t *CrawlerTask) Run() {
t.Config.Start = time.Now()
//...
}
- 产生新url 阶段 -
task_main.go:addTask2Pool()
: 若检测超时则无法再添加新的url 创建新的tab
func (t *CrawlerTask) addTask2Pool(req *model.Request) {
t.taskCountLock.Lock()
if t.crawledCount >= t.Config.MaxCrawlCount {
t.taskCountLock.Unlock()
return
} else {
t.crawledCount += 1
}
if t.Start.Add(time.Second * time.Duration(t.Config.MaxRunTime)).Before(time.Now()) {
t.taskCountLock.Unlock()
return
}
t.taskCountLock.Unlock()
//.....
}
- 创建tab准备爬取 阶段 -
task_main.go:Task()
:进程剩余时间和tab最大超时时间 取最小,作为tab的超时时间,如果没时间了,则取消创建
func (t *tabTask) Task() {
// 设置tab超时时间,若设置了程序最大运行时间, tab超时时间和程序剩余时间取小
timeremaining := t.crawlerTask.Start.Add(time.Duration(t.crawlerTask.Config.MaxRunTime) * time.Second).Sub(time.Now())
tabTime := t.crawlerTask.Config.TabRunTimeout
if t.crawlerTask.Config.TabRunTimeout > timeremaining {
tabTime = timeremaining
}
if tabTime <= 0 {
return
}
//.....
}
output
// task_main.go
type Result struct {
// 存储的url,是请求完成并且是过滤后的;也就是最终产出的全部内容
ReqList []*model.Request
// 存储的是搜集到的所有url(没有过滤,也不一定会进行请求的url)
// 它的存储逻辑是直接将每个tab产出的req存入,如果多个tab并发执行,是会重复的,因此在任务完成后需要去重操作
// 主要作用是 展示爬取到的全部域名
AllReqList []*model.Request
//..
}
举例: 假设捕获到了两个url要进行爬取,并且分别会产出3个url,如下关系:
1. www.baidu.com/a
- www.baidu.com/c
- www.baidu.com/d
- www.baidu.com/e
2. www.baidu.com/b
- www.baidu.com/c
- www.baidu.com/d
- www.baidu.com/f
根据参数可以设置最大并发爬取--max-tab-count
,比如是 2,整个爬取流程:
假设tab1
优先完成,产出了b/c/d
三个url,那么tab2
经过过滤最终只上报f
一个url。