node.js - 用node做爬虫,循环模拟接口获取数据,async/await怎么做到同步操作,控制流程?
问题描述
目前的思路如下:1.获取分类下所有的标签页面url,2.循环抓取页面中抓取当前标签页获取json的api地址,3.抓取当前标签的商品列表,4.抓取当前标签的分页加载中的商品。
但是现在做到的是,第二步开始,未等到2-4执行完毕再循环下一个,有尝试用async/await,但是未实现流程控制。此处求教。
var http = require(’http’);var fs = require('fs');var superagent = require(’superagent’);var urls = [];var pageIndex = 1;var xlsxData = ’’;getGoodsUrl(urls);function getGoodsUrl(urls){ superagent.post(’http://bravetime.davdian.com/index.php?c=Index&a=getCatNavList’).type(’text/html; charset=utf-8’).set(’Accept’,’application/json, text/javascript, */*; q=0.01’).end(function(err, res) { if (err) {console.log(’分类数据请求失败’); } else {console.log(’分类数据请求成功’);var resData = res.text;var resData = JSON.parse(resData); if(resData.data.length > 0){ resData.data.forEach(function(item){var rowObj = [];var title = item.title;var category = item.content.category; category.forEach(function(item){ var text = []; text.push(title+ ’--’ + item.text); text.push(item.link); rowObj.push(text); }); urls.push(rowObj); }); loopUrls(urls);} else { console.log(’分类数据为空’);}// saveInfo(xlsxData); }})}function loopUrls(urls){ urls.forEach(function(item){var row = item;row.forEach(function(item){ var tagTitie = item[0]; var tegUrl = item[1]; getApiUrl(tagTitie,tegUrl);}); });}function getApiUrl(title,url){ var realUrl = ’http://bravetime.davdian.com’ + url; http.get(realUrl,function(res){ var html = ’’; res.on(’data’,function(data){ html += data; }); res.on(’end’,function(){ console.log(’正在获取’ + title + ’页面数据’); var reg = /goodsUrl = '(.+)'/; var apiUrl = reg.exec(html); getGoodsJson(apiUrl[1],pageIndex); }); }).on(’error’,function(){ console.log(’获取html出错!!’); });}function getGoodsJson(url,pageIndex){ superagent.post(’http://bravetime.davdian.com/’ + url + ’page_size=10&rp=catergory_search&rl=list’).send({page:pageIndex}).type(’application/x-www-form-urlencoded; charset=UTF-8’).set(’Accept’,’application/json, text/javascript, */*; q=0.01’).end(function(err, res) { if (err) {console.log(’第’ + pageIndex + ’页请求失败’); } else {console.log(’第’ + pageIndex + ’页请求成功’);var resData = res.text;var resData = JSON.parse(resData); if(resData.data.length > 0){ resData.data.forEach(function(item){ xlsxData = xlsxData + item.goods_name + ’ ’ + item.shop_price + ’ ’ + item.goods_number + ’rn’; }); pageIndex = parseInt(pageIndex) + 1; setTimeout(function(){ getGoodsJson(url,pageIndex); },200);} else { console.log(’数据已加载完毕’); saveTxt(xlsxData); pageIndex = 1; return false;}// saveInfo(xlsxData); }})}function saveTxt(data){ fs.writeFile('create.txt',data,function (err) {if (err) throw err ; console.log('File Saved !'); //文件被保存 }) ;}function saveInfo(data){ var buffer = xlsx.build([{name: 'mySheetName', data: data}]); fs.writeFileSync('myFile.xlsx', buffer, ’binary’); console.log(’excel保存成功’);}
下面是结果图示及代码执行顺序:
问题解答
回答1:generatorasyncpromise
回答2:你这整个过程都是异步的,没看出一点同步的意思。我觉得你可能没有理解什么是异步。
Async/await 是建立在 Promise 基础上的,而 Superagent 本身是支持 Promise 的,你可以直接用 async/await。
async function() { try { const result = await superagent.get(url); console.log(result.headers); console.log(result.body); } catch (error) { console.error(error); }}
http://visionmedia.github.io/...
http://www.ruanyifeng.com/blo...
然后你需要的就是把 http.get() 换成 superagent.get()。
回答3:一般他人的业务逻辑都没什么耐性能看下去。
如楼上所言,Async/await 是建立在 Promise 基础上,如果你调用的第三方库的API接口没有返回promise对象的话,想用Async/await 你只能自己每一步都新建一个promise对象,这样其实写起来也很麻烦,当然如果能返回promise对象的话就很方便。
以下不用promise 用node核心模块event写的,供你参考一下:
const EventEmitter = require(’events’);class MyEmitter extends EventEmitter {}const myEmitter = new MyEmitter();myEmitter.on(’step1’, (m) => { //第一步 //业务逻辑处理得到结果result1 //判断是否触发下一步,如有需要将这步的结果传给下一步 myEmitter.emit(’step2’, result1); });myEmitter.on(’step2’, (result1) => { //第二步 //业务逻辑处理得到结果result2 //判断是否触发下一步,如有需要将这步的结果传给下一步 myEmitter.emit(’step3’, result2);});myEmitter.on(’step3’, (result2) => { //以此类推});myEmitter.emit(’step1’, urls);回答4:
可以使用 Node8 的 util.promisify,或者 Bluebird 等把 Node 回调形式的函数改成 Promise 风格的函数,然后就可以使用 async/await 来写代码。
代码本身还是异步调用,只是写法看起来像是同步的。所以在写的时候还是要注意流程结构,尤其是在写循环的时候。代的代码太长,所以我写个小例子来说明
async function remoteCall() { // do something}list = []; // 假设是很多数据async function process() { // 这种写法必须要一个 remoteCall 完成之后才进行另一个 for (let i = 0; i < list.length; i++) {await remoteCall(); } doAfter();}async function process2() { // 这种写法没法 await list.forEach(function(t) {remoteCall(); });}async function process3() { // 这种写法 doAfter 一早就会执行 list.forEach(async function(t) {await remoteCall(); }); // 它可能会在 remoteCall() 之前 doAfter();}async function process4() { // 这种写法必须要全部 remoteCall 成功才能进行到 doAfter // remoteCall返回的 promise 如果 reject 会抛异常 var promises = list.map(t => remoteCall()); await Promise.all(promises); doAfter();}