運行界面顯示
以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的執行