利用Node.js制作爬取大众点评的爬虫

前言

Node.js天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C、Java不一样);for循环体({})内引用i的值实际上是循环结束之后的值,因而引起各种undefined的问题;嵌套函数时,内层函数的变量并不能及时传导到外层(因为是异步)等等。

一、 API分析

大众点评开放了查询餐馆信息的API,这里给出了城市与cityid之间的对应关系,

链接:http://m.api.dianping.com/searchshop.json?&regionid=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=0rangeNavs字段中有解释;

4、categoryid表示搜索商家的分类id,比如,美食对应的id为10,具体每一个id的含义参见在start=0categoryNavs字段;

5、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文件。

二、爬虫实现

Node.js爬虫代码用到如下的第三方模块:

1、superagent,轻量级http请求库,模仿了浏览器登录;

2、cheerio,采用jQuery语法解析HTML元素,跟Python的PyQuery相类似;

3、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); } }); }

在调用爬虫函数时,采用asyncmapLimit函数实现对并发的控制;采用asyncuntil对并发的协同处理,保证三份数据结果的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); } ); }); });

其中,parseShopparseMap分别为解析商家详情页、商家地图页:

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的朋友们带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • node+experss实现爬取电影天堂爬虫

    上周写了一个node+experss的爬虫小入门.今天继续来学习一下,写一个爬虫2.0版本. 这次我们不再爬博客园了,咋玩点新的,爬爬电影天堂.因为每个周末都会在电影天堂下载一部电影来看看. talk is cheap,show me the code! 抓取页面分析 我们的目标: 1.抓取电影天堂首页,获取左侧最新电影的169条链接 2.抓取169部新电影的迅雷下载链接,并且并发异步抓取. 具体分析如下: 1.我们不需要抓取迅雷的所有东西,只需要下载最新发布的电影即可,比如下面的左侧栏.一共有

  • node.js爬虫爬取拉勾网职位信息

    简介 用node.js写了一个简单的小爬虫,用来爬取拉勾网上的招聘信息,共爬取了北京.上海.广州.深圳.杭州.西安.成都7个城市的数据,分别以前端.PHP.java.c++.python.Android.ios作为关键词进行爬取,爬到的数据以json格式储存到本地,为了方便观察,我将数据整理了一下供大家参考 数据结果 上述数据为3月13日22时爬取的数据,可大致反映各个城市对不同语言的需求量. 爬取过程展示 控制并发进行爬取 爬取到的数据文件 json数据文件 爬虫程序 实现思路 请求拉钩网的

  • 利用node.js写一个爬取知乎妹纸图的小爬虫

    前言 说起写node爬虫的原因,真是羞羞呀.一天,和往常一样,晚上吃过饭便刷起知乎来,首页便是推荐的你见过最漂亮的女生长什么样?,点进去各种漂亮的妹纸爆照啊!!!,看的我好想把这些好看的妹纸照片都存下来啊!一张张点击保存,就在第18张得时候,突然想起.我特么不是程序员么,这种手动草做的事,怎么能做,不行我不能丢程序员的脸了,于是便开始这次爬虫之旅. 原理 初入爬虫的坑,没有太多深奥的理论知识,要获取知乎上帖子中的一张图片,我把它归结为以下几步. 准备一个url(当然是诸如你见过最漂亮的女生长什么

  • 利用node.js爬取指定排名网站的JS引用库详解

    前言 本文给大家介绍的爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库.下面话不多说了,来一起看看详细的介绍: 所用到的node主要模块 express 不用多说 request http模块 cheerio 运行在服务器端的jQuery node-inspector node调试模块 node-dev 修改文件后自动重启app 关于调试Node 在任意一个文件夹,执行node-inspector,通过打开特定页面,在页面上进行调试,然后运行app

  • 使用nodejs爬取前程无忧前端技能排行

    最近准备换工作,需要更新一下技能树.为做到有的放矢,想对招聘方的要求做个统计.正好之前了解过nodejs,所以做了个爬虫搜索数据. 具体步骤: 1.  先用fiddler分析请求需要的header和body. 2.  再用superagent构建上述数据发送客户端请求. 3.  最后对返回的数据使用cheerio整理. 折腾了几个晚上,只搞出了个架子,剩余工作等有时间再继续开发. /*使用fiddler抓包,需要配置lan代理,且设置如下参数*/ process.env.https_proxy

  • Node.js环境下编写爬虫爬取维基百科内容的实例分享

    基本思路 思路一(origin:master):从维基百科的某个分类(比如:航空母舰(key))页面开始,找出链接的title属性中包含key(航空母舰)的所有目标,加入到待抓取队列中.这样,抓一个页面的代码及其图片的同时,也获取这个网页上所有与key相关的其它网页的地址,采取一个类广度优先遍历的算法来完成此任务. 思路二(origin:cat):按分类进行抓取.注意到,维基百科上,分类都以Category:开头,由于维基百科有很好的文档结构,很容易从任一个分类,开始,一直把其下的所有分类全都抓

  • node爬取微博的数据的简单封装库nodeweibo使用指南

    一.前言 就在去年12月份,有个想法是使用node爬取微博的数据,于是简单的封装了一个nodeweibo这个库.时隔一年,没有怎么维护,中途也就将函数形式改成了配置文件.以前做的一些其他的项目也下线了,为了是更加专注前端 & node.js.偶尔看到下载量一天超过60多,持续不断的有人在用这个库,但是看下载量很少也就没有更新.但是昨天,有人pull request这个分支了,提出一些中肯的建议和有用的代码.于是就认真回顾了下nodeweibo,发布了v2.0.3这个版本. 二.什么是nodewe

  • 利用Node.js制作爬取大众点评的爬虫

    前言 Node.js天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C.Java不一样):for循环体({})内引用i的值实际上是循环结束之后的值,因而引起各种undefined的问题:嵌套函数时,内层函数的变量并不能及时传导到外层(因为是异步)等等. 一. API分析 大众点评开放了查询餐馆信息的API,这里给出了城市与cityid之间的对应关系, 链接:http://m.api.dianping.com/searchshop.json

  • Node.js实现爬取网站图片的示例代码

    目录 涉及知识点 cheerio简介 什么是cheerio ? 安装cheerio 准备工作 核心代码 示例截图 涉及知识点 开发一个小爬虫,涉及的知识点如下所示: https模块,主要是用户获取网络资源,如:网页源码,图片资源等. cheerio模块,主要用于解析html源码,并可访问,查找html节点内容. fs模块,主要用于文件的读写操作,如保存图片,日志等. 闭包,主要是对于异步操作,对象的隔离保护. cheerio简介 什么是cheerio ? cheerio是为服务器特别定制的,快速

  • 利用node.js制作命令行工具方法教程(一)

    前言 之前使用过一些全局安装的NPM包,安装完之后,可以通过其提供的命令,完成一些任务.比如Fis3,可以通过fis3 server start 开启fis的静态文件服务,通过fis3 release开启文件编译与发布:还有vue-cli,可以通过vue init webpack my-project来初始化vue+webpack的项目基础配置.最近有一个需求,需要写一个类似vue-cli的NPM包,通过命令行操作实现项目初始配置,所以就查看了相关资料,学习了一下如何使用node来生成自己的命令

  • 利用Node.js批量抓取高清妹子图片实例教程

    前言 写了一个抓取图片的小玩意,分享一下. Github地址:https://github.com/focalhot/node.js-crawler (本地下载) 示例代码 //依赖模块 var fs = require('fs'); var request = require("request"); var cheerio = require("cheerio"); var mkdirp = require('mkdirp'); //目标网址 var url =

  • 如何利用Node.js做简单的图片爬取

    目录 介绍 安装引入 创建实例 元素捕获 下载图片 结语 介绍 爬虫的主要目的是收集互联网上公开的一些特定数据.利用这些数据我们可以能进行分析一些趋势对比,或者训练模型做深度学习等等.本期我们就将介绍一个专门用于网络抓取的 node.js 包—— node-crawler ,并且我们将用它完成一个简单的爬虫案例来爬取网页上图片并下载到本地. node-crawler 是一个轻量级的 node.js 爬虫工具,兼顾了高效与便利性,支持分布式爬虫系统,支持硬编码,支持http前级代理.而且,它完全是

  • 基于node.js制作简单爬虫教程

    前言:最近想学习node.js,突然在网上看到基于node的爬虫制作教程,所以简单学习了一下,把这篇文章分享给同样初学node.js的朋友. 目标:爬取 http://tweixin.yueyishujia.com/webapp/build/html/ 网站的所有门店发型师的基本信息. 思路:访问上述网站,通过chrome浏览器的network对网页内容分析,找到获取各个门店发型师的接口,对参数及返回数据进行分析,遍历所有门店的所有发型师,直到遍历完毕,同事将信息存储到本地. 步骤一:安装nod

  • 利用Node.JS实现邮件发送功能

    第一步.配置篇 首先需要安装nodemailer库 npm install nodemailer//默认会安装最新的版本. 关于这个库的文档参见nodemailer 第二步.库的一些使用介绍 这个库使用方法很简单的.首先是要创建一个用于发送邮件的实例 var transporter = nodemailer.createTransport(transport[, defaults]) transport参数属性 属性太多了就只写一些关键的属性 port:连接的端口号,一般就是465 host:你

  • 利用node.js实现自动生成前端项目组件的方法详解

    本文主要给大家介绍了关于利用node.js实现自动生成前端项目组件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 脚本编写背景 写这个小脚本的初衷是,项目本身添加一个组件太繁琐了,比如我想要去建立一个login的组件,那么我需要手动去IDE中,创建index.js(组件出口文件),login.js(业务文件),login.html,login.less这四个文件.因为每个组件都有一些输出的代码,还要把之前组件的那几行拷贝过来,这种作业真的烦,于是乎写了一个小脚本去自动

  • 利用node.js实现反向代理的方法详解

    本文主要给大家介绍的是关于利用node.js实现反向代理的相关内容,分享出供大家参考学习,下面话不多说,来一起看看详细的介绍: 跨域问题是前端开发很常见的问题解决方案有很多种 jsonp返回 Access-Control-Allow-Origin:'*' (需要注意的是 对于post请求会变成option请求需求后端支持) 前端添加代理 前端添加代理 以vue-cli为例,前端添加代理 dev: { env: require('./dev.env'), port: 8888, autoOpenB

  • 利用node.js本地搭建HTTP服务器

    我们的目的比较简单,利用node.js在本地搭建HTTP服务器,实现hello word. 系统环境: win7 64bitIP:127.0.0.1Node.js:v6.10.2Npm:3.10.10Git:2.12.2.2-64-bit 基本安装: 1.Node.js安装包及源码下载地址为:https://nodejs.org/en/download/ Git下载地址为:https://git-scm.com/download/win. 2.按照安装提示操作即可,选择安装路径.npm. 3.环

随机推荐