一、架构设计

1、从网络上获取数据的Fetcher是最费时的
2、Fetcher的输出是Parser的输入,因此整合为Worker。系统启用多个worker,并发地从网络上获取内容并解析
3、worker并发以后,多个request如何分配到多个worker上,因此增加Scheduler模块,将请求任务调度到Worker上
4、Engine是一个routine,Scheduler是一个routine,Worker是多个routine,它们之间用channel进行通信

二、简单调度器的循环等待问题

调度器是engine的一部分,engine通过函数调用的参数传递直接将request提交到worker。
1、engine把种子任务(http://www.zhenai.com/zhenghun/)通过in channel传递到10个worker中去,同时某个空闲的worker(其实全部空闲)接收到了该任务
2、某个worker完成种子任务,向out channel传递结果(结果中包含新的任务),同时空闲的engine接收到了一大堆新的任务(解析出来的470个城市列表)
3、engine要把这470个任务通过in channel传到给worker,然而只有10个worker,只能接收10个任务
4、engine处于忙态,还在发送剩下的460个任务,它们在in channel上等待worker接收;十个worker们也处于忙态,处理完了之前的任务,正在往out channel传递结果并等待engine接收
//engine处理结果并提交所有任务
for {
       result := <- out for _, item := range result.Items {
          log.Printf("Got item: %v\n", item)
       }  for _, r := range result.Requests {
         ce.Scheduler.Submit(r)
       }
}

//worker接收任务、处理任务、返回结果
go func() { for {
      request := <- in
      parseResult, err := worker(request) if err != nil { continue  }
      out <- parseResult
   }
}()

三、解决循环等待的简单方法

循环等待的原因在于engine的scheduler要提交完所有任务才会返回,开始接收out channel。
最简单的办法是将提交任务的动作开一个协程,engine开完协程就可以直接返回了,很快就能接收out channel。
func (se *SimpleScheduler) Submit(r engine.Request) { go func() {
      se.WorkerChannel <- r
    }() }
因此,有多少个任务请求,就需要开多少个协程!

四、突破反爬机制

开10个worker的情况下,珍爱网就把用户页面的请求给截住了,直接返回403,要等一段时间才能正常访问。
也许是因为访问得太频繁了,网站检测到异常行为,把我的IP给暂时禁止了。
突破反爬的方法有:虚拟IP、更换user-agent等;最简单的是调整访问频率(RateLimitter)
现在还没有尝试。