// 添加请求到队列,并发安全 func (self *Matrix) Push(req *request.Request) { if sdl.checkStatus(status.STOP) { return } // 禁止并发,降低请求积存量 self.Lock() defer self.Unlock() // 达到请求上限,停止该规则运行 if self.maxPage >= 0 { return } // 暂停状态时等待,降低请求积存量 waited := false for sdl.checkStatus(status.PAUSE) { waited = true runtime.Gosched() } if waited && sdl.checkStatus(status.STOP) { return } // 资源使用过多时等待,降低请求积存量 waited = false for self.resCount > sdl.avgRes() { waited = true runtime.Gosched() } if waited && sdl.checkStatus(status.STOP) { return } // 不可重复下载的req if !req.IsReloadable() { hash := makeUnique(req) // 已存在成功记录时退出 if self.hasHistory(hash) { return } // 添加到临时记录 self.insertTempHistory(hash) } var priority = req.GetPriority() // 初始化该蜘蛛下该优先级队列 if _, found := self.reqs[priority]; !found { self.priorities = append(self.priorities, priority) sort.Ints(self.priorities) // 从小到大排序 self.reqs[priority] = []*request.Request{} } // 添加请求到队列 self.reqs[priority] = append(self.reqs[priority], req) // 大致限制加入队列的请求量,并发情况下应该会比maxPage多 atomic.AddInt64(&self.maxPage, 1) }
// 返回是否作为新的失败请求被添加至队列尾部 func (self *Matrix) DoHistory(req *request.Request, ok bool) bool { hash := makeUnique(req) if !req.IsReloadable() { self.tempHistoryLock.Lock() delete(self.tempHistory, hash) self.tempHistoryLock.Unlock() if ok { self.history.UpsertSuccess(hash) return false } } if ok { return false } self.failureLock.Lock() defer self.failureLock.Unlock() if _, ok := self.failures[hash]; !ok { // 首次失败时,在任务队列末尾重新执行一次 self.failures[hash] = req logs.Log.Informational(" * + 失败请求: [%v]\n", req.GetUrl()) return true } // 失败两次后,加入历史失败记录 self.history.UpsertFailure(req) return false }
func (self *Surfer) Download(sp *spider.Spider, cReq *request.Request) *spider.Context { ctx := spider.GetContext(sp, cReq) var resp *http.Response var err error switch cReq.GetDownloaderID() { case SURF_ID: resp, err = self.surf.Download(cReq) case PHANTOM_ID: resp, err = self.phantom.Download(cReq) } if resp.StatusCode >= 400 { err = errors.New("响应状态 " + resp.Status) } if resp.Header.Get("Content-Encoding") == "gzip" { var gzipReader *gzip.Reader gzipReader, err = gzip.NewReader(resp.Body) resp.Body.Close() if err == nil { resp.Body = ioutil.NopCloser(gzipReader) } } ctx.SetResponse(resp).SetError(err) return ctx }
// 更新或加入失败记录, // 对比是否已存在,不存在就记录, // 返回值表示是否有插入操作。 func (self *Failure) UpsertFailure(req *request.Request) bool { self.RWMutex.Lock() defer self.RWMutex.Unlock() if self.list[req.Unique()] != nil { return false } self.list[req.Unique()] = req return true }
// core processer func (self *crawler) Process(req *request.Request) { var ( ctx = self.Downloader.Download(self.Spider, req) // download page downUrl = req.GetUrl() ) if err := ctx.GetError(); err != nil { // 返回是否作为新的失败请求被添加至队列尾部 if self.Spider.DoHistory(req, false) { // 统计失败数 cache.PageFailCount() } // 提示错误 logs.Log.Error(" * Fail [download][%v]: %v\n", downUrl, err) return } defer func() { if err := recover(); err != nil { if activeStop, _ := err.(string); activeStop == spider.ACTIVE_STOP { return } // 返回是否作为新的失败请求被添加至队列尾部 if self.Spider.DoHistory(req, false) { // 统计失败数 cache.PageFailCount() } // 提示错误 logs.Log.Error(" * Panic [process][%v]: %v\n", downUrl, err) } }() // 过程处理,提炼数据 ctx.Parse(req.GetRuleName()) // 处理成功请求记录 self.Spider.DoHistory(req, true) // 统计成功页数 cache.PageSuccCount() // 提示抓取成功 logs.Log.Informational(" * Success: %v\n", downUrl) // 该条请求文本结果存入pipeline for _, item := range ctx.PullItems() { self.Pipeline.CollectData(item) } // 该条请求文件结果存入pipeline for _, f := range ctx.PullFiles() { self.Pipeline.CollectFile(f) } // 释放ctx准备复用 spider.PutContext(ctx) }
// 生成并添加请求至队列。 // Request.Url与Request.Rule必须设置。 // Request.Spider无需手动设置(由系统自动设置)。 // Request.EnableCookie在Spider字段中统一设置,规则请求中指定的无效。 // 以下字段有默认值,可不设置: // Request.Method默认为GET方法; // Request.DialTimeout默认为常量request.DefaultDialTimeout,小于0时不限制等待响应时长; // Request.ConnTimeout默认为常量request.DefaultConnTimeout,小于0时不限制下载超时; // Request.TryTimes默认为常量request.DefaultTryTimes,小于0时不限制失败重载次数; // Request.RedirectTimes默认不限制重定向次数,小于0时可禁止重定向跳转; // Request.RetryPause默认为常量request.DefaultRetryPause; // Request.DownloaderID指定下载器ID,0为默认的Surf高并发下载器,功能完备,1为PhantomJS下载器,特点破防力强,速度慢,低并发。 // 默认自动补填Referer。 func (self *Context) AddQueue(req *request.Request) *Context { // 若已主动终止任务,则崩溃爬虫协程 self.spider.tryPanic() err := req. SetSpiderName(self.spider.GetName()). SetEnableCookie(self.spider.GetEnableCookie()). Prepare() if err != nil { logs.Log.Error(err.Error()) return self } // 自动设置Referer if req.GetReferer() == "" && self.Response != nil { req.SetReferer(self.GetUrl()) } self.spider.RequestPush(req) return self }
// 删除失败记录 func (self *Failure) DeleteFailure(req *request.Request) { self.RWMutex.Lock() delete(self.list, req.Unique()) self.RWMutex.Unlock() }
// core processer 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) }
func makeUnique(req *request.Request) string { return util.MakeUnique(req.GetUrl() + req.GetMethod()) }