大众点评上有很多美食餐馆的信息,正好可以拿来练练手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. 【AI竞赛】TinyMind汉字书法识别挑战赛开始报名啦!!
  2. 【转载】在服务器上用Fiddler抓取HTTPS流量
  3. 移动端页面去除广告注入,例如移动流量图标
  4. NV133FHM-N52屏在rk3288的Android7.1上的点亮
  5. Commit request failed Commit failed. Ref must be HEAD and is HEAD
  6. 【POJ - 1651】Multiplication Puzzle(区间dp)
  7. Joe博客模板Typecho主题
  8. Web应用运行在pywebview在窗口
  9. 说透Applet的数字签名之1——Applet及其运行
  10. winpython anaconda_Windows:安装 Anaconda《 Python:基础 》
  11. android-goldfish-3.4内核源码下载与编译
  12. vijos1697——平面几何
  13. FinalData 数据恢复工具[绿色版]
  14. 下载webex client的remover
  15. 天然产物数据库综述:2020年从哪里找天然产物数据
  16. ISLR读书笔记十六:最大边际分类器(maximal margin classifier)
  17. 基于Java实现的迷宫小游戏
  18. 华为云服务器最新信息,查询云主机信息
  19. 网易2017春招笔试——集合
  20. 神经网络反向传播算法原理笔记

热门文章

  1. 康传平Excel2018年10月份新个人所得税计算公式的两种方法
  2. 宜人贷第四季度净营收12.71亿元 同比下滑30%
  3. DouPHP如何在一个页面调用多个单页的内容
  4. 批量打印CAD文件怎么做?来试试这种方法
  5. U盘文件删除如何恢复?试试这2招,实测有效
  6. 冰河实验室受邀作为戴尔“人工智能实践课程赋能班”导师,分享《加密网络及其重要性》主题
  7. 《《《翻译》》》3d_navigation
  8. 56、数据库设计(铁路购票系统)
  9. 安科瑞基于物联网技术的智能电力抄表服务平台-Susie 周
  10. 什么视频播放器最好用?