关于nodejs爬虫:浅尝一下Node爬虫吧~

新建一个文件夹,这里我命名为“爬”。 mkdir pa初始化一个工程,并装置相干模块。 cd pa// 装置package.jsonnpm init// 装置cheerio,用来在服务端应用jq语法操作domnpm install cheerio --save// 装置request,用来发送网络申请npm install request --save新建文件命名为app.js,依照需要爬取数据,并保留到data.txt。 const http = require('http')const fs = require('fs')const cheerio = require('cheerio')function getData(url, title) { http.get(url, function(res) { let html = '' res.setEncoding('utf-8') res.on('data', function(chunk) { html += chunk }) res.on('end', function() { const $ = cheerio.load(html) // 按页面dom构造获取你须要的数据 const content = `\n\n\n\n\n\n--------布告【${title}】的内容---------\n\n` + $('.xq4').text().trim() + `\n\n--------布告【${title}】的内容完结喽---------\n\n\n` fs.appendFile('./datas/data.txt', content, 'utf-8', function(err) { if (err) { console.log(err); } }) }) })}// 依照理论状况编写申请http.get(`接口地址`, function(res) { res.setEncoding('utf-8') res.on('data', function(res) { res = JSON.parse(res) res.data.forEach(item => { const url = `页面地址带上接口返回的参数${item.id}` getData(url, item.title) }); })})执行node app.js,一个超级简略的爬虫就实现了~

March 28, 2022 · 1 min · jiezi

关于nodejs爬虫:node爬虫遇到的各种问题cheeriopuppeteer

工夫:2021年4月1号,文中各版本号以该工夫为背景问题一、网页采纳gb2312编码,爬取后中文全副乱码环境:node@8.12.0,cheerio@0.22.0网站应用的gb2312编码,开始用http间接拜访网页,cheerio加载后console进去中文全副乱码: const http = require('http')const cheerio = require('cheerio')const baseUrl = '******'http.get(baseUrl, res => { let html = '' res.on('data', data => { html += data }) res.on('end', () => { downloadHandler(html) })}).on('error', () => { console.log('出错了')})function downloadHandler(html) { const $ = cheerio.load(html) // 默认解析 console.log($.html());}cheerio解析:不解析: function downloadHandler(html) { const $ = cheerio.load(html,{ decodeEntities: false }) // 不解析 console.log($.html());} 起因:Node不反对gb2312解决:应用superagent取代http,同时应用superagent-charset解决编码问题const request = require('superagent')require('superagent-charset')(request)const cheerio = require('cheerio')const baseUrl = '******'request.get(baseUrl) .buffer(true) .charset('gbk') .end((err, html) => { downloadHandler(html) })function downloadHandler(html) { const htmlText = html.text const $ = cheerio.load(htmlText,{ decodeEntities: false })}问题二、一个循环外部,每次循环都会有一个异步申请;循环内部须要等外部所有申请返回后果后再执行解决:const idList = [1,2,3,4]getData(idList).then(data => { // get data})function getData(idList) { let asyncPool = [] idList.forEach(id => { asyncPool.push((() => { return new Promise((resolve,reject) => { return request.get(`http://detail/${id}`) .buffer(true) .charset('gbk') .then(html => { return Promise.reslove(html) }) }) })()) }) return Promise.all(asyncPool).then(data => { return data })}问题三、运行puppeteer报错:unexpected token {环境:node@8.12.0,npm@6 ...

April 2, 2021 · 1 min · jiezi

关于sf笔记备份及迁移

sf笔记支持Markdown语法和常用编程语法,而且简约,个人感觉很好用,唯一缺少就是笔记分类归档。sf提供了编辑时笔记,访问路径:笔记url+/raw 实现思路:1.使用爬虫获取全部笔记列表,获取title和href。 2.通过https://segmentfault.com+href+/raw获得全部笔记编辑时内容。(通过https://segmentfault.com+href+/raw获得全部笔记编辑时内容) 使用:需要提供个人登录当前cookie修改js文件中cook变量 const cook =`PHPSESSID=web2~a……自行安装依赖 express,superagent,cheerio js: const express = require('express');const app = express();const superagent= require('superagent');const cheerio = require('cheerio');const cook =`xxx`;//笔记列表var page = 1;var nots = []; //最终返回数据var errorStop = 0;//笔记详情var nindex = 0; //当前笔记页var notsArr = [];let getHotNews = (res) => { let $ = cheerio.load(res.text); let dom = $('.drafts-stream .title a'); //翻页数据停止 errorStop = dom && dom.length ? 0 : 1; // 找到目标数据所在的页面元素,获取数据 $('.drafts-stream .title a').each((idx, ele) => { let url = $(ele).attr('href').indexOf('?')!=-1 ? $(ele).attr('href').split('?')[0]:$(ele).attr('href'); let news = { title: $(ele).text(), // 获取标题 href:'https://segmentfault.com'+ url + '/raw' // 获取链接 }; nots.push(news) // 存入最终结果数组 }); page++;};//获取笔记详情function getNoteContent(endData){ if( nindex+1 <= nots.length){ console.log(nindex,nots[nindex].href) superagent.get(nots[nindex].href).set('Cookie',cook).end((err, res) => { if(!err){ let $ = cheerio.load(res.text); notsArr.push({title:nots[nindex].title,content:res.text});// endData.send(notsArr) nindex++; getNoteContent(endData) }else{ endData.send({data:notsArr}); } }); }else{ endData.send({data:notsArr}) }}//获取笔记列表function getNotes(req,res){ superagent.get('https://segmentfault.com/user/note?page=' + page).set('Cookie',cook).end((err1, res1) => { if (!err1) { getHotNews(res1); if(!errorStop){ getNotes(req,res) }else{ //如果无笔记则返回前端 getNoteContent(res) } } else { errorStop = 1; console.log(`抓取失败 - ${err1}`) } }); }app.get('/', function (req, res) { res.header("Access-Control-Allow-Origin", "*"); //允许的header类型 res.header("Access-Control-Allow-Headers", "content-type"); //跨域允许的请求方式 res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS"); page = 1; nots = []; //最终返回数据 errorStop = 0; //笔记详情 nindex = 0; //当前笔记页 notsArr = []; getNotes(req, res);});let server = app.listen(5100, function () { let host = server.address().address; let port = server.address().port; console.log('Your App is running at http://%s:%s', host, port);});html: ...

September 9, 2019 · 2 min · jiezi

信息检索课程实验笔记

注:前端不相关---只是用node实现了一下如何爬网站数据(大学课程的实验) 网页索引与检索实验目的l 了解搜索引擎的工作原理及实现方法;l 熟悉倒排索引的创建;l 掌握查询处理技术。 实验要求l 独立或合作(1~2人)完成实验内容;l 独立完成实验报告;(简单要求如下)1) 实验目的、内容与要求及实验环境描述;2) 索引和检索系统设计思路及总体框架;3) (负责部分的)程序结构及具体实现的流程分析,提供主要数据结构、函数分析等;4) 实验结果分析;5) 系统的优缺点、以及待改进的地方;6) 在实验过程中遇到的问题,实验的心得体会。 实验内容3.1 倒排索引(1)网页预处理。对实验一采集到的网页数据进行预处理,包括:网页的去噪和正文信息提取、中文分词、停止词处理等。(2)设计和创建倒排索引。对每个索引的词,至少应该记录其文件频率(df)。设计置入文件的数据结构,至少记录每个词在各个文档中出现的次数,即词频(tf)。同时对每个文档,记录其文档长度。(3)对索引的过程,生成相关的统计信息,例如:创建索引所需的时间、索引的大小、词汇表长度、具有最大df值的词的置入列表的大小等。(可选) 3.2检索系统 (1)设计实现一个简单的检索系统,可输入检索词,并输出查询结果,按相关度排序。 (2)对指定的查询词(IR2019查询词.txt),给出每个查询结果排序,以及相似度得分。所提交的结果将被评估。提交的结果文件有查询结果的数据块构成。每个查询词对应一个结果数据块,每个查询词提交10条查询结果。每个结果数据块格式如下:第一行是查询词序号,如“TD01”每一行是一条查询结果记录,格式为: <URL Similarity>URL:网页的规范化URL,如“http://www.scut.edu.cn/new/90...”Similarity:相似度得分每个数据块的十条记录按相似度从高到低排序,每个数据块之间以一个空行隔开.(3)对结果进行人工判断相关或不相关,然后基于该判断用评测指标Precision@10和MAP计算系统的检索性能指标。(可选)(4)采用各种查询处理技术对查询进行优化处理。并对所采用的不同技术的应用效果进行比较分析。(可选) 提交内容l 程序:包括源程序及注释,程序安装使用说明;l 查询结果文件:查询词对应查询结果汇总l 实验报告:说明程序设计的思路,并对实验过程进行分析和总结。 参考资料l 参考课程讲义的倒排索引、查询处理与检索评估、搜索引擎等章节;l 开源索引系统Lucene:http://lucene.apache.org ----------工作内容分解1生成倒序索引的文档 2 生成实验要求的结果(实验关键词txt) 3 网页 可以实时查询并且生成关键结果 4在后台跑服务器的服务 读 数据 返回数据做的优化工作1 修改停用词表2 同义但是模糊的词3 没有用本地数据库保存词语4 自己获取了词频和词语出现的位置5 result 去重6 使用db.txt存储数据参考文章:1 http://nathanchen.github.io/1...2 nodejs 读写文件http://javascript.ruanyifeng....3 json格式化的网站https://www.bejson.com/逻辑备注:1writeFileSync这是会默认会覆盖原来的内容的

June 6, 2019 · 1 min · jiezi

nodejs-request-module里的json参数的一个坑

今天工作的时候遇到一个坑,在客户端用nodejs给服务器发送HTTP请求,服务器老是报错:In the context of Data Services an unknown internal server error occurred 经过服务器端调试发现,服务器根本就没有正确解析出这个请求的content-type。在postman里能工作的场景下,正确解析出的content-type是multipart/mixed: 而我的nodejs代码里明明指定了这个content-type的啊? 经过一行行代码分析,最后发现问题出在第63行的json字段的值。我错误的赋成了true。 这个参数起什么作用?调试一下就知道了。如果为true,进入第403行。 如果请求内部有entity的content-type不是application/x-www-form-urlencoded, 则进入第1293行。 safeStringify的实现逻辑就是浏览器原生的JSON.stringify, 把应用程序传入的json对象序列化成字符串。但是我的代码里,传入request module的请求体是一个字符串,而json参数设的又是true,所以逻辑上就不对了。把这个json参数的值改为false后,一切正常。 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

May 25, 2019 · 1 min · jiezi

如何用Visual-Studio-Code远程调试运行在服务器上的nodejs应用

假设我有一个nodejs应用,运行在AWS - 亚马逊云平台上(Amazone Web Service)。我想用本地的Visual Studio Code来远程调试服务器端的nodejs应用。 Visual Studio Code的调试配置里定义了两种类型,attach和launch。Visual Studio Code的官方文档对这两种调试启动行为的解释: The best way to explain the difference between launch and attach is think of a launch configuration as a recipe for how to start your app in debug mode before VS Code attaches to it,Launch的意思简而言之就是以debug模式启动app。 while an attachconfiguration is a recipe for how to connect VS Code's debugger to an app or process that's alreadyrunning.而Attach的含义是将Visual Studio Code的调试器绑定到一个已经处于运行状态的应用。 ...

May 9, 2019 · 1 min · jiezi

批量导出某个简书用户的所有文章列表和文章超链接

简书改版后,根据文章标题搜索文章的功能就不见了。 虽然简书提供了批量下载文章的功能,但是下载到本地的文章都是markdown格式的,不包含文章的链接,这不满足我的需求。 既然我是程序员,没有这个功能我就自己实现一个。 打开简书首页,发现默认只显示8篇文章,用鼠标滑动到屏幕底部后,会触发一个懒加载事件,到后台读取更多的文章列表,所以文章读取在服务器端是采取的分页实现。 打开Chrome开发者工具,观察网络请求,请求url中99b8712e8850是我简书用户id,page=2,3,4这些是分页代码。 每页的文章内容以html格式包含在响应结构里: 我关心的只是文章标题和文章链接,如上图高亮字段所示。 最开始我写了一个nodejs应用,代码如下: var request = require('request');var jsdom = require("jsdom");var JSDOM = jsdom.JSDOM;const PREFIX = "https://www.jianshu.com";const PAGE = "https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";const MAX = 2;var mArticleResult = new Map();var pageNumber;/* a given article: https://www.jianshu.com/p/963cd23fb092 value got from API: /p/5c1d0319dc42*/var lastPageReached = false;var url = "";var aHandlers = [];// use limited for loop to ease testingfor(var i = 0; i < MAX; i++){ pageNumber = i + 1; var url = PAGE + pageNumber; // console.log("current page: " + url); var pageOptions = { url: url, method: "GET", headers: { "Accept": "text/html" } }; aHandlers.push(getArticles(pageOptions, pageNumber)); if( lastPageReached) break;}console.log("promise handler size: " + aHandlers.length);Promise.all(aHandlers).then(function(){ var articleIndex = 0; for (var [key, value] of mArticleResult) { console.log("Article[" + articleIndex++ + "]: " + key + " = " + value); } console.log("done");} );function getArticles(pageOptions, pageNumber) { return new Promise(function(resolve,reject){ var requestC = request.defaults({jar: true}); requestC(pageOptions,function(error,response,body){ if( error){ console.log("error: " + error); resolve(error); } var document = new JSDOM(body).window.document; var content = document.getElementsByTagName("li"); for( var i =0; i < content.length; i++){ var li = content[i]; var children = li.childNodes; for( var j = 0; j < children.length; j++){ var eachChild = children[j]; if( eachChild.nodeName == "DIV"){ var grandChild = eachChild.childNodes; for( var k = 0; k < grandChild.length; k++){ var grand = grandChild[k]; if( grand.nodeName == "A"){ var fragment = grand.getAttribute("href"); if( fragment.indexOf("/p") < 0) continue; console.log("title: " + grand.text); var wholeURL = PREFIX + fragment; console.log("url: " + wholeURL); if( mArticleResult.has(grand.text)){ lastPageReached = true; console.log("article size: " + mArticleResult.size); resolve(pageNumber); } mArticleResult.set(grand.text, wholeURL); } } } } }// end of outer loop resolve(pageNumber); }); });}原理就是使用nodejs的request module,向简书网站同时发起多个请求,每个请求读取一页的简书文章。 ...

May 9, 2019 · 3 min · jiezi

tesseract-OCR 图像识别插件 node-tesr 了解一下?

前言该项目诞生于一次爬虫事件,当时一时兴起想把某租房网信息爬下来,前面进行的还是挺顺畅的,但是在租房价格信息上被摆了一道,房屋的价格信息为一个数字图片为底加上偏移量来显示的,和雪碧图一样的实现方式,当然,其中加上了一点小算法,具体如下。获取数字图片信息和 offset 信息{ “offset”: [ [1, 4, 2, 8], [5, 1, 7, 8], [5, 1, 3, 8], … ] }由 offset 信息加上一点算法得出 position 信息(background-position: xxx px)以数字图片为背景,加上偏移,append 到价格信息他应该在地方略一思索,倒也不是什么大事儿,只要加个识别的过程再辅以算法即可。在实行图像识别的过程中借助到了 google 的开源软件 tesseract-OCR,因为爬虫环境是 node,遂写了一个适用于 tesseract-OCR 最新版本的 node 插件,后续还添加了命令行使用的功能。演示命令行使用 — 1命令行使用 — 2模块使用 — 1项目在这里如果觉得我对你有帮助,不妨给我个 star 吧,蟹蟹~github node-tesr正文命令行使用想要使用图像识别首先要确保电脑中已经安装了 tesseract-OCR 点击下载。想要使用命令行建议全局安装npm install node-tesr -gtesr –from=./test/output.jpg –to=./output.txt参数说明–from 需要识别的图片路径(必须)–to 若传入此参数会将识别的文字输出到该文件下(非必须,默认会将识别内容输出到命令行)–l 识别语言,对中文稍微做了点处理,识别简体 –l=chs,识别繁体 –l=cht(非必须,默认为 eng)–p 见 lib/config.js 里的说明(非必须,默认为 3 自动模式)–o 见 lib/config.js 里的说明(非必须,默认为 3 自动模式)模块引入使用npm install node-tesrconst tesseract = require(’node-tesr’)tesseract(’./output.jpg’, { l: ’eng’, oem: 3, psm: 3 }, function(err, data) { // 此处获得识别内容 console.log(data)})// 或者如下也可tesseract(’./output.jpg’, function(err, data) { // 此处获得识别内容 console.log(data)})后语效果经测试效果还是不错的,但是有一点需要注意一下,上面提到该网站的数字图片是透明底的,测试发现 tesseract-OCR 对透明底的似乎无解,这个时候就需要结合一下 images 这个 node 插件let images = require(‘images’)images(500, 100) .fill(0xff, 0xff, 0xff, 1) .draw(images(‘demo.png’), 10, 10) .save(‘output.jpg’, { quality: 100 })将透明底填充为白底即可正常识别如何提高我的图像识别准确率老板!我的图像识别率很低怎么破!来,看这里,这个可以提高图像识别率。识别算法学习待办增加网络地址图片也可识别的功能使用 then 来处理回调页脚代码即人生,我甘之如饴。我在这里 gayhub@jsjzh 欢迎大家来找我玩儿。欢迎小伙伴们直接加我,拉你进群一起学习前端呀,记得备注一下你来自哪里哦。 ...

March 14, 2019 · 1 min · jiezi

用Node+wechaty写一个爬虫脚本每天定时给女朋友发暖心微信消息

wechatBot微信每日说,每日自动发送微信消息给你心爱的人项目介绍灵感来源在掘金看到了一篇《用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件》后,在评论区偶然看到一位读者说可不可以用微信实现一下。然后最近刚好在做微信机器人的小项目,那就把这个定时任务放到微信上去做吧,说干就干,撸了一下午终于撸出来了。项目地址github:https://github.com/gengchen528/wechatBot使用库wechaty - 微信操作node-schedule - 定时任务superagent - 爬取页面信息cheerio - 抓取页面qrcode-terminal - 终端显示二维码功能定时给朋友发送每日天气提醒以及每日一句根据关键词自动加好友和自动拉群功能后续继续扩展吧…(你有好的想法也可以提pr)数据来源每日一句和上面的大佬一样也是来自one天气信息来自墨迹天气定时任务node-schedule非你莫属了,可以定时每个月、每个礼拜、每天具体什么时候执行什么任务实现效果由于是微信定时发送消息,较邮件来说,微信无法把图片和文字放在同一消息框中,所以美观度来说可能没有邮件好,不过文字进行排版后还是可以的,由于时间仓促,所以文字比较少,后续会继续增加内容;代码说明目录结构config: 存放公共变量和superagent的配置schedule: 任务调度的配置superagent: 获取每日一句和天气信息untils: 抽取的共用方法核心代码index.js关于微信的登录,定时任务的创建,发送信息的获取都在这个文件里/** * WechatBot * - https://github.com/gengchen528/wechatBot /const {Wechaty,Friendship} = require(‘wechaty’)const schedule = require(’./schedule/index’)const config = require(’./config/index’)const untils = require(’./untils/index’)const superagent = require(’./superagent/index’)// 二维码生成function onScan (qrcode, status) { require(‘qrcode-terminal’).generate(qrcode) // 在console端显示二维码 const qrcodeImageUrl = [ ‘https://api.qrserver.com/v1/create-qr-code/?data=’, encodeURIComponent(qrcode), ].join(’’) console.log(qrcodeImageUrl)}// 登录async function onLogin (user) { console.log(贴心小助理${user}登录了) // 登陆后创建定时任务 schedule.setSchedule(config.SENDDATE,()=>{ console.log(‘你的贴心小助理开始工作啦!’) main() })}//登出function onLogout(user) { console.log(${user} 登出)}// 自动加群功能async function onMessage (msg) { const contact = msg.from() // 发消息人 const content = msg.text() //消息内容 const room = msg.room() //是否是群消息 if(room){ console.log(群名: ${room.topic()} 发消息人: ${contact.name()} 内容: ${content}) }else { console.log(发消息人: ${contact.name()} 消息内容: ${content}) } if (msg.self()) { return } if(/微信每日说|每日说|微信机器人/.test(content)){ let keyRoom = await this.Room.find({topic: /^微信每日说/i}) if(keyRoom){ try{ await keyRoom.add(contact) await keyRoom.say(‘微信每日说:欢迎新朋友 ‘, contact) }catch (e) { console.error(e) } } }}// 自动加好友功能async function onFriendShip(friendship) { let logMsg try { logMsg = ‘添加好友’ + friendship.contact().name() console.log(logMsg) switch (friendship.type()) { /* * * 1. New Friend Request * * when request is set, we can get verify message from request.hello, * and accept this request by request.accept() / case Friendship.Type.Receive: if (/微信每日说|微信机器人|微信|每日说/i.test(friendship.hello())) { logMsg = ‘自动添加好友,因为验证信息中带关键字‘每日说’’ await friendship.accept() } else { logMsg = ‘没有通过验证 ’ + friendship.hello() } break /* * * 2. Friend Ship Confirmed * / case Friendship.Type.Confirm: logMsg = ‘friend ship confirmed with ’ + friendship.contact().name() break } } catch (e) { logMsg = e.message } console.log(logMsg)}// 自动发消息功能async function main() { let contact = await bot.Contact.find({name:config.NICKNAME}) || await bot.Contact.find({alias:config.NAME}) // 获取你要发送的联系人 let one = await superagent.getOne() //获取每日一句 let weather = await superagent.getWeather() //获取天气信息 let today = await untils.formatDate(new Date())//获取今天的日期 let memorialDay = untils.getDay(config.MEMORIAL_DAY)//获取纪念日天数 let str = today + ‘<br>’ + ‘今天是我们在一起的第’ + memorialDay + ‘天’ + ‘<br><br>今日天气早知道<br><br>’ + weather.weatherTips +’<br><br>’ +weather.todayWeather+ ‘每日一句:<br><br>’+one+’<br><br>’+’——来自最爱你的我’ await contact.say(str)//发送消息}const bot = new Wechaty()bot.on(‘scan’, onScan)bot.on(’login’, onLogin)bot.on(’logout’, onLogout)bot.on(‘message’, onMessage)bot.on(‘friendship’, onFriendShip)bot.start() .then(() => console.log(‘开始登陆微信’)) .catch(e => console.error(e))superagent/index.jsconst superagent = require(’../config/superagent’)const config = require(’../config/index’)const cheerio = require(‘cheerio’)async function getOne() { // 获取每日一句 let res = await superagent.req(config.ONE,‘GET’) let $ = cheerio.load(res.text) let todayOneList = $(’#carousel-one .carousel-inner .item’) let todayOne = $(todayOneList[0]).find(’.fp-one-cita’).text().replace(/(^\s)|(\s*$)/g, “”) return todayOne;}async function getWeather() { //获取墨迹天气 let url = config.MOJI_HOST+config.CITY+’/’+config.LOCATION let res = await superagent.req(url,‘GET’) let $ = cheerio.load(res.text) let weatherTips = $(’.wea_tips em’).text() const today = $(’.forecast .days’).first().find(’li’); let todayInfo = { Day:$(today[0]).text().replace(/(^\s*)|(\s*$)/g, “”), WeatherText:$(today[1]).text().replace(/(^\s*)|(\s*$)/g, “”), Temp:$(today[2]).text().replace(/(^\s*)|(\s*$)/g, “”), Wind:$(today[3]).find(’em’).text().replace(/(^\s*)|(\s*$)/g, “”), WindLevel:$(today[3]).find(‘b’).text().replace(/(^\s*)|(\s*$)/g, “”), PollutionLevel:$(today[4]).find(‘strong’).text().replace(/(^\s*)|(\s*$)/g, “”) } let obj = { weatherTips:weatherTips, todayWeather:todayInfo.Day + ‘:’ + todayInfo.WeatherText + ‘<br>’ + ‘温度:’ + todayInfo.Temp + ‘<br>’ + todayInfo.Wind + todayInfo.WindLevel + ‘<br>’ + ‘空气:’ + todayInfo.PollutionLevel + ‘<br>’ } return obj}module.exports ={ getOne,getWeather}项目运行由于需要安装chromium, 所以要先配置一下镜像,注意由于wechaty的限制,最好使用node10以上版本npmnpm config set registry https://registry.npm.taobao.orgnpm config set disturl https://npm.taobao.org/distnpm config set puppeteer_download_host https://npm.taobao.org/mirrorsyarnyarn config set registry https://registry.npm.taobao.orgyarn config set disturl https://npm.taobao.org/distyarn config set puppeteer_download_host https://npm.taobao.org/mirrors然后进行项目安装git clone git@github.com:gengchen528/wechatBot.gitcd wechatBotnpm install 或 cnpm install参数配置wechatBot/config/index.js// 配置文件// 配置文件module.exports ={ ONE:‘http://wufazhuce.com/',//ONE的web版网站 MOJI_HOST:‘https://tianqi.moji.com/weather/china/', //中国墨迹天气url CITY:‘shanghai’,//收信者所在城市 LOCATION:‘pudong-new-district’,//收信者所在区 MEMORIAL_DAY:‘2015/04/18’, //你和收信者的纪念日 NAME:‘Leo_chen’,//微信备注姓名 NICKNAME:‘Leo_chen’, //微信昵称 SENDDATE:‘30 * * * * *’,//定时发送时间,规则见 /schedule/index.js}开始运行npm run start然后掏出你的手机,最好使用小号,扫描控制台的二维码即可待解决问题由于微信登录和邮件登录不同,所以无法使用进程守护工具,目前没有测试是否能够长时间登录因为node的原因,如果发生错误,可能会导致任务无法进行,需要手动重启并登录最好能够使用小号登录,如果是常用微信登录,在电脑客户端登陆后可能会wechaty挤掉墨迹天气页面在获取的时候可能会存在延时,有时可能获取不到后续功能为了防止占用你的微信号,你和你的爱人添加我的微信后。你发送指定内容,我将会每天帮你发送消息还有在思考中…(你有好的想法也可以提出来)最后因为给这个微信加了自动加好友和拉群功能,所以有兴趣的小伙伴可以加我的微信进行测试,记得在加好友的时候带上暗号:微信每日说,然后发送微信每日说我会把你拉到群中(由于wechaty网页接口变更,目前自动拉群功能,免费版已经不能使用,只可以自动加好友,不过我会手动拉你进群 ????)赶快亲自试一试吧,相信你会挖掘出更多好玩的功能github:https://github.com/gengchen528/wechatBot ...

March 1, 2019 · 3 min · jiezi

用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:superagent.get(URL).end(function(err,res){ //}用cheerio来筛选superagent获取到的DOM,取出需要的DOMimgUrl:$(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的代码,天气预报网页也是一个道理:const superagent = require(‘superagent’); //发送网络请求获取DOMconst 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.jsconst 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’));//将数据传入模版中,生成HTMLconst html = template(data);console.log(html)mail.ejs<!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发送邮件这里我们可以发送纯text也可以发送html,注意的是邮箱密码不是你登录邮箱的密码,而是smtp授权码,什么是smtp授权码呢?就是你的邮箱账号可以使用这个smtp授权码在别的地方发邮件,一般smtp授权码在邮箱官网的设置中可以看的到,设置如下注释。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来定时执行任务,示例如下: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来判断所有数据获取完毕,再发送邮件// 其中一个数据获取函数,其他的也是类似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的参数,发送邮件模板。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); })}发送邮件具体代码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 https://github.com/Vincedream…打开main.js,修改配置项//纪念日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:https://github.com/Vincedream/NodeMail ...

February 27, 2019 · 3 min · jiezi

express+request实现-图夫在线爬取网页图片

先奉上图夫地址:https://tufu.xkboke.comGIT开源地址:git地址(欢迎star)懒惰驱动Idea有时候在站酷或者UI中国看到很好的图片和作品都会想收藏下来学习一下,但是每次右击另存为都很麻烦,而且有的还要放大后才有原图可以下载;作为一个伪全栈怎么能忍呢,然后就想着扒扒他们网站的源码看,这一看发现图片原图存放的位置都有着规律,这就很高兴啦,哈哈!雏形诞生浪起来!!很快完成了第一个小脚本,顺利把需要的图片下载了下来,但只是最简单的爬取图片,后来优化了一下,把每次下载的图片都放到不同文件夹。但是转头一想,独乐乐不如众乐乐,就想着干脆把这个脚本工具化,做一个可以兼容多个网站的爬虫工具,并且可以批量下载原图,想法很快被付诸实践,经过不断的改版后,终于我的图夫出来了第一版。迭代优化一开始只支持站酷和UI中国,而且对其他的网站都兼容的不是很好,没关系,先做出第一版,接着慢慢迭代,后来根据反馈又添加了涂鸦王国,设计癖,视觉ME等网站,最近几天在逛贴吧,发现贴吧的一些图片也是很漂亮,有很多可以当壁纸的图片,但是要下到原图需要三四次步骤,而且一次只能下载一个,所以呢,我又把它加入我的图夫工具中了,哈哈!我的图夫又慢慢壮大起来了!先放一张图展示一下我的图夫技术栈其实原理大家都知道的,就是爬虫,只是我把爬虫可视化做成了一个工具,方便日常使用而已,这里主要使用的是express,库的话用的是request,compressing用来压缩文件夹,node-uuid用来生成随机hash;放一张目录结构部分代码index.js 主要请求文件,其他文件就移步git查看吧const path = require(‘path’);const fs = require(‘fs’);const analyze = require(’./analyze’);const tarTool = require(’./tarTool’)const uuid =require(’node-uuid’)/** * 根据hash值创建文件夹 * @param path /function write(path) { fs.exists(path, function (exists) { //path为文件夹路径 if (!exists) { fs.mkdir(path, function (err) { if (err) { console.log(‘创建失败’); return false; } else { console.log(‘创建成功’); } }) } })}/* * 请求图片地址 * @param response * @param req * @param next /function start(req,response,next) { const hash = uuid.v1().replace(/-/g, “”) const imgDir = path.join(path.resolve(__dirname, ‘..’), ‘output/img/’+hash); write(imgDir) // 发起请求获取 DOM console.log(‘请求地址’,req.url); request(req.url, function(err, res, body) { if (!err && res) { console.log(‘start’); // 将 downLoad 函数作为参数传递给 analyze 模块的 findImg 方法 analyze.findImg(body,req.type,imgDir,downLoad,req.url); response.json({head: {code: 0, msg: ‘ok’}, data: hash}) }else { response.json({head: {code: 1000, msg: err}, data: ‘’}) } });}/* * 获取到 findImg 函数返回的图片地址后,利用 request 再次发起请求,将数据写入本地。 * * @param {} imgUrl * @param {} i * @param {} imgDir /function downLoad(imgUrl, i,imgDir) { console.log(‘图片地址’,imgUrl); let ext = imgUrl.split(’.’).pop(); // 再次发起请求,写文件 request(imgUrl).pipe(fs.createWriteStream(path.join(imgDir, i + ‘.’ + ext), { ’encoding’: ‘utf8’, }));}/* * 下载图片到本地后,利用tar压缩成一个压缩包,并返回路径 * @param {} req * @param {} response * @param {} next */function tarFile(req,response,next) { console.log(‘接收’,req); tarTool.tarTool(req.path,response)}module.exports= { getImg:start, tarTool:tarFile}使用方法当然既然是工具,就必须非常简单啦,你只需复制你要下载页面的URL链接,然后粘贴到我的输入框中就可以,然后选择网站类型(当然悄悄告诉你,不选也没关系,我做了一个校验),然后就是点击搜索了,接下来就是耐心的等待…loading….(因为服务器的带宽只有1M,所以会下载有点慢,如果你愿意打赏一下,我也是不介意的,哈哈),执行完毕后,会出现下载按钮,你只需要点击下载即可下载打包完毕的文件了。已支持网站站酷UI中国涂鸦王国设计癖视觉ME百度贴吧…(等待你的意见)声明本工具仅作为技术交流工具,不得用于任何商业用途或获利。本网站不存储任何图片,所有内容均通过爬虫工具爬取网页上存在的内容。通过本网站下载的任何图片不代表你拥有商用的权利或者授权,如需授权或商用请联系原网站作者或平台,谢谢理解!最后,奉上我的开源GIT地址:https://github.com/gengchen52…图夫网站地址:https://tufu.xkboke.com如果喜欢的话,请给个star,如果有什么想法的话可以提issues,也可以微信联系我,欢迎交流,也可在评论中留下你想采集的网站链接,我会不定期更新图夫完全支持的网站往期文章mpvue小程序《校友足迹》成长记(一)使用node脚本全自动删除豆瓣评论与帖子(这个最近正在更新,也会上线可视化操作,敬请关注)[基于mongodb+express+vue+axios+bootstrap的掘金最热文章收藏评论分析](https://juejin.im/post/5a1293…个人博客:www.xkboke.com ...

January 22, 2019 · 1 min · jiezi

使用nodejs代码在SAP C4C里创建Individual customer

需求:使用nodejs代码在SAP Cloud for Customer里创建Individual customer实例。代码:var createAndBind = require(’../jerryapp/service/createAccountinC4C.js’);createAndBind(“o0KlM1i2_4-zHRmDk-IWGRlA1Cjc”);上述代码基于微信open IDo0KlM1i2_4-zHRmDk-IWGRlA1Cjc在系统里创建一个Individual customer。createAccountinC4C.js的具体实现在Jerry的github上:https://github.com/i042416/wechat/blob/master/jerryapp/service/createAccountInC4C.js在命令提示行里输入命令node createAccountAndSocialProfile.js:上述代码会自动在系统里创建Individual customer,并打印出ID:要获取更多Jerry的原创文章,请关注公众号"汪子熙":

January 15, 2019 · 1 min · jiezi