├── README.md ├── demo ├── cheerio-superagent │ └── cheerio-superagent.js ├── ejsdemo │ ├── app.js │ └── mail.ejs ├── node-schedule-demo │ └── app.js └── nodeMailDemo │ └── app.js ├── email.ejs ├── main.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 写在前面 3 | 4 | 5 | 自从用邮箱注册了很多账号后,便会收到诸如以下类似的邮件,刚开始还以为是一张图片,后来仔细一看不是图片呀,好像还是HTML呀,于是好奇宝宝我Google一下,查阅多篇资料后总结出怎么用前端知识和Node做一个这样的“邮件网页”。 6 | 7 | ![image](http://blogpic.vince.xin/CF951178-3DFD-43D6-9A68-9DBD2706C98B.png) 8 | 9 | 10 | 11 | ## 确认主题 12 | 知道怎么实现功能后,思考着我该写什么主题呢,用一个HTML模板随便给小伙伴们发个邮件炫个技?不行,作为一个很cool的程序员怎么能这么low呢,最近天气变化幅度大,温度捉摸不定,女朋友总是抱怨穿少了又冷穿多了又热,嗨呀,要不我就写个每天定时给宝宝发送天气预报的邮件,另外想起宝宝喜欢看ONE·一个这个APP上的每日更新,要不发天气预报的同时,再附赠一个“ONE的每日订阅”?机智又浪漫,开始搬砖~ 13 | 14 | ## 剧透 15 | 16 | 本来是想最后放效果图的,怕你们看到一半就没兴趣了,就在前面剧透一下我最后做出来的效果图吧~ 17 | 18 | ![image](http://blogpic.vince.xin/2C971663-4C02-4CDD-8E13-1C71B8170EB4.png) 19 | 20 | ## 待解决的问题 21 | **1. 如何获取天气预报和ONE上的data?** 22 | 23 | 答:获取data有两种方法,第一种方法是获取天气预报和ONE的API,第二种是用node爬虫获取天气预报和ONE网页的信息。后来找了下,发现ONE并没有API接口,为了让两者统一,于是决定使用node上的一个插件叫`cheerio`,配合`superagent`能够很方便地爬取网页上的信息。 24 | 25 | **2. 如何做出HTML的这种邮件?** 26 | 27 | 答:之前学过一段时间的express这个框架,接触到模版引擎这个概念,传入data便可获得html文件,再结合node的fs模块,获取到这个html文件,便可以结合node的邮件插件发送HTML邮件啦! 28 | 29 | **3. 如何用node发送邮件?** 30 | 31 | 感谢无私的开源开发者,开发了一款发送邮件的Node插件`nodemailer`,兼容主流的Email厂商,只需要配置好邮箱账号和smtp授权码,便可以用你的邮箱账号在node脚本上发文件,很cool有没有~ 32 | 33 | **4. 如何做到每日定时发送?** 34 | 35 | 其实可以通过各种hack的方式写这么一个定时任务,但是既然node社区有这个定时的轮子,那我们直接用就好了,`node-schedule`是一个有着各种配置的定时任务发生器,可以定时每个月、每个礼拜、每天具体什么时候执行什么任务,这正符合每天早晨定时给宝宝发送邮件的需求。 36 | 37 | **一切准备就绪,开始做一次浪漫的程序员** 38 | 39 | ## 编写代码 40 | ### 网页爬虫 41 | 这里我们使用到`superagent`和`cheerio`组合来实现爬虫: 42 | 43 | - 分析网页DOM结构,如下图所示: 44 | 45 | ![image](http://blogpic.vince.xin/B7509558-D988-4818-8969-77FE5028882A.png) 46 | 47 | - 用superagent来获取指定网页的所有DOM: 48 | 49 | ``` javascript 50 | superagent.get(URL).end(function(err,res){ 51 | // 52 | } 53 | ``` 54 | - 用cheerio来筛选superagent获取到的DOM,取出需要的DOM 55 | 56 | ``` javascript 57 | imgUrl:$(todayOne).find('.fp-one-imagen').attr('src'), 58 | type:$(todayOne).find('.fp-one-imagen-footer').text().replace(/(^\s*)|(\s*$)/g, ""), 59 | text:$(todayOne).find('.fp-one-cita').text().replace(/(^\s*)|(\s*$)/g, "") 60 | ``` 61 | **以下就是爬取ONE的代码,天气预报网页也是一个道理:** 62 | 63 | ``` javascript 64 | const superagent = require('superagent'); //发送网络请求获取DOM 65 | const cheerio = require('cheerio'); //能够像Jquery一样方便获取DOM节点 66 | 67 | const OneUrl = "http://wufazhuce.com/"; //ONE的web版网站 68 | 69 | superagent.get(OneUrl).end(function(err,res){ 70 | if(err){ 71 | console.log(err); 72 | } 73 | let $ = cheerio.load(res.text); 74 | let selectItem=$('#carousel-one .carousel-inner .item'); 75 | let todayOne=selectItem[0]; //获取轮播图第一个页面,也就是当天更新的内容 76 | let todayOneData={ //保存到一个json中 77 | imgUrl:$(todayOne).find('.fp-one-imagen').attr('src'), 78 | type:$(todayOne).find('.fp-one-imagen-footer').text().replace(/(^\s*)|(\s*$)/g, ""), 79 | text:$(todayOne).find('.fp-one-cita').text().replace(/(^\s*)|(\s*$)/g, "") 80 | }; 81 | console.log(todayOneData); 82 | }) 83 | ``` 84 | 85 | ### EJS模版引擎生成HTML 86 | 通过爬虫获取到了数据,那么我们就能够通过将date输入到EJS渲染出HTML,我们在目录下创建js脚本和ejs模版文件: 87 | 88 | - app.js 89 | 90 | ``` javascript 91 | const ejs = require('ejs'); //ejs模版引擎 92 | const fs = require('fs'); //文件读写 93 | const path = require('path'); //路径配置 94 | 95 | //传给EJS的数据 96 | let data={ 97 | title:'nice to meet you~' 98 | } 99 | 100 | //将目录下的mail.ejs获取到,得到一个模版 101 | const template = ejs.compile(fs.readFileSync(path.resolve(__dirname, 'mail.ejs'), 'utf8')); 102 | //将数据传入模版中,生成HTML 103 | const html = template(data); 104 | 105 | console.log(html) 106 | 107 | ``` 108 | 109 | - mail.ejs 110 | 111 | ``` javascript 112 | 113 | 114 | 115 | 116 | 117 | 118 | Document 119 | 120 | 121 |

122 | <%= title %> 123 |

124 | 125 | 126 | ``` 127 | 128 | ### 用Node发送邮件 129 | 这里我们可以发送纯text也可以发送html,注意的是邮箱密码不是你登录邮箱的密码,而是smtp授权码,什么是smtp授权码呢?就是你的邮箱账号可以使用这个smtp授权码在别的地方发邮件,一般smtp授权码在邮箱官网的设置中可以看的到,设置如下注释。 130 | 131 | ``` javascript 132 | const nodemailer = require('nodemailer'); //发送邮件的node插件 133 | 134 | let transporter = nodemailer.createTransport({ 135 | service: '126', // 发送者的邮箱厂商,支持列表:https://nodemailer.com/smtp/well-known/ 136 | port: 465, // SMTP 端口 137 | secureConnection: true, // SSL安全链接 138 | auth: { //发送者的账户密码 139 | user: '账户@126.com', //账户 140 | pass: 'smtp授权码', //smtp授权码,到邮箱设置下获取 141 | } 142 | }); 143 | 144 | let mailOptions = { 145 | from: '"发送者昵称" <地址@126.com>', // 发送者昵称和地址 146 | to: 'like@vince.studio', // 接收者的邮箱地址 147 | subject: '一封暖暖的小邮件', // 邮件主题 148 | text: 'test mail', //邮件的text 149 | // html: html //也可以用html发送 150 | }; 151 | 152 | //发送邮件 153 | transporter.sendMail(mailOptions, (error, info) => { 154 | if (error) { 155 | return console.log(error); 156 | } 157 | console.log('邮件发送成功 ID:', info.messageId); 158 | }); 159 | ``` 160 | ### Node定时执行任务 161 | 这里我们用到了`node-schedule`来定时执行任务,示例如下: 162 | 163 | 164 | ``` javascript 165 | var schedule = require("node-schedule"); 166 | 167 | //1. 确定的时间执行 168 | var date = new Date(2017,12,10,15,50,0); 169 | schedule.scheduleJob(date, function(){ 170 | console.log("执行任务"); 171 | }); 172 | 173 | //2. 秒为单位执行 174 | //比如:每5秒执行一次 175 | var rule1 = new schedule.RecurrenceRule(); 176 | var times1 = [1,6,11,16,21,26,31,36,41,46,51,56]; 177 | rule1.second = times1; 178 | schedule.scheduleJob(rule1, function(){ 179 | console.log("执行任务"); 180 | }); 181 | 182 | //3.以分为单位执行 183 | //比如:每5分种执行一次 184 | var rule2 = new schedule.RecurrenceRule(); 185 | var times2 = [1,6,11,16,21,26,31,36,41,46,51,56]; 186 | rule2.minute = times2; 187 | schedule.scheduleJob(rule2, function(){ 188 | console.log("执行任务"); 189 | }); 190 | 191 | //4.以天单位执行 192 | //比如:每天6点30分执行 193 | var rule = new schedule.RecurrenceRule(); 194 | rule.dayOfWeek = [0, new schedule.Range(1, 6)]; 195 | rule.hour = 6; 196 | rule.minute =30; 197 | var j = schedule.scheduleJob(rule, function(){ 198 |     console.log("执行任务"); 199 | getData(); 200 | }); 201 | ``` 202 | 203 | ## 思路与步骤 204 | 205 | 当所有的问题都解决后,便是开始结合代码成一段完整的程序,思路很简单,我们来逐步分析: 206 | 1. 由于获取数据是异步的,并且不能判断出哪个先获取到数据,这个是可以将获取数据的函数封装成一个Promise对象,最后在一起用Promise.all来判断所有数据获取完毕,再发送邮件 207 | 208 | ``` javascript 209 | // 其中一个数据获取函数,其他的也是类似 210 | function getOneData(){ 211 | let p = new Promise(function(resolve,reject){ 212 | superagent.get(OneUrl).end(function(err, res) { 213 | if (err) { 214 | reject(err); 215 | } 216 | let $ = cheerio.load(res.text); 217 | let selectItem = $("#carousel-one .carousel-inner .item"); 218 | let todayOne = selectItem[0]; 219 | let todayOneData = { 220 | imgUrl: $(todayOne) 221 | .find(".fp-one-imagen") 222 | .attr("src"), 223 | type: $(todayOne) 224 | .find(".fp-one-imagen-footer") 225 | .text() 226 | .replace(/(^\s*)|(\s*$)/g, ""), 227 | text: $(todayOne) 228 | .find(".fp-one-cita") 229 | .text() 230 | .replace(/(^\s*)|(\s*$)/g, "") 231 | }; 232 | resolve(todayOneData) 233 | }); 234 | }) 235 | return p 236 | } 237 | 238 | ``` 239 | 2. 将爬取数据统一处理,作为EJS的参数,发送邮件模板。 240 | 241 | ``` javascript 242 | 243 | function getAllDataAndSendMail(){ 244 | let HtmlData = {}; 245 | // how long with 246 | let today = new Date(); 247 | let initDay = new Date(startDay); 248 | let lastDay = Math.floor((today - initDay) / 1000 / 60 / 60 / 24); 249 | let todaystr = 250 | today.getFullYear() + 251 | " / " + 252 | (today.getMonth() + 1) + 253 | " / " + 254 | today.getDate(); 255 | HtmlData["lastDay"] = lastDay; 256 | HtmlData["todaystr"] = todaystr; 257 | 258 | Promise.all([getOneData(),getWeatherTips(),getWeatherData()]).then( 259 | function(data){ 260 | HtmlData["todayOneData"] = data[0]; 261 | HtmlData["weatherTip"] = data[1]; 262 | HtmlData["threeDaysData"] = data[2]; 263 | sendMail(HtmlData) 264 | } 265 | ).catch(function(err){ 266 | getAllDataAndSendMail() //再次获取 267 | console.log('获取数据失败: ',err); 268 | }) 269 | } 270 | 271 | ``` 272 | 3. 发送邮件具体代码 273 | 274 | ``` javascript 275 | 276 | function sendMail(HtmlData) { 277 | const template = ejs.compile( 278 | fs.readFileSync(path.resolve(__dirname, "email.ejs"), "utf8") 279 | ); 280 | const html = template(HtmlData); 281 | 282 | let transporter = nodemailer.createTransport({ 283 | service: EmianService, 284 | port: 465, 285 | secureConnection: true, 286 | auth: EamilAuth 287 | }); 288 | 289 | let mailOptions = { 290 | from: EmailFrom, 291 | to: EmailTo, 292 | subject: EmailSubject, 293 | html: html 294 | }; 295 | transporter.sendMail(mailOptions, (error, info={}) => { 296 | if (error) { 297 | console.log(error); 298 | sendMail(HtmlData); //再次发送 299 | } 300 | console.log("Message sent: %s", info.messageId); 301 | }); 302 | } 303 | ``` 304 | 305 | 306 | ## 安装与使用 307 | 如果你觉得这封邮件的内容适合你发送的对象,可以按照以下步骤,改少量参数即可运行程序; 308 | 309 | 1. git clone https://github.com/Vincedream/NodeMail 310 | 2. 打开main.js,修改配置项 311 | 312 | ``` javascript 313 | //纪念日 314 | let startDay = "2016/6/24"; 315 | 316 | //当地拼音,需要在下面的墨迹天气url确认 317 | const local = "zhejiang/hangzhou"; 318 | 319 | //发送者邮箱厂家 320 | let EmianService = "163"; 321 | //发送者邮箱账户SMTP授权码 322 | let EamilAuth = { 323 | user: "xxxxxx@163.com", 324 | pass: "xxxxxx" 325 | }; 326 | //发送者昵称与邮箱地址 327 | let EmailFrom = '"name" '; 328 | 329 | //接收者邮箱地 330 | let EmailTo = "like@vince.studio"; 331 | //邮件主题 332 | let EmailSubject = "一封暖暖的小邮件"; 333 | 334 | //每日发送时间 335 | let EmailHour = 6; 336 | let EmialMinminute= 30; 337 | ``` 338 | 3. 终端输入`npm install`安装依赖,再输入`node main.js`,运行脚本,当然你的电脑不可能不休眠,建议你部署到你的云服务器上运行。 339 | 340 | 341 | ## 最后 342 | 冬天到了,是不是也该用程序员的专业知识给身边的人带来一些温暖呢,源代码与demo已经放到github上,要不试一试? 343 | 344 | GitHub:[https://github.com/Vincedream/NodeMail](https://github.com/Vincedream/NodeMail) -------------------------------------------------------------------------------- /demo/cheerio-superagent/cheerio-superagent.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); //发送网络请求获取DOM 2 | const cheerio = require('cheerio'); //能够像Jquery一样方便获取DOM节点 3 | 4 | const OneUrl = "http://wufazhuce.com/"; //ONE的web版网站 5 | 6 | superagent.get(OneUrl).end(function(err,res){ 7 | if(err){ 8 | console.log(err); 9 | } 10 | let $ = cheerio.load(res.text); 11 | let selectItem=$('#carousel-one .carousel-inner .item'); 12 | let todayOne=selectItem[0]; 13 | let todayOneData={ 14 | imgUrl:$(todayOne).find('.fp-one-imagen').attr('src'), 15 | type:$(todayOne).find('.fp-one-imagen-footer').text().replace(/(^\s*)|(\s*$)/g, ""), 16 | text:$(todayOne).find('.fp-one-cita').text().replace(/(^\s*)|(\s*$)/g, "") 17 | } 18 | console.log(todayOneData) 19 | }) -------------------------------------------------------------------------------- /demo/ejsdemo/app.js: -------------------------------------------------------------------------------- 1 | const ejs = require('ejs'); //ejs模版引擎 2 | const fs = require('fs'); //文件读写 3 | const path = require('path'); //路径配置 4 | 5 | //传给EJS的数据 6 | let data={ 7 | title:'nice to meet you~' 8 | } 9 | 10 | //将目录下的mail.ejs获取到,得到一个模版 11 | const template = ejs.compile(fs.readFileSync(path.resolve(__dirname, 'mail.ejs'), 'utf8')); 12 | //将数据传入模版中,生成HTML 13 | const html = template(data); 14 | 15 | console.log(html) 16 | -------------------------------------------------------------------------------- /demo/ejsdemo/mail.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |

11 | <%= title %> 12 |

13 | 14 | -------------------------------------------------------------------------------- /demo/node-schedule-demo/app.js: -------------------------------------------------------------------------------- 1 | var schedule = require("node-schedule"); 2 | 3 | //1. 确定的时间执行 4 | var date = new Date(2016,6,13,15,50,0); 5 | schedule.scheduleJob(date, function(){ 6 | console.log("执行任务"); 7 | }); 8 | 9 | //2. 秒为单位执行 10 | //比如:每5秒执行一次 11 | var rule1 = new schedule.RecurrenceRule(); 12 | var times1 = [1,6,11,16,21,26,31,36,41,46,51,56]; 13 | rule1.second = times1; 14 | schedule.scheduleJob(rule1, function(){ 15 | console.log("执行任务"); 16 | }); 17 | 18 | //3.以分为单位执行 19 | //比如:每5分种执行一次 20 | var rule2 = new schedule.RecurrenceRule(); 21 | var times2 = [1,6,11,16,21,26,31,36,41,46,51,56]; 22 | rule2.minute = times2; 23 | schedule.scheduleJob(rule2, function(){ 24 | console.log("执行任务"); 25 | }); 26 | 27 | //4.以天单位执行 28 | //比如:每天6点30分执行 29 | var rule = new schedule.RecurrenceRule(); 30 | rule.dayOfWeek = [0, new schedule.Range(1, 6)]; 31 | rule.hour = 6; 32 | rule.minute =30; 33 | var j = schedule.scheduleJob(rule, function(){ 34 |     console.log("执行任务"); 35 | getData(); 36 | }); -------------------------------------------------------------------------------- /demo/nodeMailDemo/app.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); //发送邮件的node插件 2 | 3 | let transporter = nodemailer.createTransport({ 4 | service: '126', // 发送者的邮箱厂商,支持列表:https://nodemailer.com/smtp/well-known/ 5 | port: 465, // SMTP 端口 6 | secureConnection: true, // SSL安全链接 7 | auth: { //发送者的账户密码 8 | user: 'artistcoder@126.com', //账户 9 | pass: 'xxxx', //smtp授权码,到邮箱设置下获取 10 | } 11 | }); 12 | 13 | let mailOptions = { 14 | from: '"vince" ', // 发送者昵称和地址 15 | to: 'like@vince.studio', // 接收者的邮箱地址 16 | subject: '一封暖暖的小邮件', // 邮件主题 17 | text: 'test mail', //邮件的text 18 | // html: html //也可以用html发送 19 | }; 20 | 21 | //发送邮件 22 | transporter.sendMail(mailOptions, (error, info) => { 23 | if (error) { 24 | return console.log(error); 25 | } 26 | console.log('邮件发送成功 ID:', info.messageId); 27 | }); 28 | -------------------------------------------------------------------------------- /email.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 今天是我们在一起的第 14 | <%= lastDay %> 15 | 16 |
17 |
18 | <%= weatherTip %> 19 | 近期天气预报 20 | <% for(var j = 0; j 21 |
22 | <%= threeDaysData[j].Day %> 23 |
24 | 25 | <%= threeDaysData[j].WeatherText %> 26 |
27 | <%= threeDaysData[j].Temperature %> 28 | 29 | 30 | <% if (threeDaysData[j].PollutionLevel=='level_1') { %> 31 |
32 | <%= threeDaysData[j].Pollution %> 33 |
34 | <% } %> 35 | <% if (threeDaysData[j].PollutionLevel=='level_2') { %> 36 |
37 | <%= threeDaysData[j].Pollution %> 38 |
39 | <% } %> 40 | <% if (threeDaysData[j].PollutionLevel=='level_3') { %> 41 |
42 | <%= threeDaysData[j].Pollution %> 43 |
44 | <% } %> 45 | <% if (threeDaysData[j].PollutionLevel=='level_4') { %> 46 |
47 | <%= threeDaysData[j].Pollution %> 48 |
49 | <% } %> 50 | <% if (threeDaysData[j].PollutionLevel=='level_5') { %> 51 |
52 | <%= threeDaysData[j].Pollution %> 53 |
54 | <% } %> 55 | <% if (threeDaysData[j].PollutionLevel=='level_6') { %> 56 |
57 | <%= threeDaysData[j].Pollution %> 58 |
59 | <% } %> 60 |
61 | <% } %> 62 |
63 |
64 | ONE · 一个 65 | <%= todaystr %> 66 | 67 | <%= todayOneData.type %> 68 |
<%= todayOneData.text %>
69 |
70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const superagent = require("superagent"); //发送网络请求获取DOM 2 | const cheerio = require("cheerio"); //能够像Jquery一样方便获取DOM节点 3 | const nodemailer = require("nodemailer"); //发送邮件的node插件 4 | const ejs = require("ejs"); //ejs模版引擎 5 | const fs = require("fs"); //文件读写 6 | const path = require("path"); //路径配置 7 | const schedule = require("node-schedule"); //定时器任务库 8 | //配置项 9 | 10 | //纪念日 11 | let startDay = "2016/6/24"; 12 | //当地拼音,需要在下面的墨迹天气url确认 13 | const local = "zhejiang/hangzhou"; 14 | 15 | //发送者邮箱厂家 16 | let EmianService = "126"; 17 | //发送者邮箱账户SMTP授权码 18 | let EamilAuth = { 19 | user: "xxx@126.com", 20 | pass: "xxxx" 21 | }; 22 | //发送者昵称与邮箱地址 23 | let EmailFrom = '"vince" '; 24 | 25 | //接收者邮箱地 26 | let EmailTo = "xxxxx@qq.com"; 27 | //邮件主题 28 | let EmailSubject = "一封暖暖的小邮件"; 29 | 30 | //每日发送时间 31 | let EmailHour = 5; 32 | let EmialMinminute= 20; 33 | 34 | // 爬取数据的url 35 | const OneUrl = "http://wufazhuce.com/"; 36 | const WeatherUrl = "https://tianqi.moji.com/weather/china/" + local; 37 | 38 | 39 | // 获取ONE内容 40 | function getOneData(){ 41 | let p = new Promise(function(resolve,reject){ 42 | superagent.get(OneUrl).end(function(err, res) { 43 | if (err) { 44 | reject(err); 45 | } 46 | let $ = cheerio.load(res.text); 47 | let selectItem = $("#carousel-one .carousel-inner .item"); 48 | let todayOne = selectItem[0]; 49 | let todayOneData = { 50 | imgUrl: $(todayOne) 51 | .find(".fp-one-imagen") 52 | .attr("src"), 53 | type: $(todayOne) 54 | .find(".fp-one-imagen-footer") 55 | .text() 56 | .replace(/(^\s*)|(\s*$)/g, ""), 57 | text: $(todayOne) 58 | .find(".fp-one-cita") 59 | .text() 60 | .replace(/(^\s*)|(\s*$)/g, "") 61 | }; 62 | resolve(todayOneData) 63 | }); 64 | }) 65 | return p 66 | } 67 | 68 | // 获取天气提醒 69 | function getWeatherTips(){ 70 | let p = new Promise(function(resolve,reject){ 71 | superagent.get(WeatherUrl).end(function(err, res) { 72 | if (err) { 73 | reject(err); 74 | } 75 | let threeDaysData = []; 76 | let weatherTip = ""; 77 | let $ = cheerio.load(res.text); 78 | $(".wea_tips").each(function(i, elem) { 79 | weatherTip = $(elem) 80 | .find("em") 81 | .text(); 82 | }); 83 | resolve(weatherTip) 84 | }); 85 | }) 86 | return p 87 | } 88 | 89 | // 获取天气预报 90 | function getWeatherData(){ 91 | let p = new Promise(function(resolve,reject){ 92 | superagent.get(WeatherUrl).end(function(err, res) { 93 | if (err) { 94 | reject(err); 95 | } 96 | let threeDaysData = []; 97 | let weatherTip = ""; 98 | let $ = cheerio.load(res.text); 99 | $(".forecast .days").each(function(i, elem) { 100 | const SingleDay = $(elem).find("li"); 101 | threeDaysData.push({ 102 | Day: $(SingleDay[0]) 103 | .text() 104 | .replace(/(^\s*)|(\s*$)/g, ""), 105 | WeatherImgUrl: $(SingleDay[1]) 106 | .find("img") 107 | .attr("src"), 108 | WeatherText: $(SingleDay[1]) 109 | .text() 110 | .replace(/(^\s*)|(\s*$)/g, ""), 111 | Temperature: $(SingleDay[2]) 112 | .text() 113 | .replace(/(^\s*)|(\s*$)/g, ""), 114 | WindDirection: $(SingleDay[3]) 115 | .find("em") 116 | .text() 117 | .replace(/(^\s*)|(\s*$)/g, ""), 118 | WindLevel: $(SingleDay[3]) 119 | .find("b") 120 | .text() 121 | .replace(/(^\s*)|(\s*$)/g, ""), 122 | Pollution: $(SingleDay[4]) 123 | .text() 124 | .replace(/(^\s*)|(\s*$)/g, ""), 125 | PollutionLevel: $(SingleDay[4]) 126 | .find("strong") 127 | .attr("class") 128 | }); 129 | }); 130 | resolve(threeDaysData) 131 | }); 132 | }); 133 | return p 134 | } 135 | 136 | // 发动邮件 137 | function sendMail(HtmlData) { 138 | const template = ejs.compile( 139 | fs.readFileSync(path.resolve(__dirname, "email.ejs"), "utf8") 140 | ); 141 | const html = template(HtmlData); 142 | 143 | let transporter = nodemailer.createTransport({ 144 | service: EmianService, 145 | port: 465, 146 | secureConnection: true, 147 | auth: EamilAuth 148 | }); 149 | 150 | let mailOptions = { 151 | from: EmailFrom, 152 | to: EmailTo, 153 | subject: EmailSubject, 154 | html: html 155 | }; 156 | transporter.sendMail(mailOptions, (error, info={}) => { 157 | if (error) { 158 | console.log(error); 159 | sendMail(HtmlData); //再次发送 160 | } 161 | console.log("邮件发送成功", info.messageId); 162 | console.log("静等下一次发送"); 163 | }); 164 | } 165 | 166 | // 聚合 167 | function getAllDataAndSendMail(){ 168 | let HtmlData = {}; 169 | // how long with 170 | let today = new Date(); 171 | console.log(today) 172 | let initDay = new Date(startDay); 173 | let lastDay = Math.floor((today - initDay) / 1000 / 60 / 60 / 24); 174 | let todaystr = 175 | today.getFullYear() + 176 | " / " + 177 | (today.getMonth() + 1) + 178 | " / " + 179 | today.getDate(); 180 | HtmlData["lastDay"] = lastDay; 181 | HtmlData["todaystr"] = todaystr; 182 | 183 | Promise.all([getOneData(),getWeatherTips(),getWeatherData()]).then( 184 | function(data){ 185 | HtmlData["todayOneData"] = data[0]; 186 | HtmlData["weatherTip"] = data[1]; 187 | HtmlData["threeDaysData"] = data[2]; 188 | sendMail(HtmlData) 189 | } 190 | ).catch(function(err){ 191 | getAllDataAndSendMail() //再次获取 192 | console.log('获取数据失败: ',err); 193 | }) 194 | } 195 | 196 | let rule = new schedule.RecurrenceRule(); 197 | rule.dayOfWeek = [0, new schedule.Range(1, 6)]; 198 | rule.hour = EmailHour; 199 | rule.minute = EmialMinminute; 200 | console.log('NodeMail: 开始等待目标时刻...') 201 | let j = schedule.scheduleJob(rule, function() { 202 | console.log("执行任务"); 203 | getAllDataAndSendMail(); 204 | }); 205 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-mail", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cheerio": "^1.0.0-rc.2", 13 | "ejs": "^2.5.7", 14 | "node-schedule": "^1.2.5", 15 | "nodemailer": "^4.4.0", 16 | "superagent": "^3.8.1" 17 | } 18 | } 19 | --------------------------------------------------------------------------------