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

運行界面顯示

以windows的exec文件爲例

// +build windows
package exec

import (
	"os"
	"os/exec"
	"os/signal"

	"github.com/henrylee2cn/pholcus/config"

	"github.com/henrylee2cn/pholcus/cmd" // cmd版
	"github.com/henrylee2cn/pholcus/gui" // gui版
	"github.com/henrylee2cn/pholcus/web" // web版
)

func run(which string) {
	exec.Command("cmd.exe", "/c", "title", config.FULL_NAME).Start()

	// 選擇運行界面
	switch which {
	case "gui":
		gui.Run()

	case "cmd":
		cmd.Run()

	case "web":
		fallthrough
	default:
		ctrl := make(chan os.Signal, 1)
		signal.Notify(ctrl, os.Interrupt, os.Kill)
		go web.Run()
		<-ctrl
	}
}

os/exec包的用法

https://cloud.tencent.com/developer/section/1143928

switch語句,在參數等於web的時候,fallthrough到下一步,執行default。

os/signal包用法

https://cloud.tencent.com/developer/section/1143930

將os.Interrupt和os.Kill信號中繼到ctrl,收到中斷信號則中斷當前進程。

1.cmd

調用了cmd中的Run方法

第一句,app.LogicApp.Init(cache.Task.Mode, cache.Task.Port, cache.Task.Master)

查找app.LogicApp.init

如圖,結構體Logic實現了接口App的所有方法,初始化了接口實例LogicApp。其中的init方法如下

// 使用App前必須先進行Init初始化(SetLog()除外)
func (self *Logic) Init(mode int, port int, master string, w ...io.Writer) App {
	self.canSocketLog = false
	if len(w) > 0 {
		self.SetLog(w[0])
	}
    // 打開log
	self.LogGoOn()

    // 使用參數初始化Logic
	self.AppConf.Mode, self.AppConf.Port, self.AppConf.Master = mode, port, master
	self.Teleport = teleport.New()
	self.TaskJar = distribute.NewTaskJar()
	self.SpiderQueue = crawler.NewSpiderQueue()
	self.CrawlerPool = crawler.NewCrawlerPool()

    // 選擇模式,server、client或者離線模式。
	switch self.AppConf.Mode {
	case status.SERVER:
		logs.Log.EnableStealOne(false)
		if self.checkPort() {
			logs.Log.Informational("                                                                                               !!當前運行模式爲:[ 服務器 ] 模式!!")
			self.Teleport.SetAPI(distribute.MasterApi(self)).Server(":" + strconv.Itoa(self.AppConf.Port))
		}

	case status.CLIENT:
		if self.checkAll() {
			logs.Log.Informational("                                                                                               !!當前運行模式爲:[ 客戶端 ] 模式!!")
			self.Teleport.SetAPI(distribute.SlaveApi(self)).Client(self.AppConf.Master, ":"+strconv.Itoa(self.AppConf.Port))
			// 開啓節點間log打印
			self.canSocketLog = true
			logs.Log.EnableStealOne(true)
			go self.socketLog()
		}
	case status.OFFLINE:
		logs.Log.EnableStealOne(false)
		logs.Log.Informational("                                                                                               !!當前運行模式爲:[ 單機 ] 模式!!")
		return self
	default:
		logs.Log.Warning(" *    ——請指定正確的運行模式!——")
		return self
	}
	return self
}

其中用到了teleport框架,信息如下:

https://studygolang.com/articles/10784

接下來執行run函數,

// https://github.com/henrylee2cn/pholcus/blob/master/cmd/pholcus-cmd.go
// 運行
func run() {
	// 創建蜘蛛隊列
	sps := []*spider.Spider{}
	*spiderflag = strings.TrimSpace(*spiderflag)

    // 根據spiderflag創建蜘蛛隊列
	if *spiderflag == "*" {
		sps = app.LogicApp.GetSpiderLib()

	} else {
		for _, idx := range strings.Split(*spiderflag, ",") {
			idx = strings.TrimSpace(idx)
			if idx == "" {
				continue
			}
			i, _ := strconv.Atoi(idx)
			sps = append(sps, app.LogicApp.GetSpiderLib()[i])
		}
	}

	app.LogicApp.SpiderPrepare(sps).Run()
}

sps的生成過程:

// https://github.com/henrylee2cn/pholcus/blob/master/app/app.go
// 獲取全部蜘蛛種類
func (self *Logic) GetSpiderLib() []*spider.Spider {
	return self.SpiderSpecies.Get()
}


// SpiderSpecies初始化爲spider.Species
func newLogic() *Logic {
	return &Logic{
		AppConf:       cache.Task,
		SpiderSpecies: spider.Species,
		status:        status.STOPPED,
		Teleport:      teleport.New(),
		TaskJar:       distribute.NewTaskJar(),
		SpiderQueue:   crawler.NewSpiderQueue(),
		CrawlerPool:   crawler.NewCrawlerPool(),
	}
}


// https://github.com/henrylee2cn/pholcus/blob/master/app/spider/species.go
// Species下的Get方法

// 獲取全部蜘蛛種類
func (self *SpiderSpecies) Get() []*Spider {
	if !self.sorted {
		l := len(self.list)
		initials := make([]string, l)
		newlist := map[string]*Spider{}
		for i := 0; i < l; i++ {
			initials[i] = self.list[i].GetName()
			newlist[initials[i]] = self.list[i]
		}
		pinyin.SortInitials(initials)
		for i := 0; i < l; i++ {
			self.list[i] = newlist[initials[i]]
		}
		self.sorted = true
	}
	return self.list
}


然後調用了app.LogicApp.SpiderPrepare(sps).Run()

// https://github.com/henrylee2cn/pholcus/blob/master/app/app.go

// SpiderPrepare()必須在設置全局運行參數之後,Run()的前一刻執行
// original爲spider包中未有過賦值操作的原始蜘蛛種類
// 已被顯式賦值過的spider將不再重新分配Keyin
// client模式下不調用該方法
func (self *Logic) SpiderPrepare(original []*spider.Spider) App {
	self.SpiderQueue.Reset()
	// 遍歷任務
	for _, sp := range original {
		spcopy := sp.Copy()
		spcopy.SetPausetime(self.AppConf.Pausetime)
		if spcopy.GetLimit() == spider.LIMIT {
			spcopy.SetLimit(self.AppConf.Limit)
		} else {
			spcopy.SetLimit(-1 * self.AppConf.Limit)
		}
        // 調用蜘蛛隊列的方法
		self.SpiderQueue.Add(spcopy)
	}
	// 遍歷自定義配置
	self.SpiderQueue.AddKeyins(self.AppConf.Keyins)
	return self // 返回了self,所以self.Run調用的是Logic實現的Run方法。
}

// SpiderQueue:蜘蛛隊列
func newLogic() *Logic {
	return &Logic{
		AppConf:       cache.Task,
		SpiderSpecies: spider.Species,
		status:        status.STOPPED,
		Teleport:      teleport.New(),
		TaskJar:       distribute.NewTaskJar(),
		SpiderQueue:   crawler.NewSpiderQueue(), // SpiderQueue的初始化
		CrawlerPool:   crawler.NewCrawlerPool(),
	}
}

// https://github.com/henrylee2cn/pholcus/blob/master/app/crawler/spiderqueue.go

// 採集引擎中規則隊列
type (
	SpiderQueue interface {
		Reset() //重置清空隊列
		Add(*Spider)
		AddAll([]*Spider)
		AddKeyins(string) //爲隊列成員遍歷添加Keyin屬性,但前提必須是隊列成員未被添加過keyin
		GetByIndex(int) *Spider
		GetByName(string) *Spider
		GetAll() []*Spider
		Len() int // 返回隊列長度
	}
	sq struct {
		list []*Spider
	}
)

func NewSpiderQueue() SpiderQueue {
	return &sq{
		list: []*Spider{},
	}
}

// Add方法只是爲sq設置id後append進list中。
func (self *sq) Add(sp *Spider) {
	sp.SetId(self.Len())
	self.list = append(self.list, sp)
}

// 添加keyin,遍歷蜘蛛隊列得到新的隊列(已被顯式賦值過的spider將不再重新分配Keyin)
func (self *sq) AddKeyins(keyins string) {
	keyinSlice := util.KeyinsParse(keyins)
	if len(keyinSlice) == 0 {
		return
	}

	unit1 := []*Spider{} // 不可被添加自定義配置的蜘蛛
	unit2 := []*Spider{} // 可被添加自定義配置的蜘蛛
	for _, v := range self.GetAll() {
		if v.GetKeyin() == KEYIN {
			unit2 = append(unit2, v)
			continue
		}
		unit1 = append(unit1, v)
	}

	if len(unit2) == 0 {
		logs.Log.Warning("本批任務無需填寫自定義配置!\n")
		return
	}

	self.Reset()

	for _, keyin := range keyinSlice {
		for _, v := range unit2 {
			v.Keyin = keyin
			nv := *v
			self.Add((&nv).Copy())
		}
	}
	if self.Len() == 0 {
		self.AddAll(append(unit1, unit2...))
	}

	self.AddAll(unit1)
}


// App中最終的Run方法
// 加載完任務配置後開始運行任務
func (self *Logic) Run() {
	// 確保開啓log
	self.LogGoOn()
    // 確保任務列表不爲空
	if self.AppConf.Mode != status.CLIENT && self.SpiderQueue.Len() == 0 {
		logs.Log.Warning(" *     —— 親,任務列表不能爲空哦~")
		self.LogRest()
		return
	}

    // 設置管道符finish
	self.finish = make(chan bool)

    // sync包用法 https://studygolang.com/articles/11038?fr=sidebar
	self.finishOnce = sync.Once{}
	// 重置計數
	self.sum[0], self.sum[1] = 0, 0
	// 重置計時
	self.takeTime = 0
	// 設置狀態
	self.setStatus(status.RUN)

    // 確保執行完成後設置狀態爲stop
	defer self.setStatus(status.STOPPED)
	// 任務執行
	switch self.AppConf.Mode {
	case status.OFFLINE:
		self.offline()
	case status.SERVER:
		self.server()
	case status.CLIENT:
		self.client()
	default:
		return
	}
    // 通過close控制self.finish來標記任務結束
	<-self.finish
}

// 返回當前運行狀態
func (self *Logic) setStatus(status int) {
    // 加讀寫鎖,防止衝突
	self.RWMutex.Lock()
	defer self.RWMutex.Unlock()
	self.status = status
}

下一篇是self.server、self.client、self.offline的執行

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