大众点评上有很多美食餐馆的信息,正好可以拿来练练手Node.js。

1. API分析

大众点评开放了查询商家信息的API,这里给出了城市与cityid之间的对应关系,链接http://m.api.dianping.com/searchshop.json?&regionid=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的思路相类似):

  1. 逐步爬取searchshop API的取商家基本信息列表;
  2. 通过爬取的所有商家的id,异步并发爬取评分信息、经纬度;
  3. 最后将三份数据通过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?&regionid=%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大众点评爬虫相关推荐

  1. Node.js实现简易爬虫

    为什么选择利用node来写爬虫呢?就是因为cheerio这个库,全兼容jQuery语法,熟悉的话用起来真真是爽 依赖选择 cheerio: Node.js 版的jQuery http:封装了一个HTP ...

  2. Node.js实现网络新闻爬虫及搜索功能(一)

    Node.js实现网络新闻爬虫及搜索功能(一) Node.js实现网络新闻爬虫及搜索功能(一) 项目要求 一.爬虫部分 1. 引入相关包 2. 建立数据库连接 3. 爬取并解析网页首页 4. 爬取并解 ...

  3. Node.js实现网络新闻爬虫及搜索功能(四)

    Node.js实现网络新闻爬虫及搜索功能(四) Node.js实现网络新闻爬虫及搜索功能(四) 项目要求 三.搜索网站部分 1. 建立前端网页 2. 建立后端路由 Node.js实现网络新闻爬虫及搜索 ...

  4. Node.js实现网络爬虫

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.必要工具安装 二.爬取前操作 三.爬取新闻数据 四.建立个人网页展示爬取内容 总结 前言 网络爬虫是按照一定规则自 ...

  5. 【Python3爬虫】大众点评爬虫(搞定CSS反爬)

    本次爬虫的爬取目标是大众点评上的一些店铺的店铺名称.推荐菜和评分信息. 一.页面分析 进入大众点评,然后选择美食(http://www.dianping.com/wuhan/ch10),可以看到一页有 ...

  6. python爬取大众点评_【Python3爬虫】大众点评爬虫(破解CSS反爬)

    本次爬虫的爬取目标是大众点评上的一些店铺的店铺名称.推荐菜和评分信息. 一.页面分析 进入大众点评,然后选择美食(http://www.dianping.com/wuhan/ch10),可以看到一页有 ...

  7. Node.js实现简单爬虫 讲解

    一.什么是爬虫 网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定规则,自动的抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模 ...

  8. 一次失败的大众点评爬虫

    仅供学习,别搞其他事 前几天一觉醒来发现群里这么一条消息: 感觉这个还不简单?干就完了 打开网站,抓个登录的包(不会写模拟登录,就是菜),发现店名还是很好抓的 但是电话得点到具体店家链接才能看到 这思 ...

  9. 大众点评 爬虫抓取 数字文字解密

    分析网页内容 原网址:https://www.dianping.com/zhengzhou/ch0 大家在抓取网页的时候会遇到各种问题,比如字体加密,但是当我爬取大众点评网站的时候发现,它里面的字体以 ...

最新文章

  1. 嫦娥“挖土”归来有多难?看看中国首颗返回式卫星的故事
  2. 题目1185:特殊排序
  3. 系统工具源码设计页面
  4. 专题实验 字符集(全球化支持)
  5. mysql 5.6 my.cnf配置文件_mysql 5.6 my.cnf 配置
  6. .net程序员转战android第二篇---牛刀小试
  7. 音乐服务器 linux,Linux 下五个很酷的音乐播放器
  8. 阿里云智能语音交互服务-录音文件识别采样率不支持-UNSUPPORTED_SAMPLE_RATE 解决方案
  9. Cadence OrCAD Capture原理图检查之逐个元件Part检查的方法
  10. DxO PhotoLab 2.0完整汉化版|DxO PhotoLab 2.0中文版(WinX64)
  11. Luminati提供了哪些工具来帮助自动化操作?
  12. ZOJ--1005:Jugs(dfs)
  13. Linux下嵌入式开发环境配置
  14. 【程序源代码】小程序最佳开发实践-租房小程序
  15. 曾经中国最伟大的语文教材
  16. theano 训练样本制作(二)
  17. FFMPEG实现对AAC解码(采用封装格式实现)
  18. springboot中maven文件pom.xml的<resource>下的<includes>和<excludes>
  19. 2020上半年勒索病毒报告:勒索手段升级,不交赎金就公开数据
  20. 【MySQL数据库】--- 初识数据库以及MySQL数据库在Linux云服务器下载(详细教程)

热门文章

  1. 笔记本 ubuntu 10.04禁用触摸板
  2. 你用什么样的编程语言,决定你能拿多少钱
  3. 百度卫士,给您带来轻巧、快速、智能、纯净的防毒防护电脑的产品体验
  4. RxJava基础知识
  5. 2级基本编程问题(四)(C++)
  6. java 不兼容类型_JAVA不兼容的类型:无法将对象转换为我的类型
  7. 注意力机制 - 多头注意力
  8. PPT制作色块型目录页
  9. Excel如何批量将工作表复制到多个工作簿内
  10. nginx 限流学习