spider handler
测试的时候,发现当指定所有节点运行时,结果集不一样。经检查发现,同步爬虫文件到其他节点的时候会出现问题:假设有节点 a、b、c,a发布了一个爬虫,b、c 经过了一段时间同步了这个爬虫,而 b 在远端 gridfs 上修改了爬虫文件后 系统出错 没有同步到其他节点,而其他节点不知道 b 的文件是脏数据。此时引入 md5 文件,比对远端 gridfs 字段,进行校验。
package spider_handler
import (
"constants"
"database"
"model"
"utils"
"fmt"
"github.com/apex/log"
"github.com/globalsign/mgo/bson"
"github.com/satori/go.uuid"
"github.com/spf13/viper"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"runtime/debug"
"strings"
"sync"
)
const (
Md5File = "md5.txt"
)
type SpiderSync struct {
Spider model.Spider
}
func (s *SpiderSync) CreateMd5File(md5 string) {
path := filepath.Join(viper.GetString("spider.path"), s.Spider.Name)
utils.CreateDirPath(path)
fileName := filepath.Join(path, Md5File)
file := utils.OpenFile(fileName)
defer utils.Close(file)
if file != nil {
if _, err := file.WriteString(md5 + "\n"); err != nil {
log.Errorf("file write string error: %s", err.Error())
debug.PrintStack()
}
}
}
func (s *SpiderSync) CheckIsScrapy() {//拉取下来爬虫文件时,判断解压的文件中有无 scrapy.cfg 字段,用来判断执行什么样的命令.
if s.Spider.Type == constants.Configurable {
return
}
s.Spider.IsScrapy = utils.Exists(path.Join(s.Spider.Src, "scrapy.cfg"))
if s.Spider.IsScrapy {
s.Spider.Cmd = "scrapy crawl"
}
if err := s.Spider.Save(); err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return
}
}
func (s *SpiderSync) AfterRemoveDownCreate() {
if model.IsMaster() {
s.CheckIsScrapy()
}
}
func (s *SpiderSync) RemoveDownCreate(md5 string) {
s.RemoveSpiderFile()
s.Download()
s.CreateMd5File(md5)
s.AfterRemoveDownCreate()
}
// 删除本地文件
func (s *SpiderSync) RemoveSpiderFile() {
path := filepath.Join(
viper.GetString("spider.path"),
s.Spider.Name,
)
//爬虫文件有变化,先删除本地文件
if err := os.RemoveAll(path); err != nil {
log.Errorf("remove spider files error: %s, path: %s", err.Error(), path)
debug.PrintStack()
}
}
// 下载爬虫
func (s *SpiderSync) Download() {
spiderId := s.Spider.Id.Hex()
fileId := s.Spider.FileId.Hex()
isDownloading, key := s.CheckDownLoading(spiderId, fileId)
if isDownloading {
log.Infof(fmt.Sprintf("spider is already being downloaded, spider id: %s", s.Spider.Id.Hex()))
return
} else {
_ = database.RedisClient.HSet("spider", key, key)
}
session, gf := database.GetGridFs("files")
defer session.Close()
f, err := gf.OpenId(bson.ObjectIdHex(fileId))
defer utils.Close(f)
if err != nil {
log.Errorf("open file id: " + fileId + ", spider id:" + spiderId + ", error: " + err.Error())
debug.PrintStack()
return
}
// 生成唯一ID
randomId := uuid.NewV4()
tmpPath := viper.GetString("other.tmppath")
if !utils.Exists(tmpPath) {
if err := os.MkdirAll(tmpPath, 0777); err != nil {
log.Errorf("mkdir other.tmppath error: %v", err.Error())
return
}
}
// 创建临时文件
tmpFilePath := filepath.Join(tmpPath, randomId.String()+".zip")
tmpFile := utils.OpenFile(tmpFilePath)
// 将该文件写入临时文件
if _, err := io.Copy(tmpFile, f); err != nil {
log.Errorf("copy file error: %s, file_id: %s", err.Error(), f.Id())
debug.PrintStack()
return
}
// 解压缩临时文件到目标文件夹
dstPath := filepath.Join(
viper.GetString("spider.path"),
s.Spider.Name,
)
if err := utils.DeCompress(tmpFile, dstPath); err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return
}
cmd := exec.Command("chmod", "-R", "777", dstPath)
if err := cmd.Run(); err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return
}
// 关闭临时文件
if err := tmpFile.Close(); err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return
}
// 删除临时文件
if err := os.Remove(tmpFilePath); err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return
}
_ = database.RedisClient.HDel("spider", key)
}
file.go
遍历获取本地爬虫文件夹的结构,返回 file 结构体,其中的 children 层层嵌套,用于前端的文件目录展示。
package services
import (
"model"
"github.com/apex/log"
"os"
"path"
"runtime/debug"
"strings"
)
func GetFileNodeTree(dstPath string, level int) (f model.File, err error) {
return getFileNodeTree(dstPath, level, dstPath)
}
func getFileNodeTree(dstPath string, level int, rootPath string) (f model.File, err error) {
dstF, err := os.Open(dstPath)
if err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return f, err
}
defer dstF.Close()
fileInfo, err := dstF.Stat()
if err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return f, nil
}
if !fileInfo.IsDir() { //如果dstF是文件
return model.File{
Label: fileInfo.Name(),
Name: fileInfo.Name(),
Path: strings.Replace(dstPath, rootPath, "", -1),
IsDir: false,
Size: fileInfo.Size(),
Children: nil,
}, nil
} else { //如果dstF是文件夹
dir, err := dstF.Readdir(0) //获取文件夹下各个文件或文件夹的fileInfo
if err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return f, nil
}
f = model.File{
Label: path.Base(dstPath),
Name: path.Base(dstPath),
Path: strings.Replace(dstPath, rootPath, "", -1),
IsDir: true,
Size: 0,
Children: nil,
}
for _, subFileInfo := range dir {
subFileNode, err := getFileNodeTree(path.Join(dstPath, subFileInfo.Name()), level+1, rootPath)
if err != nil {
log.Errorf(err.Error())
debug.PrintStack()
return f, err
}
f.Children = append(f.Children, subFileNode)
}
return f, nil
}
}