運行過程
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)
}
}