之前几年总觉得北京房价再高也和我没有什么关系,总认为租房子也不错。其实还是自己太年轻,无家无室的,too young too simple。今年交了女朋友,人很好,我非常喜欢她。但是逐渐的我发现,我喜欢她并不够,至少在北京给她个稳定的住所都给不了。之前从来没有关注过北京的房价,这阵子在链家,安居客等网站上简单了解了下,比我想象中的还贵。虽然这房价太离谱,简直就是个笑话,现代人努力一辈子仅仅为了一个能遮风避雨普普通通的住宅,但既然游戏规则就如此,只能要么忍,要么滚。最终我决定忍,剩下的只能自己加倍努力,加倍付出,来为我们亲爱的祖国添砖加瓦,为实现现代化而奋斗终生,呵呵。。。
回归正题,最近在学习 nodeJs,想正好用学到的东西爬下北京昌平区的房价,然后导出到 excel 中来有个比较可视化的数据。
数据来源于安居客网站,准确性我就无从知道了,仅作参考。
首先,运行此程序需要 nodeJs 开发环境,用 npm 安装所需的依赖包,package.json 文件如下:
- {
- "name": "houseprice",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {
- "cheerio": "^0.22.0",
- "excel-export": "^0.5.1",
- "node-schedule": "^1.2.0",
- "nodemailer": "^2.6.4",
- "request": "^2.75.0"
- }
- }
安装好相关依赖后,新建入口文件 index.js
首先定义变量,引入相关文件:
- /**
- * 北京昌平区实时二手房房价信息汇总
- */
- var http = require('http') var fs = require('fs') var cheerio = require('cheerio') var request = require('request') var excelPort = require('excel-export');
- var nodemailer = require('nodemailer') var schedule = require('node-schedule')
- var fileName = '北京昌平区实时房价列表-' + new Date().toLocaleString().split(' ')[0];
- var conf = {};
- var rows = [];
- var marginPrice = 320;
- 其中,cheerio模块用来将爬到的数据筛选出来,相当于node端的jQuery;excelPort模块用来将数据导出到excel中,网上随便找的一个,轻便,但是功能有限;nodemailer模块用来发邮件,我想程序跑完后主动给我发封邮件通知自己;schedule模块是定时器功能,我想要每天定时跑一次任务,然后通过邮件通知我。安居客网站数据是分页,大概60页左右,每页50条数据,总数据大约3000条左右。获取每页的url的函数如下:
- /**
- * 获取指定页面的url地址
- * @param index
- * @returns {string}
- */
- function getPageUrl(index) {
- return 'http://beijing.anjuke.com/sale/changping/p' + index + '/#filtersort';
- }
接下来是主要功能函数,从第一页开始通过递归的方式循环调用,直到最后一页。爬完数据之后导出到 excel,然后发邮件。代码如下:
- /**
- * 爬去内容并导出excel和发邮件通知
- * @param url
- * @param curPage
- * @param marginPrice
- */
- function fetch(url, curPage) {
- if (!marginPrice) {
- marginPrice = 9999;
- }
- http.get(url,
- function(res) {
- var html = '';
- res.setEncoding('utf-8');
- res.on('data',
- function(chunk) {
- html += chunk;
- }) res.on('end',
- function() {
- var $ = cheerio.load(html);
- var list = $('#houselist-mod .list-item');
- list.each(function(i, item) {
- item = $(item);
- var name = item.find('.houseListTitle').text();
- var area = item.find('.details-item').eq(0).find('span').eq(0).text();
- var totalPrice = item.find('.pro-price .price-det strong').text() * 1;
- var category = item.find('.details-item').eq(0).find('span').eq(1).text();
- var price = item.find('.details-item').eq(0).find('span').eq(2).text();
- var floor = item.find('.details-item').eq(0).find('span').eq(3).text();
- var year = item.find('.details-item').eq(0).find('span').eq(4).text();
- var position = item.find('.details-item').eq(1).find('span').text();
- if (marginPrice >= totalPrice) {
- rows.push([name, area, totalPrice, category, price, floor, year, position])
- }
- });
- if (curPage == 1) {
- conf.cols = [{
- caption: '名称',
- type: 'string',
- width: 50
- },
- {
- caption: '面积',
- type: 'string',
- width: 15
- },
- {
- caption: '总价',
- type: 'number',
- width: 15
- },
- {
- caption: '类别',
- type: 'string',
- width: 15
- },
- {
- caption: '单价',
- type: 'string',
- width: 15
- },
- {
- caption: '楼层',
- type: 'string',
- width: 15
- },
- {
- caption: '年份',
- type: 'string',
- width: 15
- },
- {
- caption: '位置',
- type: 'string',
- width: 60
- }]
- }
- //下一页
- if ($('.aNxt').attr('href')) {
- var nextPage = curPage + 1;
- fetch(getPageUrl(nextPage), nextPage, marginPrice);
- } else {
- conf.rows = rows;
- var result = excelPort.execute(conf);
- var genFilePath = 'result/' + fileName + ".xlsx";
- fs.writeFile(genFilePath, result, 'binary',
- function(err) {
- if (err) {
- console.log(err)
- } else {
- console.log('done!');
- //发送邮件
- sendMail();
- }
- })
- }
- schedule模块功能挺多,支持cron表达式,能满足大部分需求,我这里仅仅为了测试功能是否好用,代码如下:
- /**
- * 执行定时任务
- */
- function execSchedule() {
- var date = new Date(2016, 9, 23, 23, 56, 1);
- schedule.scheduleJob(date,
- function() {
- fetch(getPageUrl(1), 1, marginPrice);
- })
- }
- //fetch(getPageUrl(1),1,marginPrice);
- //sendMail();
- execSchedule();
其中,marginPrice 变量是我用来过滤房价,比如说只导出总价小于 320 万的就行了。
完整代码如下:
- /**
- * 北京昌平区实时二手房房价信息汇总
- */
- var http = require('http') var fs = require('fs') var cheerio = require('cheerio') var request = require('request') var excelPort = require('excel-export');
- var nodemailer = require('nodemailer') var schedule = require('node-schedule')
- var fileName = '北京昌平区实时房价列表-' + new Date().toLocaleString().split(' ')[0];
- var conf = {};
- var rows = [];
- var marginPrice = 1;
- /**
- * 获取指定页面的url地址
- * @param index
- * @returns {string}
- */
- function getPageUrl(index) {
- return 'http://beijing.anjuke.com/sale/changping/p' + index + '/#filtersort';
- }
- /**
- * 爬去内容并导出excel和发邮件通知
- * @param url
- * @param curPage
- * @param marginPrice
- */
- function fetch(url, curPage) {
- if (!marginPrice) {
- marginPrice = 9999;
- }
- http.get(url,
- function(res) {
- var html = '';
- res.setEncoding('utf-8');
- res.on('data',
- function(chunk) {
- html += chunk;
- }) res.on('end',
- function() {
- var $ = cheerio.load(html);
- var list = $('#houselist-mod .list-item');
- list.each(function(i, item) {
- item = $(item);
- var name = item.find('.houseListTitle').text();
- var area = item.find('.details-item').eq(0).find('span').eq(0).text();
- var totalPrice = item.find('.pro-price .price-det strong').text() * 1;
- var category = item.find('.details-item').eq(0).find('span').eq(1).text();
- var price = item.find('.details-item').eq(0).find('span').eq(2).text();
- var floor = item.find('.details-item').eq(0).find('span').eq(3).text();
- var year = item.find('.details-item').eq(0).find('span').eq(4).text();
- var position = item.find('.details-item').eq(1).find('span').text();
- if (marginPrice >= totalPrice) {
- rows.push([name, area, totalPrice, category, price, floor, year, position])
- }
- });
- if (curPage == 1) {
- conf.cols = [{
- caption: '名称',
- type: 'string',
- width: 50
- },
- {
- caption: '面积',
- type: 'string',
- width: 15
- },
- {
- caption: '总价',
- type: 'number',
- width: 15
- },
- {
- caption: '类别',
- type: 'string',
- width: 15
- },
- {
- caption: '单价',
- type: 'string',
- width: 15
- },
- {
- caption: '楼层',
- type: 'string',
- width: 15
- },
- {
- caption: '年份',
- type: 'string',
- width: 15
- },
- {
- caption: '位置',
- type: 'string',
- width: 60
- }]
- }
- //下一页
- if ($('.aNxt').attr('href')) {
- var nextPage = curPage + 1;
- fetch(getPageUrl(nextPage), nextPage, marginPrice);
- } else {
- conf.rows = rows;
- var result = excelPort.execute(conf);
- var genFilePath = 'result/' + fileName + ".xlsx";
- fs.writeFile(genFilePath, result, 'binary',
- function(err) {
- if (err) {
- console.log(err)
- } else {
- console.log('done!');
- //发送邮件
- sendMail();
- }
- })
- }
- })
- })
- }
- /**
- * 发送邮件通知
- */
- function sendMail() {
- var transporter = nodemailer.createTransport('smtps://447818666%40qq.com:liqianghello@smtp.qq.com') var opts = {
- from: '447818666@qq.com',
- to: '447818666@qq.com',
- subject: 'nodeJs邮件系统测试',
- text: '纯文本',
- html: 'html文本h1'
- }
- transporter.sendMail(opts,
- function(err, info) {
- if (err) {
- console.log(err)
- } else {
- console.log(info.response);
- }
- })
- }
- /**
- * 执行定时任务
- */
- function execSchedule() {
- var date = new Date(2016, 9, 23, 23, 56, 1);
- schedule.scheduleJob(date,
- function() {
- fetch(getPageUrl(1), 1, marginPrice);
- })
- }
- //fetch(getPageUrl(1),1,marginPrice);
- //sendMail();
- execSchedule();
自己测试 nodeJs 爬数据的功能大体就这些,肯定有很多不足和不对的地方,欢迎提意见。。。
来源: http://www.cnblogs.com/heaventear/p/6013322.html