运行过程
1.offline模式
// https://github.com/henrylee2cn/pholcus/blob/master/app/app.go#L372
// ******************************************** 私有方法 ************************************************* \\
// 离线模式运行
func (self *Logic) offline() {
self.exec()
}
// 开始执行任务
func (self *Logic) exec() {
// 根据蜘蛛队列的长度选择并发
count := self.SpiderQueue.Len()
// 重置页面计数, 把pagesum设为空。
// https://github.com/henrylee2cn/pholcus/blob/master/runtime/cache/cache.go#L53
cache.ResetPageCount()
// 刷新输出方式的状态, 可选项mgo、mysql、kafka
// https://github.com/henrylee2cn/pholcus/blob/master/app/pipeline/output.go#L22
pipeline.RefreshOutput()
// 初始化资源队列
// https://github.com/henrylee2cn/pholcus/blob/master/app/scheduler/scheduler.go#L30
scheduler.Init()
// 设置爬虫队列
// https://github.com/henrylee2cn/pholcus/blob/master/app/crawler/crawlerpool.go#L38
crawlerCap := self.CrawlerPool.Reset(count)
logs.Log.Informational(" * 执行任务总数(任务数[*自定义配置数])为 %v 个\n", count)
logs.Log.Informational(" * 采集引擎池容量为 %v\n", crawlerCap)
logs.Log.Informational(" * 并发协程最多 %v 个\n", self.AppConf.ThreadNum)
logs.Log.Informational(" * 默认随机停顿 %v~%v 毫秒\n", self.AppConf.Pausetime/2, self.AppConf.Pausetime*2)
logs.Log.App(" * —— 开始抓取,请耐心等候 ——")
logs.Log.Informational(` *********************************************************************************************************************************** `)
// 开始计时
cache.StartTime = time.Now()
// 根据模式选择合理的并发
if self.AppConf.Mode == status.OFFLINE {
// 可控制执行状态
go self.goRun(count)
} else {
// 保证接收服务端任务的同步
self.goRun(count)
}
}
goRun方法
// 任务执行
func (self *Logic) goRun(count int) {
// 执行任务
var i int
for i = 0; i < count && self.Status() != status.STOP; i++ {
pause:
if self.IsPause() {
time.Sleep(time.Second)
goto pause
}
// 从爬行队列取出空闲蜘蛛,并发执行
c := self.CrawlerPool.Use()
if c != nil {
go func(i int, c crawler.Crawler) {
// 执行并返回结果消息
c.Init(self.SpiderQueue.GetByIndex(i)).Run()
// 任务结束后回收该蜘蛛
self.RWMutex.RLock()
if self.status != status.STOP {
self.CrawlerPool.Free(c)
}
self.RWMutex.RUnlock()
}(i, c)
}
}
// 监控结束任务
for ii := 0; ii < i; ii++ {
s := <-cache.ReportChan
if (s.DataNum == 0) && (s.FileNum == 0) {
logs.Log.App(" * [任务小计:%s | KEYIN:%s] 无采集结果,用时 %v!\n", s.SpiderName, s.Keyin, s.Time)
continue
}
logs.Log.Informational(" * ")
switch {
case s.DataNum > 0 && s.FileNum == 0:
logs.Log.App(" * [任务小计:%s | KEYIN:%s] 共采集数据 %v 条,用时 %v!\n",
s.SpiderName, s.Keyin, s.DataNum, s.Time)
case s.DataNum == 0 && s.FileNum > 0:
logs.Log.App(" * [任务小计:%s | KEYIN:%s] 共下载文件 %v 个,用时 %v!\n",
s.SpiderName, s.Keyin, s.FileNum, s.Time)
default:
logs.Log.App(" * [任务小计:%s | KEYIN:%s] 共采集数据 %v 条 + 下载文件 %v 个,用时 %v!\n",
s.SpiderName, s.Keyin, s.DataNum, s.FileNum, s.Time)
}
self.sum[0] += s.DataNum
self.sum[1] += s.FileNum
}
// 总耗时
self.takeTime = time.Since(cache.StartTime)
var prefix = func() string {
if self.Status() == status.STOP {
return "任务中途取消:"
}
return "本次"
}()
// 打印总结报告
logs.Log.Informational(" * ")
logs.Log.Informational(` *********************************************************************************************************************************** `)
logs.Log.Informational(" * ")
switch {
case self.sum[0] > 0 && self.sum[1] == 0:
logs.Log.App(" * —— %s合计采集【数据 %v 条】, 实爬【成功 %v URL + 失败 %v URL = 合计 %v URL】,耗时【%v】 ——",
prefix, self.sum[0], cache.GetPageCount(1), cache.GetPageCount(-1), cache.GetPageCount(0), self.takeTime)
case self.sum[0] == 0 && self.sum[1] > 0:
logs.Log.App(" * —— %s合计采集【文件 %v 个】, 实爬【成功 %v URL + 失败 %v URL = 合计 %v URL】,耗时【%v】 ——",
prefix, self.sum[1], cache.GetPageCount(1), cache.GetPageCount(-1), cache.GetPageCount(0), self.takeTime)
case self.sum[0] == 0 && self.sum[1] == 0:
logs.Log.App(" * —— %s无采集结果,实爬【成功 %v URL + 失败 %v URL = 合计 %v URL】,耗时【%v】 ——",
prefix, cache.GetPageCount(1), cache.GetPageCount(-1), cache.GetPageCount(0), self.takeTime)
default:
logs.Log.App(" * —— %s合计采集【数据 %v 条 + 文件 %v 个】,实爬【成功 %v URL + 失败 %v URL = 合计 %v URL】,耗时【%v】 ——",
prefix, self.sum[0], self.sum[1], cache.GetPageCount(1), cache.GetPageCount(-1), cache.GetPageCount(0), self.takeTime)
}
logs.Log.Informational(" * ")
logs.Log.Informational(` *********************************************************************************************************************************** `)
// 单机模式并发运行,需要标记任务结束
if self.AppConf.Mode == status.OFFLINE {
self.LogRest()
self.finishOnce.Do(func() { close(self.finish) })
}
}
c.Init(self.SpiderQueue.GetByIndex(i)).Run() 执行任务
c的类型是crawler.Crawler
先执行Init
https://github.com/henrylee2cn/pholcus/blob/master/app/crawler/crawler.go#L42
func (self *crawler) Init(sp *spider.Spider) Crawler {
self.Spider = sp.ReqmatrixInit()
self.Pipeline = pipeline.New(sp)
self.pause[0] = sp.Pausetime / 2
if self.pause[0] > 0 {
self.pause[1] = self.pause[0] * 3
} else {
self.pause[1] = 1
}
return self
}
再执行Run
https://github.com/henrylee2cn/pholcus/blob/master/app/crawler/crawler.go#L55
// 任务执行入口
func (self *crawler) Run() {
// 预先启动数据收集/输出管道
self.Pipeline.Start()
// 运行处理协程
c := make(chan bool)
go func() {
// 本文件内的run方法
self.run()
close(c)
}()
// 启动任务
self.Spider.Start()
<-c // 等待处理协程退出
// 停止数据收集/输出管道
self.Pipeline.Stop()
}
func (self *crawler) run() {
for {
// 队列中取出一条请求并处理
req := self.GetOne()
if req == nil {
// 停止任务
if self.Spider.CanStop() {
break
}
time.Sleep(20 * time.Millisecond)
continue
}
// 执行请求
self.UseOne() // 使用资源空位
go func() {
defer func() {
self.FreeOne() // 释放资源空位
}()
logs.Log.Debug(" * Start: %v", req.GetUrl())
self.Process(req)
}()
// 随机等待
self.sleep()
}
// 等待处理中的任务完成
self.Spider.Defer()
}
// Process方法
func (self *crawler) Process(req *request.Request) {
var (
downUrl = req.GetUrl()
sp = self.Spider
)
// 返回前的处理函数
defer func() {
if p := recover(); p != nil {
if sp.IsStopping() {
// println("Process$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
return
}
// 返回是否作为新的失败请求被添加至队列尾部
if sp.DoHistory(req, false) {
// 统计失败数
cache.PageFailCount()
}
// 提示错误
stack := make([]byte, 4<<10) //4KB
length := runtime.Stack(stack, true)
start := bytes.Index(stack, []byte("/src/runtime/panic.go"))
stack = stack[start:length]
start = bytes.Index(stack, []byte("\n")) + 1
stack = stack[start:]
if end := bytes.Index(stack, []byte("\ngoroutine ")); end != -1 {
stack = stack[:end]
}
stack = bytes.Replace(stack, []byte("\n"), []byte("\r\n"), -1)
logs.Log.Error(" * Panic [process][%s]: %s\r\n[TRACE]\r\n%s", downUrl, p, stack)
}
}()
var ctx = self.Downloader.Download(sp, req) // download page
if err := ctx.GetError(); err != nil {
// 返回是否作为新的失败请求被添加至队列尾部
if sp.DoHistory(req, false) {
// 统计失败数
cache.PageFailCount()
}
// 提示错误
logs.Log.Error(" * Fail [download][%v]: %v\n", downUrl, err)
return
}
// 过程处理,提炼数据
ctx.Parse(req.GetRuleName())
// 该条请求文件结果存入pipeline
for _, f := range ctx.PullFiles() {
if self.Pipeline.CollectFile(f) != nil {
break
}
}
// 该条请求文本结果存入pipeline
for _, item := range ctx.PullItems() {
if self.Pipeline.CollectData(item) != nil {
break
}
}
// 处理成功请求记录
sp.DoHistory(req, true)
// 统计成功页数
cache.PageSuccCount()
// 提示抓取成功
logs.Log.Informational(" * Success: %v\n", downUrl)
// 释放ctx准备复用
spider.PutContext(ctx)
}
// Spider中的Defer方法
// https://github.com/henrylee2cn/pholcus/blob/master/app/spider/spider.go#L331
// 退出任务前收尾工作
func (self *Spider) Defer() {
// 取消所有定时器
if self.timer != nil {
self.timer.drop()
self.timer = nil
}
// 等待处理中的请求完成
self.reqMatrix.Wait()
// 更新失败记录
self.reqMatrix.TryFlushFailure()
}
2.server模式
服务器模式只是用来添加任务。
https://github.com/henrylee2cn/pholcus/blob/master/app/app.go#L378
// 服务器模式运行,必须在SpiderPrepare()执行之后调用才可以成功添加任务
// 生成的任务与自身当前全局配置相同
func (self *Logic) server() {
// 标记结束
defer func() {
self.finishOnce.Do(func() { close(self.finish) })
}()
// 便利添加任务到库
tasksNum, spidersNum := self.addNewTask()
if tasksNum == 0 {
return
}
// 打印报告
logs.Log.Informational(" * ")
logs.Log.Informational(` *********************************************************************************************************************************** `)
logs.Log.Informational(" * ")
logs.Log.Informational(" * —— 本次成功添加 %v 条任务,共包含 %v 条采集规则 ——", tasksNum, spidersNum)
logs.Log.Informational(" * ")
logs.Log.Informational(` *********************************************************************************************************************************** `)
}
3.client模式
客户端模式从服务器下载任务并执行,执行过程与offline模式一样
https://github.com/henrylee2cn/pholcus/blob/master/app/app.go#L436
// 客户端模式运行
func (self *Logic) client() {
// 标记结束
defer func() {
self.finishOnce.Do(func() { close(self.finish) })
}()
for {
// 从任务库获取一个任务
t := self.downTask()
if self.Status() == status.STOP || self.Status() == status.STOPPED {
return
}
// 准备运行
self.taskToRun(t)
// 重置计数
self.sum[0], self.sum[1] = 0, 0
// 重置计时
self.takeTime = 0
// 执行任务
self.exec()
}
}
// taskToRun方法
func (self *Logic) taskToRun(t *distribute.Task) {
// 清空历史任务
self.SpiderQueue.Reset()
// 更改全局配置
self.setAppConf(t)
// 初始化蜘蛛队列
for _, n := range t.Spiders {
sp := self.GetSpiderByName(n["name"])
if sp == nil {
continue
}
spcopy := sp.Copy()
spcopy.SetPausetime(t.Pausetime)
if spcopy.GetLimit() > 0 {
spcopy.SetLimit(t.Limit)
} else {
spcopy.SetLimit(-1 * t.Limit)
}
if v, ok := n["keyin"]; ok {
spcopy.SetKeyin(v)
}
self.SpiderQueue.Add(spcopy)
}
}