likes
comments
collection
share

Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬

作者站长头像
站长
· 阅读数 46

大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬虫系统,有时候我们想要一些测试数据。自己去造数据又非常麻烦。怎么办呢?今天就给大家介绍一个比较好的办法,写一个爬虫工具。

项目初始化

一、安装 koa生成器依赖

npm i koa-generator -g

二、创建koa2 项目

koa2 crawler

三、切换目录

cd crawler

四、安装依赖

npm i

五、安装 puppeteer

npm i puppeteer -S

  1. 运行项目

npm run dev

如果npm 的版本是6点几的版本,项目应该能正常运行, 如果npm 是比较高的版本会报一个错, '.'不是一个命令。 这时需要改动下package.json 中script中的dev 值改成nodemon bin/www

原来的: "dev": "./node_modules/.bin/nodemon bin/www", 现在的:"dev": "nodemon bin/www",

代码编写

一、打开rotues文件夹下面的index.js

二、删掉无用的路由

三、导入puppeteer

const pt = require('puppeteer')

添加路由方法:

router.get('/xiaomi', async (ctx, next) => {
  ctx.body = {
    code: 200,
    data: []
  }
})

创建一个无头浏览器:const bs = await pt.launch(),

声明一个保存需要爬取的页面地址: url = 'https://www.mi.com/shop',

打开一个tab: pg = await bs.newPage()

加载指定页面: await pg.goto(url)

调用evaluate 方法,evaluate 方法接收一个函数作为参数,这个函数里面相当于浏览器环境,可以通过dom 操作获取指定网页的数据。

evaluate方法完整实现:

const result = await pg.evaluate(() => {
      const data = []
      const list = document.querySelectorAll('.brick-list li')
      for (var i = 0; i < list.length; i++) {
        const imgDom = list[i].querySelector('.figure img'),
              titleDom = list[i].querySelector('h3'),
              descDom = list[i].querySelector('.desc'),
              hrefDom = list[i].querySelector('a')
        if (list[i].querySelector('.figure img')) {
          data.push({
            img: imgDom ? imgDom.getAttribute('data-src') : '',
            title: titleDom ? titleDom.innerHTML : '',
            desc: descDom ? descDom.innerHTML : '',
            href: hrefDom ? hrefDom.getAttribute('href') : ''
          })
        }
      }
      return data
    })

关闭浏览器: await bs.close()

响应数据到前端:

ctx.body = {
      code: 200,
      data: result
    }

访问 http://localhost:3000/xiaomi 查看效果:

Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬

可以看到数据爬取到了。效果是实现了,那么我们的代码实现有没有问题呢?

我们现是在主进程中完成的爬取逻辑,是不太合理的,因为一个项目都会有多个api 请求,这样很容易阻碍其他方法和请求的运行。

性能优化-简单抽取子进程

新建puppeteer 文件夹 新建index.js

内容如下:

const pt = require('puppeteer')

;(async function () {
  const bs = await pt.launch(),
    url = 'https://www.mi.com/shop',
    pg = await bs.newPage()
    await pg.goto(url)
    const result = await pg.evaluate(() => {
      const data = []
      const list = document.querySelectorAll('.brick-list li')
      for (var i = 0; i < list.length; i++) {
        const imgDom = list[i].querySelector('.figure img'),
              titleDom = list[i].querySelector('h3'),
              descDom = list[i].querySelector('.desc'),
              hrefDom = list[i].querySelector('a')
        if (list[i].querySelector('.figure img')) {
          data.push({
            img: imgDom ? imgDom.getAttribute('data-src') : '',
            title: titleDom ? titleDom.innerHTML : '',
            desc: descDom ? descDom.innerHTML : '',
            href: hrefDom ? hrefDom.getAttribute('href') : ''
          })
        }
      }
      return data
    })
    // console.log(result)
    process.send(result)
    await bs.close()
    
    setTimeout(() => {
      process.exit(0)
    })
})();

注意: 大部分逻辑还是之前的逻辑, 新增了两个不一样的, 数据获取到之后 利用process.send(result) 发送到主进程。process.exit(0) 退出子进程,主进程能收到相关消息。

主进程:routes index.js

导入const cp = require('child_process') 处理主进程和子进程之间的消息传递, 导入const { resolve } = require('path') 处理路径相关

请求方法修改:

router.get('/xiaomi', async (ctx, next) => {
  const script = resolve(__dirname, '../puppeteer/crawler.js')
  const child = cp.fork(script, [])

  let invoke = false
  child.on('message', (data) => {
    ctx.body = {
      code: 200,
      data: data
    }
  })

  child.on('exit', (code) => {
    if (invoke) {
      return
    }
    console.log(code, 'exit')
    invoke = true
  })

  child.on('error', (error) => {
    if (invoke) {
      return
    }
    invoke = true
  })
})

删除了之前的爬取逻辑改成引入了。添加了message, exit,error的事件监听。 现在来访问下http://localhost:3000/xiaomi 看下效果

Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬

会发现变成了Not Found,问题出在哪里呢?

主要出现在message 这个方法这个地方,ctx 写在message 的方法监听里面是不行的。因为页面已经提返回了。怎么办呢? 可以利用Promise和await 进行改造下

来看下改造后的代码:

const data = await new Promise((resolve, reject) => {
    child.on('message', (data) => {
      console.log(ctx, 'message')
      resolve(data)
    })
  })

  ctx.body = {
    code: 200,
    data: data
  }

现在再来访问下http://localhost:3000/xiaomi 看下效果

Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬

发现正常了。

基本功能实现了,但是回过头来看看,代码写得还是很乱,没有层次感。所以接下来就要着重优化下代码,将爬取逻辑封装,将子进程方法封装,controllers 控制器封装。

代码层次优化,封装子进程方法,爬虫方法

  1. 新建libs 文件夹
  2. libs 新建crawler.js 封装爬虫方法
  3. libs 新建utils.js 封装子进程方法
  4. 新建crawlers文件夹 用于存放具体爬虫的业务文件
  5. 新建milist.js 用于存放爬起小米网站的逻辑
  6. 新建controllers 文件夹存放控制器
  7. controllers 下新建Crawler.js 存放爬虫控制器
  8. 修改routes下面的index.js文件名为crawler.js
  9. 修改app.js 中 index 为crawler
  10. 访问 http://localhost:3000/crawler/xiaomi 看效果

先来看看utils.js 方法的封装:

const cp = require('child_process')
const { resolve } = require('path')

module.exports = {
  stratProcess (options) {
    const script = resolve(__dirname, options.path)
    const child = cp.fork(script, [])

    let invoke = false

    child.on('message', (data) => {
      options.message(data)
    })

    child.on('exit', (code) => {
      if(invoke) {
        return
      }
      invoke = true
      options.exit(code)
    })

    child.on('error', (err) => {
      if(invoke) {
        return
      }
      invoke = true
      options.error(err)
    })
  }
}

爬虫方法的封装:

const pt = require('puppeteer')

module.exports = async (options) => {
  const bs = await pt.launch(),
        url = options.url,
        pg = await bs.newPage()
  await pg.goto(url)
  const result = await pg.evaluate(options.cb)
  await bs.close()
  process.send(result)

  setTimeout(() => {
    process.exit(0)
  }, 1000)
}

crawlers/milist.js 的实现

const crawler = require('../libs/crawler')

crawler({
  url: 'https://www.mi.com/shop',
  cb () {
    const data = []
      const list = document.querySelectorAll('.brick-list li')
      for (var i = 0; i < list.length; i++) {
        const imgDom = list[i].querySelector('.figure img'),
              titleDom = list[i].querySelector('h3'),
              descDom = list[i].querySelector('.desc'),
              hrefDom = list[i].querySelector('a')
        if (list[i].querySelector('.figure img')) {
          data.push({
            img: imgDom ? imgDom.getAttribute('data-src') : '',
            title: titleDom ? titleDom.innerHTML : '',
            desc: descDom ? descDom.innerHTML : '',
            href: hrefDom ? hrefDom.getAttribute('href') : ''
          })
        }
      }
      return data
  }
})

controllers/Crawler.js 爬虫控制器方法实现:

const { stratProcess } = require('../libs/utils')
class Crawler {
  async crawGetmiList (ctx) {
    await new Promise((resolve, reject) => {
      stratProcess({
        path: '../crawlers/milist',
        async message (data) {
          resolve(data)
          ctx.body = {
            code: 200,
            data: data
          }
        },
        async exit(code) {
          console.log(code)
        },
        async error(err) {
          console.log(err)
        }
      })
    })
    
  }
}

module.exports =  new Crawler()

routes/crawler.js 实现:

const router = require('koa-router')()
const crawler = require('../controllers/Crawler.js')
router.prefix('/crawler')

router.get('/xiaomi', crawler.crawGetmiList)
module.exports = router

app.js 的实现:

const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const crawler = require('./routes/crawler')
const cors = require('koa2-cors')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(cors())
app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(crawler.routes(), crawler.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

访问http://localhost:3000/crawler/xiaomi 查看效果:

Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬

可以看到结果也正常返回。结果是和之前一样,但是代码的层级结果清晰了很多,以后维护也会非常方便。

Koa2 + Puppeteer 开发一个爬虫系统 的分享就到这了,感谢收看,一起学习一起进步

转载自:https://juejin.cn/post/7409138396792782882
评论
请登录