# egg-puppeteer-data **Repository Path**: script-data/egg-puppeteer-data ## Basic Information - **Project Name**: egg-puppeteer-data - **Description**: 一个简单的egg demo 通过puppeteer爬取数据并持久化 - **Primary Language**: NodeJS - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2022-01-15 - **Last Updated**: 2023-06-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 简介 nodejs作为前端开发人员介入后端的首选技术,多年的的发展也诞生了如koa和express等优秀的库。但是每个人的风格不同,包括目录结构,代码组织等各不相同,可能会增加后期维护成本。 于是乎我们可以使用现在已有的成熟的nodejs框架来进行业务开发,便于后期的维护。 现行市面上较为流行的nodejs框架有Nest.js和egg.js #### nest - 依赖注入容器 - 模块化封装 - 使用 Typescript - AOP 代码,面向切面编程 - 内置支持 TypeScript - 可基于Express或者fastify - 个人维护 #### Egg.js的特性 - 提供基于 Egg 定制上层框架的能力 - 高度可扩展的插件机制 - 内置多进程管理 - 基于 Koa 开发,性能优异 - 框架稳定,测试覆盖率高 - 渐进式开发 - alibaba 考虑egg已经经过阿里多年的打磨且使用的项目经受过各种考验。国内使用人员较多,文档较为全面,所以学习了一下egg.js ### 起步 ``` $ mkdir egg-demo && cd egg-demo $ npm init egg --type=simple $ npm i $ npm run dev $ open http://localhost:7001 ``` 至此 你会在7001端口看到 hello egg ### 以一个小爬虫为例 目标:通过 puppeteer 爬取豆瓣 https://movie.douban.com/explore 热门电影的标题图片和链接,存入到mysql #### 目录结构 ``` egg-demo ├── jsconfig.json ├── typings // 类型定义TypedDefinition │ ├── app │ │ ├── controller │ │ │ └── index.d.ts │ │ └── index.d.ts │ └── config │ ├── index.d.ts │ └── plugin.d.ts ├── package-lock.json ├── appveyor.yml ├── README.md ├── package.json ├── app // 主要的业务逻辑所在文件夹 │ ├── public │ ├── router.js │ └── controller │ └── home.js ├── config // 一些配置 │ ├── config.default.js │ └── plugin.js └── test // 测试 └── app └── controller └── home.test.js ``` #### 实现爬虫 - 在app/router.js 增加路由 ``` module.exports = app => { const { router, controller } = app; router.get('/', controller.home.index); + router.get('/runDoubanCrawler', controller.home.runDoubanCrawler); }; ``` - 在app/controller/home.js 增加处理这个路由的业务逻辑 ``` const Controller = require('egg').Controller; class HomeController extends Controller { async index() { const { ctx } = this; ctx.body = 'hello'; } + async runDoubanCrawler() { + const { ctx } = this; + const result = await ctx.service.doubanHot.runDoubanCrawler(); + ctx.body = result; + } } module.exports = HomeController; ``` - 可以看到在runDoubanCrawler中调用的是service 里的doubanHot中的runDoubanCrawler,需要在app/service/ 新建doubanHot.js来 ``` 'use strict'; // 引入puppeteer const puppeteer = require('puppeteer'); const Service = require('egg').Service; class DoubanHotService extends Service { async douban() { // 创建一个browser const browser = await puppeteer.launch({ executablePath: puppeteer.executablePath(), headless: false, }); const douban = 'https://movie.douban.com/explore'; // 打开新页面 const page = await browser.newPage(); await page.goto(douban, { waitUntil: 'networkidle0', }); // 在页面上下文执行js const movieList = await page .evaluate(async dom => { return Array.from($(dom).find('.list a')).map(el => { const $el = $(el); const detailUrl = $el.attr('href'); const picUrl = $el.find('img').attr('src'); const title = $el.find('img').attr('alt'); const item = { detailUrl, picUrl, title }; return item; }); }, 'body') .catch(err => console.log('err', err)); // 关闭页面 await page.close(); return movieList; } async runDoubanCrawler() { return await this.douban(); } } module.exports = DoubanHotService; ``` 现在我们在浏览器打开127.0.0.1:7001/runDoubanCrawler 就可以看到一个json 如下。说明爬取数据已经开发完成 ``` [ { detailUrl: 'https://movie.douban.com/subject/26709258/?tag=热门&from=gaia_video', picUrl: 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2568288336.jpg', title: '罗小黑战记', }, { detailUrl: 'https://movie.douban.com/subject/30372377/?tag=热门&from=gaia', picUrl: 'https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2569548689.jpg', title: '续命之徒:绝命毒师电影', }, ... ] ``` #### 持久化数据 需要使用mysql 和mysql 的orm 框架来对爬取来的数据进行存储 npm install --save egg-sequelize mysql2 ##### 修改doubanHot 中的 runDoubanCrawler 方法 ``` // 调用model中的Douban 批量把 抓取来的 list插入数据库 async runDoubanCrawler() { const list = await this.douban(); // 数据批量存入Douban 表中 await this.ctx.model.Douban.bulkCreate(list); return { msg: '抓取成功~', }; } ``` ##### 在model层定义Douban表,在app/service/ 新建doubanHot.js ``` module.exports = app => { const { STRING, INTEGER } = app.Sequelize; const Douban = app.model.define('douban', { id: { type: INTEGER, primaryKey: true, autoIncrement: true }, detailUrl: STRING(128), picUrl: STRING(128), title: STRING(30), add_time: { type: STRING, defaultValue: new Date().getTime(), }, }); return Douban; }; ``` ##### 添加sequelize插件 在config/plugin中添加如下代码 ``` exports.sequelize = { enable: true, // 表示开启 package: 'egg-sequelize', // 包名 }; ``` ##### 在config中配置 在config/config.default.js 添加config配置 ``` config.sequelize = { dialect: 'mysql', // support: mysql, mariadb, postgres, mssql database: 'egg', host: '127.0.0.1', password: 'password', port: 3306, }; ``` ##### 在文件根目录建app.js 作用是在应用启动时增加钩子 ``` // egg钩子 用来初始化一些操作 class AppBootHook { constructor(app) { this.app = app; } async serverDidReady() { // 当mysql表不存在的时候自动创建表 await this.app.model.sync({ force: false }); } } module.exports = AppBootHook; ``` 当前在浏览器中输入 127.0.0.1:7001/runDoubanCrawler 几秒钟后就能看到如下 ``` { msg: '抓取成功~', } ``` #### 总结 egg通过对文件的强制拆分和写法的固定给了我们一套规则,在维护的时候成本更低。 具体使用细节官方文档已经很清楚。该文只是使用事例。实际应用还是要复杂一些。 当然egg非常健壮,它还可以完成更多功能。包括中间件的使用,安全,异常处理,进程通讯,定时任务等... ### 链接 - egg https://eggjs.org/ - puppeteer https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md