Koa2 + Puppeteer 开发一个爬虫系统大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬
大家好!今天给大家介绍的是Koa2 + Puppeteer 开发一个爬虫系统,有时候我们想要一些测试数据。自己去造数据又非常麻烦。怎么办呢?今天就给大家介绍一个比较好的办法,写一个爬虫工具。
项目初始化
一、安装 koa生成器依赖
npm i koa-generator -g
二、创建koa2 项目
koa2 crawler
三、切换目录
cd crawler
四、安装依赖
npm i
五、安装 puppeteer
npm i puppeteer -S
- 运行项目
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 查看效果:
可以看到数据爬取到了。效果是实现了,那么我们的代码实现有没有问题呢?
我们现是在主进程中完成的爬取逻辑,是不太合理的,因为一个项目都会有多个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
看下效果
会发现变成了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
看下效果
发现正常了。
基本功能实现了,但是回过头来看看,代码写得还是很乱,没有层次感。所以接下来就要着重优化下代码,将爬取逻辑封装,将子进程方法封装,controllers 控制器封装。
代码层次优化,封装子进程方法,爬虫方法
- 新建libs 文件夹
- libs 新建crawler.js 封装爬虫方法
- libs 新建utils.js 封装子进程方法
- 新建crawlers文件夹 用于存放具体爬虫的业务文件
- 新建milist.js 用于存放爬起小米网站的逻辑
- 新建controllers 文件夹存放控制器
- controllers 下新建Crawler.js 存放爬虫控制器
- 修改routes下面的index.js文件名为crawler.js
- 修改app.js 中 index 为crawler
- 访问 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 开发一个爬虫系统 的分享就到这了,感谢收看,一起学习一起进步
转载自:https://juejin.cn/post/7409138396792782882