nodeJs小练习之爬取北京昌平区房价
之前几年总觉得北京房价再高也和我没有什么关系,总认为租房子也不错。其实还是自己太年轻,无家无室的,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:'<h1 style="color:red">html文本h1</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爬数据的功能大体就这些,肯定有很多不足和不对的地方,欢迎提意见。。。