# koa_puppeteer **Repository Path**: sakakokiya1/koa_puppeteer ## Basic Information - **Project Name**: koa_puppeteer - **Description**: 使用Koa2,Puppeteer 实现爬虫功能 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2023-01-21 - **Last Updated**: 2023-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 第一版 ## 1. 声明路由 - 添加路由前缀 ```apl router.prefix('/crawler'); ``` - 声明路由 ```apl router.get('/crawl_slider_data', crawlerController.crawlSliderData); ``` ## 2. 控制器 - 进入路由`http://localhost:3000/crawler/crawl_slider_data` - 调用`controller`中的方法`crawlSliderData` - 控制器中的`crawlSliderData`方法主要作用是调用`子进程` - 控制器 ```js const { startProcess } = require('../libs/utils'); // 引入子进程 class Crawler { crawlSliderData(){ startProcess({ path: '../crawlers/slider', // 子进程所在路径 async message(data){ // 获取进程中send出的数据内容 console.log(data); }, async exit(code){ // 进程退出 console.log(code); }, async error(error){ // 错误抛出 console.log(error); } }) } } module.exports = new Crawler(); ``` ## 3. 启动进程方法 - `startProcess`方法 - 传入一个对象 `options` - 为上面传入的对象 ```js const cp = require('child_process'), // 引入子进程child_process { resolve } = require('path'); module.exports = { startProcess(options) { const script = resolve(__dirname, options.path), // 找到子进程 ../crawlers/slider child = cp.fork(script, []); // 执行脚本 // 子进程是否被调用了 let invoked = false; // 如果进程发送了信息(send信息) 则在message中会收到 child.on('message', (data) => { options.message(data); }); child.on('exit', (code) => { // 被调用 直接return if(invoked) { return; } invoked = true; options.exit(code); }); child.on('error', (err) => { if(invoked){ return; } invoked = true; options.error(err); }) } } ``` ## 4. 子进程 ```js const crawler = require('../libs/crawler') // 引入封装好的puppeteer // 传入需要爬取的网页路径 回调函数手机数据返回出来 crawler({ url: 'https://msiwei.ke.qq.com/#category=-1&tab=0', callback(){ const $ = window.$, $item = $('.agency-big-banner-ul .agency-big-banner-li'); // 声明接收数据的数组 const data = []; $item.each((index, item) => { const $el = $(item), $elLink = $el.find('.js-banner-btnqq'); // 获取a链接 const dataItem = { cid: $elLink.attr('data-id'), href: $elLink.prop('href'), // 获取a链接的跳转地址 title: $elLink.prop('title'), // 获取title属性 imgUrl: $elLink.find('img').prop('src') // 获取图片地址 }; data.push(dataItem); }); return data; } }) ``` ## 5. 封装的puppeteer ```js const pt = require('puppeteer'); module.exports = async function(options) { const bs = await pt.launch(), // 启动浏览器 pg = await bs.newPage(), // 打开一个页面 url = options.url; // 等待去url 状态 为networkidle2 await pg.goto(url, { waitUntil: 'networkidle2' }); const result = await pg.evaluate(options.callback); // callback 中返回出来爬取的数据 await bs.close(); // 关闭浏览器 process.send(result); // 进程将结果sned出去 // 最后关闭进程 setTimeout(() => { process.exit(0); }, 1000); } ``` ------ **新建分支保存第一版** - 完成轮播图部分数据爬取 ------ ## 第二版 ## 6. 新建配置文件 - 配置七牛账户 ```js module.exports = { // 七牛账户配置 qiniu: { keys: { // 秘钥管理中的ak sk ak: 'Jkt4cUXADFOlFRFrK3AUGLsP_gKEmIeNcbzVQSxj', sk: '7gtu3n6vekAHMbBrCrbeRRFOatpD5b2By46OpdHz', }, bucket: { // 可能存在多个空间 tximg: { bucket_name: 'gtj-txclass-img', // 空间名 domain: 'http://tximg.gaotengjun.fun/', // 加速域名 } } } } ``` ## 7. 安装nanoid qiniu ```nim npm i nanoid qiniu -S # 生产环境 ``` ## 8. 封装上传七牛云的方法 ```js // 七牛上传 https://developer.qiniu.com/kodo/1289/nodejs 下载文件部分 qiniuUpload(options){ const mac = new qiniu.auth.digest.Mac(options.ak, options.sk), conf = new qiniu.conf.Config(), // 在使用 Node.js SDK 中的FormUploader和ResumeUploader上传文件之前,必须要构建一个上传用的config client = new qiniu.rs.BucketManager(mac, conf), key = nanoid() + options.ext; // 文件名 return new Promise((resolve, reject) => { // 抓取资源到空间 将网络图片的url添加到对应空间,并赋值名字为key client.fetch(options.url, options.bucket, key, (error, ret, info) => { if(error){ reject(error); }else{ if(info.statusCode === 200){ // 上传成功获取文件名 resolve({key}); // console.log(ret); }else{ reject(info); console.log(ret, info); } } }); }) ``` ## 9. 封装数据为保存sql数据做准备 - 将爬取数据的对象添加一项`imgKey`上传后的文件名 1. **遍历数据** 2. **目的是将网络资源图片上传七牛,并修改上传的图片名** ```js const { startProcess, qiniuUpload } = require('../libs/utils'), config = require('../config/config') class Crawler { crawlSliderData(){ startProcess({ path: '../crawlers/slider', async message(data){ data.map(async item => { // 判断存在图片地址, 并且图片名为空 还未修改 if(item.imgUrl && !item.imgKey){ const qiniu = config.qiniu; try{ // 网络资源可能报错使用try const imgData = await qiniuUpload({ url: item.imgUrl, bucket: qiniu.bucket.tximg.bucket_name, ak: qiniu.keys.ak, sk: qiniu.keys.sk, ext: '.jpg', // 文件后缀 }); // console.log(imgData); // 上传结果返回的是文件名, 文件名存在的话返回成功,重新赋值到data中 console.log(imgData.key); if(imgData.key){ item.imgKey = imgData.key; } console.log(item); }catch(e){ console.log(e); } } }); console.log(data); }, async exit(code){ console.log(code); }, async error(error){ console.log(error); } }) } } module.exports = new Crawler(); ``` **完成第二部分** - 实现网络图片上传七牛云,并获取图片地址等信息 ## 第三版 ## 10. 安装依赖 ```js npm i mysql2 sequelize -S ``` ## 11. 新建数据库配置文件 ```js module.exports = { mysql: { base: { host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, idle: 10000 // 10s } }, // 数据库名 用户名 密码 conf: ['txclass', 'root', 'root'] } } // const Sequelize = require('sequelize'); // const seq = new Sequelize('txclass', 'root', 'root', { // host: 'localhost', // dialect: 'mysql', // pool: { // 连接池 多人操作不同线程 // max: 5, // 最大连接数 // min: 0, // 最小连接数 // idle: 10000 // 超时时间 // } // }); ``` ## 12. 根据配置文件连接数据库并测试 ```js const Sequelize = require('sequelize'), { mysql } = require('../../config/db_config'); const seq = new Sequelize(...mysql.conf, mysql.base); seq.authenticate().then(res => { console.log('MySQL server is connected completely.'); }).catch(error => { console.log('MySQL server is filed to be connected. Error information is below ' + error); }); module.exports = seq; ``` - 运行文件测试是否连接成功 ## 13. 构建数据库表中的模型 - 在配置文件创建数据库字段类型配置文件 - `db_type_config` ```js // 数据库字段类型 配置文件 const Sequelize = require('sequelize'); module.exports = { STRING: Sequelize.STRING, // 字符串 INT: Sequelize.INTEGER, // 整形 } ``` - 创建`slide`表模型 ```js const seq = require('../connection/mysql_connect'), { STRING, INT } = require('../../config/db_type_config'); // 建表 // 使用seq示例建slider表 const Slider = seq.define('slider', { cid: { comment: 'courde ID', // 注释 type: STRING, // 类型 allowNull: false, // 是否允许为空 unique: true, // 是否唯一 }, href: { comment: 'course detail page link', type: STRING, // 类型 allowNull: false, // 是否允许为空 }, imgUrl: { comment: 'course image url', type: STRING, // 类型 allowNull: false, // 是否允许为空 }, title: { comment: 'course name', type: STRING, // 类型 allowNull: false, // 是否允许为空 }, imgKey: { comment: 'qiniu image name', type: STRING, // 类型 allowNull: false, // 是否允许为空 }, status: { comment: 'course status', type: INT, // 类型 defaultValue: 1, // 默认值 allowNull: false, // 是否允许为空 } }); module.exports = Slider; ``` - 模型下创建`index.js`用于全部模型的引入 ```js // 用于导出所有的模型 const Slider = require('./slider'); module.exports = { Slider } ``` - 将模型同步到数据库中 ```js // 同步数据库 const seq = require('./connection/mysql_connect'); // 引入模型 require('./models') // 同步数据库时 才执行的代码 seq.authenticate().then(res => { console.log('MySQL server is connected completely.'); }).catch(error => { console.log('MySQL server is filed to be connected. Error information is below ' + error); }); seq.sync({ force: true, }).then(() => { console.log('The tabel has been synchronised int odatabase successfully'); // 关闭同步进程 process.exit(); }) ``` - 手动运行`sync.js`查看打印结果判断是否同步成功 ## 14. 创建services文件夹用于操作对应表 - 创建`Slider.js`对应`sliders`表 ```js const SliderModel = require('../db/models/slider'); class SliderService { // 添加数据 async addSliderData(data){ return await SliderModel.create(data); } } // 导出类的实例 module.exports = new SliderService(); ``` - 在控制器中获取到数据后添加到数据库中 ```js const { startProcess, qiniuUpload } = require('../libs/utils'), { addSliderData } = require('../services/Slider'), config = require('../config/config'); class Crawler { crawlSliderData(){ startProcess({ path: '../crawlers/slider', async message(data){ data.map(async item => { // 判断存在图片地址, 并且图片名为空 还未修改 if(item.imgUrl && !item.imgKey){ const qiniu = config.qiniu; try{ // 网络资源可能报错使用try const imgData = await qiniuUpload({ url: item.imgUrl, bucket: qiniu.bucket.tximg.bucket_name, ak: qiniu.keys.ak, sk: qiniu.keys.sk, ext: '.jpg', // 文件后缀 }); // console.log(imgData); // 上传结果返回的是文件名, 文件名存在的话返回成功,重新赋值到data中 console.log(imgData.key); if(imgData.key){ item.imgKey = imgData.key; } // 将数据添加到数据库中 ============================== 添加数据 ========================================================== const result = await addSliderData(item); if(result){ console.log('data create Ok'); }else{ console.log('fail data'); } }catch(e){ console.log(e); } } }); console.log(data); }, async exit(code){ console.log(code); }, async error(error){ console.log(error); } }) } } module.exports = new Crawler(); ``` ## 剩余内容见语雀 https://www.yuque.com/u21664855/fduotk/rb0g3g/edit?toc_node_uuid=GoMywoDT2fW4m2Zb ## 工程师项目二 ## 1. 安装Redis依赖 ```css npm i redis -S ``` ## 2. 安装操作Redis依赖 - `koa-redis` - `Koa2`操作`redis`依赖 - `koa-generic-session` - `Koa2`操作`session` ```css npm i koa-redis koa-generic-session -S ```