URL发送请求里跑的爬虫程序怎么停止?

大部分时候我在node下做爬虫爬大量数据还是用脚本方式,图的是方便。在命令行下开启可以通过node xxx.js,停止可以control+c退出。

但是如果是给客户使用,那还是得把爬虫任务放在url请求的响应操作时进行,这样可以方便的在浏览器中开启任务。

但是将爬虫任务放在web应用时,会有一个问题,即任务如何取消?

可以试想一下,如果不去思考这个问题的爬虫程序会遇到什么问题:用户通过url发送一次请求到服务器,爬虫任务就开始执行,但如果这个爬虫还未结束,url又发送请求一次(也许是误操或其他原因),那就会有两个爬虫的进程同时进行。

这会大大浪费服务器的资源,而且容易发生意料之外的错误令程序不那么健壮。

当然了,可以通过一些方法去限制开启重复的爬虫任务(包括其他耗时的任务)。譬如设个开关、改造一下请求,等等,但都算不得上正统的方法,可以算作“歪门邪道”吧。

我查阅了一下node的资料,发现正统的方法是使用node的API process进程来实现。

以下代码通过KOA框架的示例代码实现:

首先我在根目录下模拟了一个耗时约100秒的任务task.js:

for (let i = 0; i < 100; i++) {
  setTimeout(()=>{
    console.log(`task ${i} is on`)
  },i*1000)
}

然后,在请求路由的回调函数中,用child_process (关于child_process有很多资料可以从网上查阅)来执行这个任务脚本:

const child_process = require('child_process')

router.get('/', (ctx, next) => {
  child_process.fork('task.js')
  ctx.body = 'task started'
})

这个时候前一个任务没结束时如果再次请求url就会遇到我们担心的情况:多个任务会同时运行,这个时候我们利用另一个API process(同样,process可以查阅相关文档介绍)来kill掉之前进程,并稍微写点判断逻辑:

const child_process = require('child_process')
const process = require('process')

let task = null
// 用来存储全局任务
router.get('/', (ctx, next) => {
  if (!task) {
    //没有任务时
    console.log('no task')
    task = child_process.fork('task.js')
    //启动脚本,将返回值赋值到全局任务变量中
  } else {
    console.log(task.pid)
    process.kill(task.pid)
    //存储在全局任务变量中的pid是进程id,kill掉
    task = child_process.fork('task.js')
    //重新启动脚本
  }
  ctx.body = 'task started'
})

至此,我们的需求就用这样的方式实现。每一次发送请求都会先中断正在进行(如果有)的爬虫任务,并重头开启一个新的爬虫任务。

完整的示例代码在我的github上https://github.com/18978909244/task-restart-demo