Node.js大众点评爬虫
大众点评上有很多美食餐馆的信息,正好可以拿来练练手Node.js。
1. API分析
大众点评开放了查询商家信息的API,这里给出了城市与cityid之间的对应关系,链接http://m.api.dianping.com/searchshop.json?®ionid=0&start=0&categoryid=10&sortid=0&cityid=110以GET方式给出了餐馆的信息(JSON格式)。首先解释下GET参数的含义:
- start为步进数,表示分步获取信息的index,与
nextStartIndex字段相对应; - cityid表示城市id,比如,合肥对应于110;
- regionid表示区域id,每一个id代表含义在start=0时
rangeNavs字段中有解释; - categoryid表示搜索商家的分类id,比如,美食对应的id为10,具体每一个id的含义参见在start=0时
categoryNavs字段; - sortid表示商家结果的排序方式,比如,0对应智能排序,2对应评价最好,具体每一个id的含义参见在start=0时
sortNavs字段。
在GET返回的JSON串中list字段为商家列表,id表示商家的id,作为商家的唯一标识。在返回的JSON串中是没有商家的口味、环境、服务的评分信息以及经纬度的;因而我们还需要爬取两个商家页面:http://m.dianping.com/shop/<id>、http://m.dianping.com/shop/<id>/map。
通过以上分析,确定爬取策略如下(与dianping_crawler的思路相类似):
- 逐步爬取searchshop API的取商家基本信息列表;
- 通过爬取的所有商家的id,异步并发爬取评分信息、经纬度;
- 最后将三份数据通过id做聚合,输出成json文件。
2. 爬虫实现
Node.js爬虫代码用到如下的第三方模块:
- superagent,轻量级http请求库,模仿了浏览器登录;
- cheerio,采用jQuery语法解析HTML元素,跟Python的PyQuery相类似;
- async,牛逼闪闪的异步流程控制库,Node.js的必学库。
导入依赖库:
var util = require("util");
var superagent = require("superagent");
var cheerio = require("cheerio");
var async = require("async");
var fs = require('fs');
声明全局变量,用于存放配置项及中间结果:
var cityOptions = {
"cityId": 110, // 合肥
// 全部商区, 蜀山区, 庐阳区, 包河区, 政务区, 瑶海区, 高新区, 经开区, 滨湖新区, 其他地区, 肥西县
"regionIds": [0, 356, 355, 357, 8840, 354, 8839, 8841, 8843, 358, -922],
"categoryId": 10, // 美食
"sortId": 2, // 人气最高
"threshHold": 5000 // 最多餐馆数
};
var idVisited = {}; // used to distinct shop
var ratingDict = {}; // id -> ratings
var posDict = {}; // id -> pos
判断一个id是否在前面出现过,若object没有该id,则为undefined(注意不是null):
function isVisited(id) {
if (idVisited[id] != undefined) {
return true;
} else {
idVisited[id] = true;
return false;
}
}
采取回调函数的方式,实现顺序逐步地递归调用爬虫函数(代码结构参考了这里):
function DianpingSpider(regionId, start, callback) {
console.log('crawling region=', regionId, ', start =', start);
var searchBase = 'http://m.api.dianping.com/searchshop.json?®ionid=%s&start=%s&categoryid=%s&sortid=%s&cityid=%s';
var url = util.format(searchBase, regionId, start, cityOptions.categoryId, cityOptions.sortId, cityOptions.cityId);
superagent.get(url)
.end(function (err, res) {
if (err) return console.err(err.stack);
var restaurants = [];
var data = JSON.parse(res.text);
var shops = data['list'];
shops.forEach(function (shop) {
var restaurant = {};
if (!isVisited(shop['id'])) {
restaurant.id = shop['id'];
restaurant.name = shop['name'];
restaurant.branchName = shop['branchName'];
var regex = /(.*?)(\d+)(.*)/g;
if (shop['priceText'].match(regex)) {
restaurant.price = parseInt(regex.exec(shop['priceText'])[2]);
} else {
restaurant.price = shop['priceText'];
}
restaurant.star = shop['shopPower'] / 10;
restaurant.category = shop['categoryName'];
restaurant.region = shop['regionName'];
restaurants.push(restaurant);
}
});
var nextStart = data['nextStartIndex'];
if (nextStart > 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一致性(不会因为并发完成时间不一致而丢数据):
DianpingSpider(0, 0, function (err, 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)
}, function (err, 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分别为解析商家详情页、商家地图页:
function parseShop(id) {
var shopBase = 'http://m.dianping.com/shop/%s';
var shopUrl = util.format(shopBase, id);
superagent.get(shopUrl)
.end(function (err, res) {
if (err) return console.err(err.stack);
console.log('crawling shop:', shopUrl);
var restaurant = {};
var $ = cheerio.load(res.text);
var desc = $("div.shopInfoPagelet > 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文件中:
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天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C、Java不一样);for循环体({})内引用i的值实际上是循环结束之后的值,因而引起各种undefined的问题;嵌套函数时,内层函数的变量并不能及时传导到外层(因为是异步)等等。
Node.js大众点评爬虫的更多相关文章
- Node.js 网页瘸腿爬虫初体验
延续上一篇,想把自己博客的文档标题利用Node.js的request全提取出来,于是有了下面的初哥爬虫,水平有限,这只爬虫目前还有点瘸腿,请看官你指正了. // 内置http模块,提供了http服务器 ...
- 基于Node.js的强大爬虫 能直接发布抓取的文章哦
基于Node.js的强大爬虫 能直接发布抓取的文章哦 基于Node.js的强大爬虫能直接发布抓取的文章哦!本爬虫源码基于WTFPL协议,感兴趣的小伙伴们可以参考一下 一.环境配置 1)搞一台服务器,什 ...
- 爬虫--反爬--css反爬---大众点评爬虫
大众点评爬虫分析,,大众点评 的爬虫价格利用css的矢量图偏移,进行加密 只要拦截了css 解析以后再写即可 # -*- coding: utf- -*- """ Cre ...
- node.js主从分布式爬虫
前言 前文介绍过用Python写爬虫,但是当任务多的时候就比较慢, 这是由于Python自带的http库urllib2发起的http请求是阻塞式的,这意味着如果采用单线程模型,那么整个进程的大部分时间 ...
- 使用node.js制作简易爬虫
最近看了些node.js方面的知识,就像拿它来做些什么.因为自己喜欢摄影,经常上蜂鸟网,所以寻思了一下,干脆做个简单的爬虫来扒论坛的帖子. 直接上代码吧. var sys = require(&quo ...
- node.js 89行爬虫爬取智联招聘信息
写在前面的话, .......写个P,直接上效果图.附上源码地址 github/lonhon ok,正文开始,先列出用到的和require的东西: node.js,这个是必须的 request,然发 ...
- 【Python3爬虫】大众点评爬虫(破解CSS反爬)
本次爬虫的爬取目标是大众点评上的一些店铺的店铺名称.推荐菜和评分信息. 一.页面分析 进入大众点评,然后选择美食(http://www.dianping.com/wuhan/ch10),可以看到一页有 ...
- 使用Node.js搭建数据爬虫crawler
0. 通用爬虫框架包括: (1) 将爬取url加入队列,并获取指定url的前端资源(crawler爬虫框架主要使用Crawler类进行抓取网页) (2)解析前端资源,获取指定所需字段的值,即获取有价值 ...
- Node.js 网页爬虫再进阶,cheerio助力
任务还是读取博文标题. 读取app2.js // 内置http模块,提供了http服务器和客户端功能 var http=require("http"); // cheerio模块, ...
随机推荐
- document.documentElement.clientHeight 与 document.body.clientHeight(杜绝千篇一律的抄袭!!)
document.documentElement.clientHeight 与 document.body.clientHeight用来获取页面可视高度我觉得有点问题.这两个应该不是一个东西. 页面中 ...
- Node.js:OS模块
os模块,可以用来获取操作系统相关的信息和机器物理信息,例如操作系统平台,内核,cpu架构,内存,cpu,网卡等信息. 使用如下所示: const os = require('os'); var de ...
- Xamarin+Prism开发详解三:Visual studio 2017 RC初体验
Visual studio 2017 RC出来一段时间了,最近有时间就想安装试试,随带分享一下安装使用体验. 1,卸载visual studio 2015 虽然可以同时安装visual studio ...
- H5坦克大战之【画出坦克】
今天是个特殊的日子,圣诞节,也是周末,在这里先祝大家圣诞快乐!喜庆的日子,我们可以稍微放松一下,扯一扯昨天雷霆对战凯尔特人的比赛,这场比赛大威少又双叒叕拿下三双,而且是一个45+11+11的超级三双, ...
- 高性能 TCP/UDP/HTTP 通信框架 HP-Socket v4.1.1
HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服务端组件.客户端组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP/HTTP 通信系统,提供 C/ ...
- “风投云涌”:那些被资本看中的IT企业的风光与辛酸
进入七月份以来,纷享销客获得D轮融资1亿美元,撼动业界,资本与IT联姻令一部分创业者眼红的同时,没有人注意到背后的风险. 科技与资本的结合,是当今经济社会前行的宏大主题.相关统计显示,软件行 ...
- iOS开源项目周报1229
由OpenDigg 出品的iOS开源项目周报第三期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. Ma ...
- 初识git版本控制系统
当下git分布式版本控制系统越来越火,掌握git也是必须的一个技能.因此,对git做了如下学习. Git初级指南 1. 先安装git.(ps:在select cmponents处要勾选Git Bash ...
- Atitit.研发管理软件公司的软资产列表指南
Atitit.研发管理软件公司的软资产列表指南 1. Isv模型下的软资产1 2. 实现层面implet1 3. 规范spec层1 4. 法则定律等val层的总结2 1. Isv模型下的软资产 Sof ...
- 微软开放.NET框架源代码和Mono
微软一直在朝着更加开放的方向努力.例如,公司首席执行官萨特亚纳德拉(Satya Nadella)在Windows 10预览发布会上声称微软喜欢Linux,这并不出人意料,但是对于一家将Linux视作威 ...