目录

crawlergo源码

目录

├─cmd
│  └─main.go  # 程序主入口
└─pkg
    ├─config  # 一些配置相关
    ├─engine  # chrome相关程序
    ├─filter  # 去重相关
    ├─js      # 一些注入的js
    ├─logger  # 日志
    ├─model   # url和请求相关的库
    └─tools   # 一些通用类库
        └─requests

爬虫生命周期

../../../img/2023/crawlergo.png

上述标记了每个大步骤标记了a、b、c… 便于后面分析功能时说明在哪个步骤实现的

  1. 初始化全局参数(所有的url请求都依赖这些参数): 设置关键字过滤IgnoreKeywords; 设置表单参数,若没有则使用默认值: default= config.go->DefaultInputText

  2. 初始化爬虫任务

    1. 初始化参数(一些chrome的配置): 最大并发数、最大深度、url请求超时时间等
    2. 初始化浏览器 InitBrowser():忽略证书错误、不请求图片url、不展示gui等
    3. 初始化过滤器 smartFilter.Init():所有url在爬取前都要经过smartFilter.DoFilter(*model.Request)进行过滤
    4. 创建协成池(根据参数max-tab-count):同时开启最多max-tab-count个tab页,也就是并发爬取数量限制
  3. 运行爬虫任务

    1. 若配置了--robots-path true(默认false)则爬取robot.txt中的url
    2. 若配置了--fuzz-path-dict path 则开启目录爆破,爬取文件中所有关键字组成的url;也可以使用--fuzz-path true 开启后会爬取path_expansion.go->pathStr变量中的内容
    3. 开始爬取: 每个url请求都会调用addTask2Pool(req *model.Request)
  4. 每个url的爬取流程

    1. url会调用addTask2Pool,并且每个url都会是一个独立的goroutine
    2. 每个url执行会创建一个tab页来运行: 每个tab页都会监听多种事件,便于在不同时刻处理不同的请求逻辑: 可以参考 https://chromedevtools.github.io/devtools-protocol/tot/Network/
    3. 重要的事件有: 请求前的拦截、解析js中的url、重定向、40x请求、表单填充等
    4. 开始爬取(tab *Tab) Start(): 到这一步才真正的开始爬取
    5. 每个tab请求都遵循InitBrowser()时的参数,并且每个tab页也可以在chromedp.Run中添加参数
    6. 爬取ing:爬取过程中事件会经过刚才注册过的
    7. 爬取后:收集所有url结果添加到func (tab *Tab) AddResultUrl
    8. 爬取的url结果会再次过滤后添加到addTask2Pool,进行下一阶段爬取
    9. 1开始重复流程,直至爬取不到任何结果或符合终止条件(最大深度、最大时间等)
  5. 输出结果,终止浏览器

过滤去重

使用方式是: 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"

)

此功能流程:

  1. 步骤c进行初始化设置
  2. 步骤c创建初始url时 进行一次过滤
  3. 步骤g产生新的url后 进行一次过滤
  4. 步骤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

关于进程超时

目前的超时只能根据并发数量-mtab-run-timeout--max-crawled-count 大致的进行时间上的控制 参考。我这里增加了 最大的执行时间控制。但无法达到精准的控制,只能在秒级别内,比如设置20秒,会在20-23秒左右停止

根据上图,可以知道df两个步骤,一个是加入新的url,一个是tab创建设置tab的超时时间。因此可以在这两个地方进行限制。

  1. 增加参数,并记录Crawlergo启动时间
type TaskConfig struct{
   //..
   MaxRunTime int64
   Start time.Time
}

func (t *CrawlerTask) Run() {
   t.Config.Start = time.Now()
   //...
}
  1. 产生新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()
   //.....
}
  1. 创建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,整个爬取流程:

../../../img/2023/result1.png

假设tab1优先完成,产出了b/c/d三个url,那么tab2经过过滤最终只上报f一个url。