前言
Node.js天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C、Java不一样);for循环体({})内引用i的值实际上是循环结束之后的值,因而引起各种undefined的问题;嵌套函数时,内层函数的变量并不能及时传导到外层(因为是异步)等等。
一、API分析
大众点评开放了查询餐馆信息的API,nofollow" target="_blank" href="http://api.t.dianping.com/n/base/cities.xml">这里给出了城市与cityid
之间的对应关系,
链接:http://m.api.dianping.com/searchshop.json?®ionid=0&start=0&categoryid=10&sortid=0&cityid=110
以GET
方式给出了餐馆的信息(JSON格式)。
首先解释下GET参数的含义:
1、start
为步进数,表示分步获取信息的index,与nextStartIndex
字段相对应;
2、cityid
表示城市id,比如,合肥对应于110;
3、regionid
表示区域id,每一个id代表含义在start=0
时rangeNavs
字段中有解释;
4、categoryid
表示搜索商家的分类id,比如,美食对应的id为10,具体每一个id的含义参见在start=0
时categoryNavs
字段;
5、sortid
表示商家结果的排序方式,比如,0对应智能排序,2对应评价最好,具体每一个id的含义参见在start=0时sortNavs字段。
在GET返回的JSON串中list字段为商家列表,id表示商家的id,作为商家的唯一标识。在返回的JSON
串中是没有商家的口味、环境、服务的评分信息以及经纬度的;
因而我们还需要爬取两个商家页面:http://m.dianping.com/shop/、http://m.dianping.com/shop//map。
通过以上分析,确定爬取策略如下(与dianping_crawler的思路相类似):
1、逐步爬取searchshop API的取商家基本信息列表;
2、通过爬取的所有商家的id,异步并发爬取评分信息、经纬度;
3、最后将三份数据通过id做聚合,输出成json文件。
二、爬虫实现
Node.js爬虫代码用到如下的第三方模块:
1、superagent
,轻量级http请求库,模仿了浏览器登录;
2、cheerio
,采用jQuery语法解析HTML元素,跟Python的PyQuery相类似;
3、async
,牛逼闪闪的异步流程控制库,Node.js的必学库。
导入依赖库:
rush:js;">
var util = require("util"); var superagent = require("superagent"); var cheerio = require("cheerio"); var async = require("async"); var fs = require('fs');
声明全局变量,用于存放配置项及中间结果:
ratings var posDict = {}; // id -> pos
判断一个id是否在前面出现过,若object
没有该id,则为undefined
(注意不是null
):
rush:js;">
function isVisited(id) { if (idVisited[id] != undefined) { return true; } else { idVisited[id] = true; return false; } }
采取回调函数的方式,实现顺序逐步地递归调用爬虫函数:
start && nextStart < cityOptions.threshHold) { DianpingSpider(regionId,nextStart,function (err,restaurants2) { if (err) return callback(err); callback(null,restaurants.concat(restaurants2)) }); } else { callback(null,restaurants); } }); }
在调用爬虫函数时,采用async
的mapLimit
函数实现对并发的控制;采用async
的until
对并发的协同处理,保证三份数据结果的id一致性(不会因为并发完成时间不一致而丢数据):
rush:js;">
DianpingSpider(0,restaurants) { if (err) return console.err(err.stack); var concurrency = 0; var crawlMove = function (id,callback) { var delay = parseInt((Math.random() * 30000000) % 1000,10); concurrency++; console.log('current concurrency:',concurrency,
Now crawling id=',id,costs(ms):',delay); parseShop(id); parseMap(id); setTimeout(function () { concurrency--; callback(null,id); },delay); }; async.mapLimit(restaurants,5,function (restaurant,callback) { crawlMove(restaurant.id,callback) },ids) { console.log('crawled ids:',ids); var resultArray = []; async.until( function () { return restaurants.length === Object.keys(
ratingDict).length && restaurants.length === Object.keys(posDict).length },function (callback) { setTimeout(function () { callback(null) },1000) },function (err) { restaurants.forEach(function (restaurant) { var
rating =
ratingDict[restaurant.id]; var pos = posDict[restaurant.id]; var result = Object.assign(restaurant,
rating,pos); resultArray.push(result); }); writeAsJson(resultArray); } ); }); });
其中,parseShop
与parseMap
分别为解析商家详情页、商家地图页:
div.desc > span"); restaurant.taste = desc.eq(0).text().split(":")[1]; restaurant.surrounding = desc.eq(1).text().split(":")[1]; restaurant.service = desc.eq(2).text().split(":")[1];
ratingDict[id] = restaurant; }); } function parseMap(id) { var mapBase = 'http://m.dianping.com/shop/
%s/map'; var mapUrl = util.format(mapBase,id); superagent.get(mapUrl) .end(function (err,res) { if (err) return console.err(err.stack); console.log('crawling map:',mapUrl); var restaurant = {}; var $ = cheerio.load(res.text); var data = $("body > script").text(); var latRegex = /(.*lat:)(\d+.\d+)(.*)/; var lngRegex = /(.*lng:)(\d+.\d+)(.*)/; if(data.match(latRegex) && data.match(lngRegex)) { restaurant.latitude = latRegex.exec(data)[2]; restaurant.longitude = lngRegex.exec(data)[2]; }else { restaurant.latitude = ''; restaurant.longitude = ''; } posDict[id] = restaurant; }); }
将array
的每一个商家信息,逐行写入到json文件中:
rush:js;">
function writeAsJson(arr) { fs.writeFile( 'data.json',arr.map(function (data) { return JSON.stringify(data); }).join('\n'),function (err) { if (err) return err.stack; }) }
总结
以上就是这篇文章的全部内容,希望本文能给学习或者使用node.js的朋友们带来一定的帮助,如果有疑问大家可以留言交流。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。