nodejs eggjs框架 爬虫 readhub.me
最近做了一款 高仿ReadHub小程序 微信小程序 canvas 自动适配 自动换行,保存图片分享到朋友圈 https://gitee.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模块
可通过图片中看到 每次滚动加载数据时 都会有api请求数据, 我们发现 下次触发滚动加载时,的lastCursor的值 为 上次请求的 数组中最后一个对象中的order值
所以我们发现 只是的请求 url地址为 https://api.readhub.me/topic?lastCursor=53058&pageSize=20 中 的lastCursor 动态设置,即可完成抓取数据
那么接下来 我们需要 建立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;
项目运行图
具体代码已被开源,后续我会继续更新,欢迎指正
https://github.com/richard1015/egg-example
https://gitee.com/richard1015/egg-example
nodejs eggjs框架 爬虫 readhub.me的更多相关文章
- 一次使用NodeJS实现网页爬虫记
前言 几个月之前,有同事找我要PHP CI框架写的OA系统.他跟我说,他需要学习PHP CI框架,我建议他学习大牛写的国产优秀框架QeePHP. 我上QeePHP官网,发现官方网站打不开了,GOOGL ...
- (转)windows下安装nodejs及框架express
转自:http://jingyan.baidu.com/article/456c463b60fb380a583144a9.html windows下安装nodejs及框架express nodejs从 ...
- nodejs的简单爬虫
闲聊 好久没写博客了,前几天小颖在朋友的博客里看到了用nodejs的简单爬虫.所以小颖就自己试着做了个爬博客园数据的demo.嘻嘻...... 小颖最近养了条泰日天,自从养了我家 ...
- Scrapy框架-----爬虫
说明:文章是本人读了崔庆才的Python3---网络爬虫开发实战,做的简单整理,希望能帮助正在学习的小伙伴~~ 1. 准备工作: 安装Scrapy框架.MongoDB和PyMongo库,如果没有安装, ...
- 第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码
第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码 打码接口文件 # -*- coding: cp936 -*- import sys import os ...
- 第三百三十四节,web爬虫讲解2—Scrapy框架爬虫—Scrapy爬取百度新闻,爬取Ajax动态生成的信息
第三百三十四节,web爬虫讲解2—Scrapy框架爬虫—Scrapy爬取百度新闻,爬取Ajax动态生成的信息 crapy爬取百度新闻,爬取Ajax动态生成的信息,抓取百度新闻首页的新闻rul地址 有多 ...
- 第三百三十三节,web爬虫讲解2—Scrapy框架爬虫—Scrapy模拟浏览器登录—获取Scrapy框架Cookies
第三百三十三节,web爬虫讲解2—Scrapy框架爬虫—Scrapy模拟浏览器登录 模拟浏览器登录 start_requests()方法,可以返回一个请求给爬虫的起始网站,这个返回的请求相当于star ...
- 第三百三十二节,web爬虫讲解2—Scrapy框架爬虫—Scrapy使用
第三百三十二节,web爬虫讲解2—Scrapy框架爬虫—Scrapy使用 xpath表达式 //x 表示向下查找n层指定标签,如://div 表示查找所有div标签 /x 表示向下查找一层指定的标签 ...
- 第三百三十一节,web爬虫讲解2—Scrapy框架爬虫—Scrapy安装—Scrapy指令
第三百三十一节,web爬虫讲解2—Scrapy框架爬虫—Scrapy安装—Scrapy指令 Scrapy框架安装 1.首先,终端执行命令升级pip: python -m pip install --u ...
随机推荐
- Gym101138D Strange Queries/BZOJ5016 SNOI2017 一个简单的询问 莫队、前缀和、容斥
传送门--Gym 传送门--BZOJ THUWC2019D1T1撞题可还行 以前有些人做过还问过我,但是我没有珍惜,直到进入考场才追悔莫及-- 设\(que_{i,j}\)表示询问\((1,i,1,j ...
- Gulp压缩前端CS,JS,图片文件
Gulp 基于Node.js的前端构建工具,Gulp有许多插件(这里是插件),使用Gulp可以实现前端代码的编译(sass.less).压缩.图片的压缩等,当然主要是前端工程化,不过我目前只是需要压缩 ...
- Ubuntu脚本修改IP信息
#!/bin/bash cd /etc/network #清除4-9行 sed -i '4,9d' interfaces #在第3行添加网卡名称 sed -i "3a auto ${1}&q ...
- OpenResty入门之使用Lua扩展Nginx
记住一点:nginx配置文件很多坑来源自你的空格少了或多了. 1.Centos下载安装 如果你的系统是 Centos 或 RedHat 可以使用以下命令: yum install readline-d ...
- 分布式系统消息中间件——RabbitMQ的使用思考篇
分布式系统消息中间件--RabbitMQ的使用思考篇 前言 前面的两篇文章分布式系统消息中间件--RabbitMQ的使用基础篇与分布式系统消息中间件--RabbitMQ的使用进阶篇,我们简单介 ...
- Spring如何加载log4j配置文件
今天有朋友在群里问了这个问题,于是写了这篇文章进行整理. 问题如下: 在项目中添加了log4j.properties配置文件,并没有在Spring配置文件中配置,也没有在web.xml中配置,但是代码 ...
- AtCoder Beginner Contest 053
D - Card Eater Time limit : 2sec / Memory limit : 256MB Score : 400 points Problem Statement Snuke h ...
- Linux—vim常用命令
vim常用命令: 1. 键入i进入编辑模式2. esc进入命令模式3. a,进入编辑模式3. b,光标移动到单词前,end,光标移动到行尾4. home光标移动到行首5. cc,删除当前行,并进入编辑 ...
- 现有n 个乱序数,都大于 1000 ,让取排行榜前十,时间复杂度为o(n), top10, 或者 topK,应用场景榜单Top:10,堆实现Top k
一.topK python实现 def topk(k, lst): top = [0 for i in range(k)] #生成一个长度为K 的有序列表 for item in lst: #循环 ...
- Python_内置函数之max
源码: def max(*args, key=None): # known special case of max """ max(iterable, *[, defau ...