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来写爬虫呢?就是因为cheerio这个库,全兼容jQuery语法,熟悉的话用起来真真是爽 依赖选择 cheerio: Node.js 版的jQuery http:封装了一个HTP ...
- Node.js实现网络新闻爬虫及搜索功能(一)
Node.js实现网络新闻爬虫及搜索功能(一) Node.js实现网络新闻爬虫及搜索功能(一) 项目要求 一.爬虫部分 1. 引入相关包 2. 建立数据库连接 3. 爬取并解析网页首页 4. 爬取并解 ...
- Node.js实现网络新闻爬虫及搜索功能(四)
Node.js实现网络新闻爬虫及搜索功能(四) Node.js实现网络新闻爬虫及搜索功能(四) 项目要求 三.搜索网站部分 1. 建立前端网页 2. 建立后端路由 Node.js实现网络新闻爬虫及搜索 ...
- Node.js实现网络爬虫
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.必要工具安装 二.爬取前操作 三.爬取新闻数据 四.建立个人网页展示爬取内容 总结 前言 网络爬虫是按照一定规则自 ...
- 【Python3爬虫】大众点评爬虫(搞定CSS反爬)
本次爬虫的爬取目标是大众点评上的一些店铺的店铺名称.推荐菜和评分信息. 一.页面分析 进入大众点评,然后选择美食(http://www.dianping.com/wuhan/ch10),可以看到一页有 ...
- python爬取大众点评_【Python3爬虫】大众点评爬虫(破解CSS反爬)
本次爬虫的爬取目标是大众点评上的一些店铺的店铺名称.推荐菜和评分信息. 一.页面分析 进入大众点评,然后选择美食(http://www.dianping.com/wuhan/ch10),可以看到一页有 ...
- Node.js实现简单爬虫 讲解
一.什么是爬虫 网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定规则,自动的抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模 ...
- 一次失败的大众点评爬虫
仅供学习,别搞其他事 前几天一觉醒来发现群里这么一条消息: 感觉这个还不简单?干就完了 打开网站,抓个登录的包(不会写模拟登录,就是菜),发现店名还是很好抓的 但是电话得点到具体店家链接才能看到 这思 ...
- 大众点评 爬虫抓取 数字文字解密
分析网页内容 原网址:https://www.dianping.com/zhengzhou/ch0 大家在抓取网页的时候会遇到各种问题,比如字体加密,但是当我爬取大众点评网站的时候发现,它里面的字体以 ...
最新文章
- 嫦娥“挖土”归来有多难?看看中国首颗返回式卫星的故事
- 题目1185:特殊排序
- 系统工具源码设计页面
- 专题实验 字符集(全球化支持)
- mysql 5.6 my.cnf配置文件_mysql 5.6 my.cnf 配置
- .net程序员转战android第二篇---牛刀小试
- 音乐服务器 linux,Linux 下五个很酷的音乐播放器
- 阿里云智能语音交互服务-录音文件识别采样率不支持-UNSUPPORTED_SAMPLE_RATE 解决方案
- Cadence OrCAD Capture原理图检查之逐个元件Part检查的方法
- DxO PhotoLab 2.0完整汉化版|DxO PhotoLab 2.0中文版(WinX64)
- Luminati提供了哪些工具来帮助自动化操作?
- ZOJ--1005:Jugs(dfs)
- Linux下嵌入式开发环境配置
- 【程序源代码】小程序最佳开发实践-租房小程序
- 曾经中国最伟大的语文教材
- theano 训练样本制作(二)
- FFMPEG实现对AAC解码(采用封装格式实现)
- springboot中maven文件pom.xml的<resource>下的<includes>和<excludes>
- 2020上半年勒索病毒报告:勒索手段升级,不交赎金就公开数据
- 【MySQL数据库】--- 初识数据库以及MySQL数据库在Linux云服务器下载(详细教程)