Skip to content

eggjs 框架 主要功能1. 定时爬虫ReadHub 2. 转接ReadHub api接口 提供给News小程序使用

Notifications You must be signed in to change notification settings

richard1015/egg-example

Repository files navigation

eggTest

最近做了一款 高仿ReadHub小程序 微信小程序 canvas 自动适配 自动换行,保存图片分享到朋友圈 https://github.com/richard1015/News

具体代码已被开源,后续我会继续更新,欢迎指正

https://github.com/richard1015/egg-example

https://gitee.com/richard1015/egg-example

你可能会像我一样,平常对科技圈发生的热点新闻很感兴趣。每天利用刚打开电脑的时候,又或者是工作间隙,浏览几个更新及时的科技资讯网站。但是,科技圈每天发生的热点新闻就那么几件。看来看去,新闻的重复度高,硬广软文还看了不少。不仅浪费时间,还抓不住重点。

ReadHub 通过爬虫各种科技新闻 大数据过滤筛选 (个人猜想,大概是这一个意思),所以自己写个爬虫把数据爬到自己mysql数据库中

代码思路:

通过网上各种调用 动态网站数据 爬虫分为两种解决方案

1.模拟浏览器请求 使用 相应框架 比如:Selenium、PhantomJs。

2.精准分析页面,找到对应请求接口,直接获取api数据。

优点:性能高,使用方便。我们直接获取原数据接口(换句话说就是直接拿取网页这一块动态数据的API接口),肯定会使用方便,并且改变的可能性也比较小。 缺点:缺点也是明显的,如何获取接口API?有些网站可能会考虑到数据的安全性,做各种限制、混淆等。这就需要看开发者个人的基本功了,进行各种分析了。 我个人在爬取ReadHub时,发现《热门话题》 列表是 无混淆,所以找到请求规律,爬取成功 ,剩下 开发者资讯、科技动态、区块链快讯、招聘行情 请求index被混淆,所以暂无成功。 我在本次采用第二种解决方案 chrome浏览器分析

1.使用chrome 调试工具 Mac 按 alt + cmd+ i Windows 按 F12 或者 右键检查 或 审查元素 找到Network 选中 xhr模块

预览1

可通过图片中看到 每次滚动加载数据时 都会有api请求数据, 我们发现 下次触发滚动加载时,的lastCursor的值 为 上次请求的 数组中最后一个对象中的order值

所以我们发现 只是的请求 url地址为 https://api.readhub.me/topic?lastCursor=53058&pageSize=20 中 的lastCursor 动态设置,即可完成抓取数据

预览2

那么接下来 我们需要 建立mysql数据库

CREATE DATABASE `news` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;
CREATE TABLE `news` (
  `id` varchar(11) COLLATE utf8_bin NOT NULL,
  `order` double NOT NULL,
  `title` varchar(200) COLLATE utf8_bin NOT NULL,
  `jsonstr` json DEFAULT NULL,
  `createdAt` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `updatedAt` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `insertTime` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

然后就是编写 nodejs 中代码逻辑 我在下面的抓取冲采用 eggjs 框架中的 egg-mysql 进行连接数据库 https://eggjs.org/zh-cn/tutorials/mysql.html#egg-mysql

使用定时任务来 执行爬取数据

1.news.service 中代码实现

// app/service/news.js
const Service = require('egg').Service;

class NewsService extends Service {
    async list(pageIndex = '', pageSize = '20') {
        try {
            // read config
            const { serverUrl } = this.config.readhub;
            // 热门话题
            const topic = `${serverUrl}topic?lastCursor=${pageIndex}&pageSize=${pageSize}`;
            // use build-in http client to GET hacker-news api
            const result = await this.ctx.curl(topic,
                {
                    dataType: 'json',
                }
            );
            if (result.status === 200) {
                return result.data;
            }
            return [];
        } catch (error) {
            this.logger.error(error);
            return [];
        }
    }
    async saveDB(list) {
        try {
            const newsClient = this.app.mysql.get("news");
            list.data.forEach(item => {
                // 插入
                newsClient.insert('news', {
                    id: item.id,
                    order: item.order,
                    title: item.title,
                    jsonstr: JSON.stringify(item),
                    createdAt: new Date(item.createdAt).getTime(),
                    updatedAt: new Date(item.updatedAt).getTime(),
                }).then(result => {
                    // 判断更新成功
                    const updateSuccess = result.affectedRows === 1;
                    this.logger.info(item.id + " > " + updateSuccess);
                }).catch(error => {
                    //入库失败错误机制触发
                    this.app.cache.errorNum += 1;
                })
            });
        } catch (error) {
            this.logger.error(error);
        }
    }
}

module.exports = NewsService;

2.定时任务代码实现

update_cache.js

const Subscription = require('egg').Subscription;

class UpdateCache extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      interval: '5s', // 隔单位 m 分 、  s 秒、  ms  毫秒 
      type: 'all', // 指定所有的 worker 都需要执行
      immediate: true, //配置了该参数为 true 时,这个定时任务会在应用启动并 ready 后立刻执行一次这个定时任务。
      disable: false//配置该参数为 true 时,这个定时任务不会被启动。
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
    let ctx = this.ctx;
    ctx.logger.info('update cache errorNum  = ' + ctx.app.cache.errorNum);
    // errorNum 当错误数量 > 50时 停止抓取数据
    if (ctx.app.cache.errorNum > 50) {
      ctx.logger.info('errorNum > 50 stop ');
      return;
    }
    ctx.logger.info('update cache begin ! currentLastCursor = ' + ctx.app.cache.lastCursor);
    const pageIndex = ctx.app.cache.lastCursor || '';
    const pageSize = '20';
    const newsList = await ctx.service.news.list(pageIndex == 1 ? '' : pageIndex, pageSize);
    if (newsList.data.length == 0) {
      //没有数据时错误机制触发
      this.app.cache.errorNum += 1;
      ctx.logger.info('no data stop ! currentLastCursor = ' + ctx.app.cache.lastCursor);
    } else {
      ctx.service.news.saveDB(newsList)
      ctx.app.cache.lastCursor = newsList.data[newsList.data.length - 1].order;
      ctx.logger.info('update cache end ! currentLastCursor set  = ' + ctx.app.cache.lastCursor);
    }
  }
}

module.exports = UpdateCache;

update_cache_init.js

const Subscription = require('egg').Subscription;

class UpdateCacheInit extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      interval: 60 * 24 + 'm', // 隔单位 m 分 、  s 秒、  ms  毫秒 
      type: 'all', // 指定所有的 worker 都需要执行
      immediate: true, //配置了该参数为 true 时,这个定时任务会在应用启动并 ready 后立刻执行一次这个定时任务。
      disable: false//配置该参数为 true 时,这个定时任务不会被启动。
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
    let ctx = this.ctx;
    ctx.logger.info('update cache init');
    if (ctx.app.cache.errorNum > 50) {
      //初始化内置缓存
      ctx.app.cache = {
        lastCursor: '',
        errorNum: 0 //错误数量
      };
    }
  }
}

module.exports = UpdateCacheInit;

项目运行图

预览3 预览4

QuickStart

see egg docs for more detail.

Development

$ npm i
$ npm run dev
$ open http://localhost:7001/

Deploy

$ npm start
$ npm stop

npm scripts

  • Use npm run lint to check code style.
  • Use npm test to run unit test.
  • Use npm run autod to auto detect dependencies upgrade, see autod for more detail.

About

eggjs 框架 主要功能1. 定时爬虫ReadHub 2. 转接ReadHub api接口 提供给News小程序使用

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published