• node.js爬虫杭州房产销售及数据可视化


         现在年轻人到25岁+,总的要考虑买房结婚的问题,2016年的一波房价大涨,小伙伴们纷纷表示再也买不起上海的房产了,博主也得考虑考虑未来的发展了,思考了很久,决定去杭州工作、买房、定居、生活,之前去过很多次杭州,很喜欢这个城市,于是例行每天晚上都要花一点时间关注杭州的房产销售情况,以及价格,起初我每天都在杭州的本地论坛,透明售房网上查看,每一天的房产销售数据,但是无奈博主不是杭州本地人,看了网页上展示的很多楼盘,但是我不知道都在什么地方啊,于是乎,看到价格合适的,总是到高德地图去搜索地理位置,每次非常麻烦,于是我想是不是可以,写一个小的爬虫工具,每天抓取透明售房网上的销售记录,直接展示在地图上,直观明了的看看都是哪些地方的楼盘地理位置不错,同时价格也在能接受的范围内,同时最近在学习node.js,正好可以练练手。说干就干,一个下午时间,有了初步的成果如下,后期在加入每天的销售数据,加入到mongoDB中,用于分析每周、每月的销售数据,用于自己买房的参考,要学以致用嘛!

                                                                    

            先说下基本思路:

            第一步:利用nodejs,技术抓取透明售房网的实时的数据(http://www.tmsf.com/daily.htm),存储在后台;

            第二步:页面请求后台数据,然后借助高德地图提供的按照名称查询地理位置的服务,展示在地图上,并绑定每个楼盘的销售详情;

           ok,有了基本思路,下面一步一步的开干:

            一:后台爬虫

              1.抓取在线网络数据

              这里先介绍一个利器,cheerio(https://github.com/cheeriojs/cheerio),可以说是位服务器特别定制的,快速,灵活,实施的jQuery核心实现,或者说是后台解析html的;安装nodejs 模块这里不再说明,抓取html页面逻辑比较简单,直接上代码:                     

    复制代码
     1 //定义爬虫数据源网络地址
     2 var url = 'http://www.tmsf.com/daily.htm';
     3 
     4 /**
     5  * 请求网络地址抓取数据
     6  * @param {function} callBack 传回爬虫数据处理之后的最终结果
     7  */
     8 function getHzfcSaleInfo(callBack) {
     9     var hzfcSaleInfo = [];
    10     http.get(url, function(res) {
    11         var html = '';
    12         res.on('data', function(data) {
    13             html += data;
    14         });
    15         res.on('end', function() {
    16             hzfcSaleInfo = filterData(html);
    17             callBack(hzfcSaleInfo);
    18         });
    19         res.on('error', function() {
    20             console.log('获取数据出错');
    21         });
    22     })
    23 }
    复制代码

               2.解析获取的数据

                已经抓取整个网页的数据,在这一步中要根据网页的DOM,结构来分析应该怎么解析:首先我们可以看到,每日房产销售情况的数据是分行政区展示在并列的几个div中,通过display控制显示哪一个行政区,所以思路就是首先获取这个外层container,然后不停一层一层的循环解析数据;

               

              其中解析到每一行的数据的时候,发现了一个有点奇葩的网页展示,每一行后面数字竟然不是直接用数字来表示的,而是用css的图片来代替,可能就是为了防止我这种爬虫的吧,不过不管了,有了css,还不能转成数字吗,哈哈

             

               具体代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    /**
     * 解析DOM节点,提取核心数据
     * @param {string} html 页面整体html
     * @returns {array} 最终处理之后的数据
     */
    function filterData(html) {
        var $ = cheerio.load(html);
        var data = [];
        var container = $('#myCont2')
        var districts = container.find('table');
        districts.each(function() {
            var district = $(this);
            var trs = district.find('tr');
            trs.each(function() {
                var tr = $(this);
                var tds = tr.find('td');
                var i = 0;
                var estateName;
                var estateSite;
                var estateSign;
                var estateReserve;
                var estateArea;
                var estatePrice;
                tds.each(function() {
                    var col = $(this);
                    if (i == 0) {
                        estateName = col.find('a').text();
                    else if (i == 1) {
                        estateSite = col.text().replace(/[^u4e00-u9fa5]/gi, "");
                    else if (i == 2) {
                        var spanClass = '';
                        var spans = col.find('span');
                        spans.each(function(a) {
                            var span = $(this);
                            var cssName = classNameToNumb(span.attr('class'));
                            spanClass = spanClass + cssName;
                        });
                        estateSign = spanClass;
                    else if (i == 3) {
                        var spanClass = '';
                        var spans = col.find('span');
                        spans.each(function(a) {
                            var span = $(this);
                            var cssName = classNameToNumb(span.attr('class'));
                            spanClass = spanClass + cssName;
                        });
                        estateReserve = spanClass;
                    else if (i == 4) {
                        var spanClass = '';
                        var spans = col.find('span');
                        spans.each(function(a) {
                            var span = $(this);
                            var cssName = classNameToNumb(span.attr('class'));
                            spanClass = spanClass + cssName;
                        });
                        estateArea = spanClass + '㎡';
                    else if (i == 5) {
                        var spanClass = '';
                        var spans = col.find('span');
                        spans.each(function(a) {
                            var span = $(this);
                            var cssName = classNameToNumb(span.attr('class'));
                            spanClass = spanClass + cssName;
                        });
                        estatePrice = spanClass + '元/㎡';
                    }
                    i++;
                })
                var estateData = {
                    estateName: estateName,
                    estateSite: estateSite,
                    estateSign: estateSign,
                    estateReserve: estateReserve,
                    estateArea: estateArea,
                    estatePrice: estatePrice
                }
                if (estateData.estateName) {
                    data.push(estateData);
                }
            })
        })
        return data;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
     * 根据class name 提取数值
     * @param {string} className 节点class name
     * @returns 数值
     */
    function classNameToNumb(className) {
        var numb;
        if (className == 'numbzero') {
            numb = '0';
        else if (className == 'numbone') {
            numb = '1';
        else if (className == 'numbtwo') {
            numb = '2';
        else if (className == 'numbthree') {
            numb = '3';
        else if (className == 'numbfour') {
            numb = '4';
        else if (className == 'numbfive') {
            numb = '5';
        else if (className == 'numbsix') {
            numb = '6';
        else if (className == 'numbseven') {
            numb = '7';
        else if (className == 'numbeight') {
            numb = '8';
        else if (className == 'numbnine') {
            numb = '9';
        else if (className == 'numbdor') {
            numb = '.';
        }
        return numb;
    }

      

    数据抓取的最终结果,先做个简单的展示:

                                                                                                              

      

    二:页面展示

          1.搭建基本的web server,为了方便使用的是express(http://www.expressjs.com.cn/)框架,直接上代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    var express = require('express');
    var getHzfcSaleInfo = require('./hzfc');
    var app = express();
    app.use(express.static('public'));
    //处理前台页面的数据请求
    app.get('/getHzfcSaleInfo'function(req, res) {
        /**
         * 处理前台页面ajax请求
         * 返回给前台全部的处理数据
         * @param {any} data
         */
        var hzfcSaleInfo = getHzfcSaleInfo(function(data) {
            res.end(JSON.stringify({ data: data }));
            // data.forEach(function(item) {
            //     if (item.estateName) {
            //         console.log(item.estateName + ' ' + item.estateSite + ' ' + item.estateSign + ' ' + item.estateReserve + ' ' + item.estateArea + ' ' + item.estatePrice + ' ');
            //     }
            // })
        });
        //res.end(hzfcSaleInfo);
    });
    /**
     * 启动web server
     */
    var server = app.listen(8081, function() {
        console.log('web server start success''访问地址为:http://localhost:8081/index.html');
    })

         其中app.get方法用来处理前台页面的请求

         2.前台页面展示:

         首先利用高德地图API(http://lbs.amap.com/api/javascript-api/summary/),在网页中展示黑色的地图底图,然后页面发送请求给后台请求数据,然后利用高德api的由名称查询地理位置的方法,递归请求每个楼盘的地理位置,然后用marker添加到地图上,

         代码如下:

     View Code

        结束语:

       这只是个初步的版本,很简单的展示每天都的销售情况,所有的代码都托管在了GITHUB上,项目地址为:https://github.com/react-map/HangzhouRealEstate,各路小伙伴如果有新的思路,新的想法,可以直接在Issues上提出来,一起做一个房产销售数据可视化的平台。

  • 相关阅读:
    标准差,绝对中位差
    批处理计时
    四元数压缩
    float类型的存储方式
    通俗易懂理解——浮点与定点的计算机表示及互转
    max MultiRes修改器
    Topogun拓补工具
    3dmax高模到低模烘法线
    在线曲线绘制
    景深
  • 原文地址:https://www.cnblogs.com/zhaodahai/p/6825063.html
Copyright © 2020-2023  润新知