海量数据爬虫效率优化之:多线程

上一篇文章也提到了,我最近写了一个百万级数据的爬虫程序。第一次写完业务逻辑时,测试了一下总计耗时大约在40个小时左右。

那意味着无论是程序出错或者譬如新的需求(如之后需要新增一个数据),都要耗费大量的时间调试和运行,这简直到了无法容忍的程度。

于是我花了不少时间进行性能效率上的优化,今天要讲的是其中很重要的:多线程。

我是用Node写的爬虫程序。Node.js是单线程的Event Loop,所以不能像其他语言使用多线程的方案,但我们可以通过代码模拟多线程:

譬如我已经写好一个爬虫方法run并暴露出去:

module.exports = async (url)=>{
    // ...单个爬虫方法
}

通常爬虫会有如此的编写逻辑:

const run = require('run')
const task = async (urls)=>{
    for(let i = 0;i<urls.length;i++){
        await run(urls[i])
    }
}

let urls = [...]  //待爬列表

task(urls)

这个时候爬虫任务task会遍历urls数组,通过一个个url把数据爬下。因为await 会让爬虫处于“待机”状态,所以当urls数组特别大,这个任务的执行会没法发挥全部效率。

这时我想到了可以把urls数组切割成若干个平行任务,每个任务按量平均分配urls数组的若干个作为独立的爬虫任务。

于是我把爬虫任务改写成:

const run = require('run')
const task = async (urls)=>{
  /*
  * 核心代码
  */
  let taskNum = 20
  let total = urls.length
  let each = Math.ceil(total / taskNum)

  for(let i = 0;i<taskNum;i++){
      run(urls.slice(i * each + 0, (i + 1) * each))
  }
}

let urls = [...]  //待爬列表

task(urls)

以上3行核心代码是设置开启“多线程”的数量taskNum,并将待爬urls分割成taskNum个爬虫任务。

我示例代码里taskNum设置为20,可以根据实际场景来设置更多或更少的“多线程”任务。

这样一来,后来的测算用20个多线程,能提高整体效率大约10倍的时间(想提高20倍是理论情况,实际上受各种因素影响是达不到的),这大大加强了爬虫的性能效率。

这是其中一个性能优化的逻辑,明天会将到断点续传,敬请期待吧!