用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件
本文首发于个人博客:Vince'Blog
项目源码:NodeMail,欢迎star,说不定哪天脱单了就能用到了
写在前面
自从用邮箱注册了很多账号后,便会收到诸如以下类似的邮件,刚开始还以为是一张图片,后来仔细一看不是图片呀,好像还是HTML呀,于是好奇宝宝我Google一下,查阅多篇资料后总结出怎么用前端知识和Node做一个这样的“邮件网页”。
确认主题
知道怎么实现功能后,思考着我该写什么主题呢,用一个HTML模板随便给小伙伴们发个邮件炫个技?不行,作为一个很cool的程序员怎么能这么low呢,最近天气变化幅度大,温度捉摸不定,女朋友总是抱怨穿少了又冷穿多了又热,嗨呀,要不我就写个每天定时给宝宝发送天气预报的邮件,另外想起宝宝喜欢看ONE·一个这个APP上的每日更新,要不发天气预报的同时,再附赠一个“ONE的每日订阅”?机智又浪漫,开始搬砖~
剧透
本来是想最后放效果图的,怕你们看到一半就没兴趣了,就在前面剧透一下我最后做出来的效果图吧~
待解决的问题
1. 如何获取天气预报和ONE上的data?
答:获取data有两种方法,第一种方法是获取天气预报和ONE的API,第二种是用node爬虫获取天气预报和ONE网页的信息。后来找了下,发现ONE并没有API接口,为了让两者统一,于是决定使用node上的一个插件叫
cheerio,配合
superagent能够很方便地爬取网页上的信息。
2. 如何做出HTML的这种邮件?
答:之前学过一段时间的express这个框架,接触到模版引擎这个概念,传入data便可获得html文件,再结合node的fs模块,获取到这个html文件,便可以结合node的邮件插件发送HTML邮件啦!
3. 如何用node发送邮件?
感谢无私的开源开发者,开发了一款发送邮件的Node插件
nodemailer,兼容主流的Email厂商,只需要配置好邮箱账号和smtp授权码,便可以用你的邮箱账号在node脚本上发文件,很cool有没有~
4. 如何做到每日定时发送?
其实可以通过各种hack的方式写这么一个定时任务,但是既然node社区有这个定时的轮子,那我们直接用就好了,
node-schedule是一个有着各种配置的定时任务发生器,可以定时每个月、每个礼拜、每天具体什么时候执行什么任务,这正符合每天早晨定时给宝宝发送邮件的需求。
一切准备就绪,开始做一次浪漫的程序员
编写代码
网页爬虫
这里我们使用到
superagent和
cheerio组合来实现爬虫:
- 分析网页DOM结构,如下图所示:
- 用superagent来获取指定网页的所有DOM:
[code]superagent.get(URL).end(function(err,res){ // } 复制代码
- 用cheerio来筛选superagent获取到的DOM,取出需要的DOM
[code]imgUrl:$(todayOne).find('.fp-one-imagen').attr('src'), type:$(todayOne).find('.fp-one-imagen-footer').text().replace(/(^\s*)|(\s*$)/g, ""), text:$(todayOne).find('.fp-one-cita').text().replace(/(^\s*)|(\s*$)/g, "") 复制代码
以下就是爬取ONE的代码,天气预报网页也是一个道理:
[code]const superagent = require('superagent'); //发送网络请求获取DOM const cheerio = require('cheerio'); //能够像Jquery一样方便获取DOM节点 const OneUrl = "http://wufazhuce.com/"; //ONE的web版网站 superagent.get(OneUrl).end(function(err,res){ if(err){ console.log(err); } let $ = cheerio.load(res.text); let selectItem=$('#carousel-one .carousel-inner .item'); let todayOne=selectItem[0]; //获取轮播图第一个页面,也就是当天更新的内容 let todayOneData={ //保存到一个json中 imgUrl:$(todayOne).find('.fp-one-imagen').attr('src'), type:$(todayOne).find('.fp-one-imagen-footer').text().replace(/(^\s*)|(\s*$)/g, ""), text:$(todayOne).find('.fp-one-cita').text().replace(/(^\s*)|(\s*$)/g, "") }; console.log(todayOneData); }) 复制代码
EJS模版引擎生成HTML
通过爬虫获取到了数据,那么我们就能够通过将date输入到EJS渲染出HTML,我们在目录下创建js脚本和ejs模版文件:
- app.js
[code]const ejs = require('ejs'); //ejs模版引擎 const fs = require('fs'); //文件读写 const path = require('path'); //路径配置 //传给EJS的数据 let data={ title:'nice to meet you~' } //将目录下的mail.ejs获取到,得到一个模版 const template = ejs.compile(fs.readFileSync(path.resolve(__dirname, 'mail.ejs'), 'utf8')); //将数据传入模版中,生成HTML const html = template(data); console.log(html) 复制代码
- mail.ejs
[code]<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <h1> <%= title %> </h1> </body> </html> 复制代码
用Node发送邮件
4000这里我们可以发送纯text也可以发送html,注意的是邮箱密码不是你登录邮箱的密码,而是smtp授权码,什么是smtp授权码呢?就是你的邮箱账号可以使用这个smtp授权码在别的地方发邮件,一般smtp授权码在邮箱官网的设置中可以看的到,设置如下注释。
[code]const nodemailer = require('nodemailer'); //发送邮件的node插件 let transporter = nodemailer.createTransport({ service: '126', // 发送者的邮箱厂商,支持列表:https://nodemailer.com/smtp/well-known/ port: 465, // SMTP 端口 secureConnection: true, // SSL安全链接 auth: { //发送者的账户密码 user: '账户@126.com', //账户 pass: 'smtp授权码', //smtp授权码,到邮箱设置下获取 } }); let mailOptions = { from: '"发送者昵称" <地址@126.com>', // 发送者昵称和地址 to: 'like@vince.studio', // 接收者的邮箱地址 subject: '一封暖暖的小邮件', // 邮件主题 text: 'test mail', //邮件的text // html: html //也可以用html发送 }; //发送邮件 transporter.sendMail(mailOptions, (error, info) => { if (error) { return console.log(error); } console.log('邮件发送成功 ID:', info.messageId); }); 复制代码
Node定时执行任务
这里我们用到了
node-schedule来定时执行任务,示例如下:
[code]var schedule = require("node-schedule"); //1. 确定的时间执行 var date = new Date(2017,12,10,15,50,0); schedule.scheduleJob(date, function(){ console.log("执行任务"); }); //2. 秒为单位执行 //比如:每5秒执行一次 var rule1 = new schedule.RecurrenceRule(); var times1 = [1,6,11,16,21,26,31,36,41,46,51,56]; rule1.second = times1; schedule.scheduleJob(rule1, function(){ console.log("执行任务"); }); //3.以分为单位执行 //比如:每5分种执行一次 var rule2 = new schedule.RecurrenceRule(); var times2 = [1,6,11,16,21,26,31,36,41,46,51,56]; rule2.minute = times2; schedule.scheduleJob(rule2, function(){ console.log("执行任务"); }); //4.以天单位执行 //比如:每天6点30分执行 var rule = new schedule.RecurrenceRule(); rule.dayOfWeek = [0, new schedule.Range(1, 6)]; rule.hour = 6; rule.minute =30; var j = schedule.scheduleJob(rule, function(){     console.log("执行任务"); getData(); }); 复制代码
思路与步骤
当所有的问题都解决后,便是开始结合代码成一段完整的程序,思路很简单,我们来逐步分析:
- 由于获取数据是异步的,并且不能判断出哪个先获取到数据,这个是可以将获取数据的函数封装成一个Promise对象,最后在一起用Promise.all来判断所有数据获取完毕,再发送邮件
[code]// 其中一个数据获取函数,其他的也是类似 function getOneData(){ let p = new Promise(function(resolve,reject){ superagent.get(OneUrl).end(function(err, res) { if (err) { reject(err); } let $ = cheerio.load(res.text); let selectItem = $("#carousel-one .carousel-inner .item"); let todayOne = selectItem[0]; let todayOneData = { imgUrl: $(todayOne) .find(".fp-one-imagen") .attr("src"), type: $(todayOne) .find(".fp-one-imagen-footer") .text() .replace(/(^\s*)|(\s*$)/g, ""), text: $(todayOne) .find(".fp-one-cita") .text() .replace(/(^\s*)|(\s*$)/g, "") }; resolve(todayOneData) }); }) return p } 复制代码
- 将爬取数据统一处理,作为EJS的参数,发送邮件模板。
[code] function getAllDataAndSendMail(){ let HtmlData = {}; // how long with let today = new Date(); let initDay = new Date(startDay); let lastDay = Math.floor((today - initDay) / 1000 / 60 / 60 / 24); let todaystr = today.getFullYear() + " / " + (today.getMonth() + 1) + " / " + today.getDate(); HtmlData["lastDay"] = lastDay; HtmlData["todaystr"] = todaystr; Promise.all([getOneData(),getWeatherTips(),getWeatherData()]).then( function(data){ HtmlData["todayOneData"] = data[0]; HtmlData["weatherTip"] = data[1]; HtmlData["threeDaysData"] = data[2]; sendMail(HtmlData) } ).catch(function(err){ getAllDataAndSendMail() //再次获取 console.log('获取数据失败: ',err); }) } 复制代码
- 发送邮件具体代码
[code] function sendMail(HtmlData) { const template = ejs.compile( fs.readFileSync(path.resolve(__dirname, "email.ejs"), "utf8") ); const html = template(HtmlData); let transporter = nodemailer.createTransport({ service: EmianService, port: 465, secureConnection: true, auth: EamilAuth }); let mailOptions = { from: EmailFrom, to: EmailTo, subject: EmailSubject, html: html }; transporter.sendMail(mailOptions, (error, info={}) => { if (error) { console.log(error); sendMail(HtmlData); //再次发送 } console.log("Message sent: %s", info.messageId); }); } 复制代码
安装与使用
如果你觉得这封邮件的内容适合你发送的对象,可以按照以下步骤,改少量参数即可运行程序;
- git clone github.com/Vincedream/…
- 打开main.js,修改配置项
[code]//纪念日 let startDay = "2016/6/24"; //当地拼音,需要在下面的墨迹天气url确认 const local = "xiangtan"; //发送者邮箱厂家 let EmianService = "163"; //发送者邮箱账户SMTP授权码 let EamilAuth = { user: "xxxxxx@163.com", pass: "xxxxxx" }; //发送者昵称与邮箱地址 let EmailFrom = '"name" <xxxxxx@163.com>'; //接收者邮箱地 let EmailTo = "like@vince.studio"; //邮件主题 let EmailSubject = "一封暖暖的小邮件"; //每日发送时间 let EmailHour = 6; let EmialMinminute= 30; 复制代码
- 终端输入
npm install
安装依赖,再输入node main.js
,运行脚本,当然你的电脑不可能不休眠,建议你部署到你的云服务器上运行。
最后
冬天到了,是不是也该用程序员的专业知识给身边的人带来一些温暖呢,源代码与demo已经放到github上,要不试一试?
GitHub:github.com/Vincedream/…
作者:Vince_
链接:https://juejin.im/post/5c75fa4af265da2d84109219
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
------------------------------------------- 手动分割 -----------------------------------------
知识点总结
http请求模块 SuperAgent
[code]//请求方式 superagent.get('/api') // 地址栏带参数 ,相当于 /api?name=value superagent .get('/api') .query({name: value})//query方法 //设置请求头 superagent .get('/api') .query({name: value}) .set({'token':'xxxxxxxxx'})//set方法 //post数据传参,content-type默认为application/json superagent .post('/api') .send({name: value})//send方法 .set({'token':'xxxxxxxxx'}) //设置content-type superagent .post('/api') .send({name: value}) .set({//set方法 'token':'xxxxxxxxx', 'Content-Type': 'application/x-www-form-urlencoded' }) //请求完成结果 superagent .post('/api') .send({name: value}) .set({ 'token':'xxxxxxxxx', 'Content-Type': 'application/x-www-form-urlencoded' }) .end(function(err, res){//end方法,err为错误信息,res为响应数据 if(err) { console.log(err); return; } console.log(res.text);//res.text为响应结果 console.log(res.status);//res.status为状态码 })
cheerio模块(可以理解为传入html字符串后,可以像jquery一样操作dom,非常方便的去获取我们想要的数据,可以不用写正则)
[code]//创建虚拟dom load方法 var $ = cheerio.load('<body><p>hello world</p></body>');//通常赋值给变量$,方便直接像写jquery一样的写法. //操作dom var text = $('body p').text();
ejs模块
这篇文章写的不错
[code]<!-- template中 --> <h1><%= title %></h1> <!-- js中 --> var data = {title: 'hello world'}; ejs.render(html字符串, data);
定时任务 node-schedule
[code]var j = schedule.scheduleJob('* * * * * *', function(){ // 这里写需要执行的任务 }); // 取消定时器(用于单次任务) j.cancel(); /* 第一位参数是 Cron 从左到右依次是 秒 0-59 分 0-59 时 0-23 日 1-31 月 1-12 周 1-7 (1表示周一,7表示周日) 举例 每隔五分钟执行依次 * */5 * * * * 每天下午5点20执行 * 20 17 * * * 每周一周三周五的下午2点4点内每隔5分钟执行依次 * */5 2,4 * * 1,3,5 */
发送邮件 nodemailer
[code]let transporter = nodemailer.createTransport({ service: 'QQ', // 发送者的邮箱厂商,支持列表:https://nodemailer.com/smtp/well-known/ port: 465, // SMTP 端口 secureConnection: true, // SSL安全链接 auth: { //发送者的账户密码 user: 'xxxx@qq.com', //账户 pass: 'xxxx', //smtp授权码,到邮箱设置下获取 } }); let mailOptions = { from: '"昵称" <xxxx@qq.com>', // 发送者昵称和地址 to: 'xxxx@qq.com', // 接收者的邮箱地址 subject: '主题', // 邮件主题 text: '邮件内容', //邮件的text, // html: html //用html发送时配置这一项,关掉text }; // 发送邮件 transporter.sendMail(mailOptions, (error, info) => { if (error) { return console.log(error); } console.log('邮件发送成功 ID:', info.messageId); });
- 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话
- 写一个python定时发送消息的脚本——每天跟你女盆友说晚安
- 一个python的邮件发送脚本,自动,定时,可以附件发送,抄送,附有说明文件
- 终于用PYTHON作了一个实用于工作的脚本啦----通过邮件定时发送服务器空闲空间
- linux下收集系统硬盘,内存使用情况以定时邮件方式发给root的一个脚本
- 一个python的邮件发送脚本,自动,定时,可以附件发送,抄送,附有说明文件 (本脚本是python的2.7.x环境下可用)
- 一个python的邮件发送脚本,自动,定时,可以附件发送,抄送,附有说明文件
- 一个python的邮件发送脚本,自动,定时,可以附件发送,抄送
- 一个nodejs+mongodb搭建的博客,使用module: ejs, express, mongodb, underscore等,不错的入门文章
- 分享一个定时自动备份oracle数据库的脚本
- 用node.js+express+ejs+bootstrap来建立一个基本网页
- 在Java中如何设置一个定时任务,在每天的一个时间点自动执行一个特定的程序
- 之前帮朋友写的一个小工具-征途2新浪卡全自动淘号工具+按钮精灵脚本
- [python脚本]一个简单的web爬虫(1)
- 一个很有趣的问题:那些用QQ邮箱发应聘邮件的人啊(附:怎样写一封得体的电子邮件)
- 花了一上午,合成的一个粗糙的IT用来了解EXCHANGE运行情况的自动邮件脚本
- Crontab 实现定时执行一个shell脚本(以每隔十分钟执行一次为例)
- node+express+ejs搭建一个简单的"页面"
- Nodejs express框架一个工程中同时使用ejs模版和jade模版