zoukankan      html  css  js  c++  java
  • 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',50},
    {caption:'面积',type:'string',15},
    {caption:'总价',type:'number',15},
    {caption:'类别',type:'string',15},
    {caption:'单价',type:'string',15},
    {caption:'楼层',type:'string',15},
    {caption:'年份',type:'string',15},
    {caption:'位置',type:'string',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',50},
    {caption:'面积',type:'string',15},
    {caption:'总价',type:'number',15},
    {caption:'类别',type:'string',15},
    {caption:'单价',type:'string',15},
    {caption:'楼层',type:'string',15},
    {caption:'年份',type:'string',15},
    {caption:'位置',type:'string',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爬数据的功能大体就这些,肯定有很多不足和不对的地方,欢迎提意见。。。






  • 相关阅读:
    c# 进制
    java生成验证码
    java基础练习题
    java九九乘法表
    java list集合练习
    深入理解Java的接口和抽象类
    java 接口 练习
    java泛型详解
    Java 继承 小练习
    Java单例模式深入详解
  • 原文地址:https://www.cnblogs.com/heaventear/p/6013322.html
Copyright © 2011-2022 走看看