golang爬蟲項目Pholcus源碼分析(三)

運行過程

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)
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章