背景
双11的时候,为了给我家主子囤些猫砂猫罐头啥的,一直刷豆瓣爱猫小组的开车贴,边刷边玩cod16,结果就是错过了很多豪车,游戏KD也降了不少。事后我痛定思痛,把猫送人,耽搁我刷金枪 决定整个小玩意儿糊弄一下主子
实现
其实要做的事也简单,写个爬虫,定时爬一下带关键字的帖子,然后邮件通知我。尽管是随便发挥,接触下实际工作中没太接触过的东西也是好的么。
作为一个纯前端,当然是用node啦,配合puppeteer、nodemailer、node-schedule来实现核心功能
部分代码
我把关键的部分提出来方便看,其实都是几个工具的快速入门部分,难度基本没有,毕竟是糊弄入门学习么。
不合理的地方大佬们使劲喷,我顶得住
爬虫部分
const puppeteer = require('puppeteer');
const url = 'https://www.douban.com/group/search?group=656297&cat=1013&q=开车';
const sleep = time => new Promise(resolve => {
setTimeout(resolve, time);
});
// puppeteer文档地址:https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#pagegotourl-options
const queryInfos = async () => {
const browser = await puppeteer.launch({});
const page = await browser.newPage();
page.goto(url, {
waitUntil: 'networkidle2',
});
await sleep(5000);// 确保爬到东西,时间可以缩短
const result = await page.evaluate(() => {
// eslint-disable-next-line no-undef
const $ = window.$;
// 我比较关注的关键字,可以考虑扩展下来实现类似只匹配猫砂但不匹配猫砂盆,这就不展开了
const keywords = /罐|餐盒|巅峰|渴望|go|砂/;
const postList = $('.td-subject a');
const links = [];
if (postList.length > 0) {
postList.each((i, e) => {
const title = $(e).text();
const link = $(e).attr('href');
const id = link.match(/\d+/g).toString();
if (keywords.test(title)) {
links.push({
id,
title,
link,
keywords: title.match(keywords),
});
}
});
}
return links;
});
browser.close();
return result;
};
邮件通知
const nodemailer = require('nodemailer');
const smtpTransport = require('nodemailer-smtp-transport');
const url = 'https://www.douban.com/group/search?group=656297&cat=1013&q=开车';
const qqTransport = nodemailer.createTransport(smtpTransport({
service: 'QQ',
auth: {
user: '[email protected]',// 发件地址
pass: '',// 授权码
},
}));
const sendMail = function(num, content) {
qqTransport.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: `${num}辆新车上路`,
html: content,
}, function(err, res) {
if (err) {
return;
}
console.log('发送成功', res);
});
};
async queryCarsList() {
const list = await getInfos();
let total = 0;
let carList = '';
// 注意async/await跟for更配哦
for (let i = 0, len = list.length; i < len; i++) {
const car = list[i];
const {
id,
title,
link,
} = car;
// 这部分的实现比较简单粗暴,希望大家多批评
// 爬到的数据我一开始简单粗暴的直接用fs写到json文件里了,但来都来了,顺便用下数据库也无伤大雅么
const temp = await queryById(id);
if (temp.length < 1) {
carList += ``;
total++;
const info = new this.ctx.model.Cars({
...car,
});
info.save();
}
}
if (total > 0) {
sendMail(total, carList);
}
}
async queryById(id) {
return this.ctx.model.Cars.find({id});
}
定时任务
基本的功能setInterval应该也是可以完成的,但为了后期好扩展,而且,来都来了,试一下node-schedule也无妨
const schedule = require('node-schedule');
const queryTask = ()=>{
// 半小时执行一次
schedule.scheduleJob('* 30 * * * *', queryCarsList);
}
// queryTask.cancel() // 取消任务
部署
部署就不展开讲了,我是打算eggjs+pm2丢自己服务器上,目前还是本机跑一下,毕竟本职工作优先级最高,而且最近不用给它囤东西了
改进
考虑下要一直维护的话,还有很多地方要搞
- 就这点数据量,引入数据库是否有必要?其他方案?
- 开车贴时效性较高,要不要定期重置一下数据?
- 有新车时,要不要直接爬内容,再根据内容判断是否是关注的信息?
- 要不要整个dashboard页面?
- dashboard页面考不考虑试下WebSocket?
- 。。。。。
算球,为了一只猫,逗猫棒乐呵乐呵得了❌
唉,来都来了✅
总结
具体功能不难实现,但好像能接触的东西还不少,而且要做好了还是得花点功夫的,随便发散一下,需求又是无限多,慢慢填吧。
对了,不合理的地方大佬们使劲喷,我顶得住
附恶毒甲方照片一张: