node.js爬虫杭州房产销售及数据可视化
Posted 叶祖辉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 + \'\\n\'); // } // }) }); //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添加到地图上,
代码如下:
结束语:
这只是个初步的版本,很简单的展示每天都的销售情况,所有的代码都托管在了GITHUB上,项目地址为:https://github.com/react-map/HangzhouRealEstate,各路小伙伴如果有新的思路,新的想法,可以直接在Issues上提出来,一起做一个房产销售数据可视化的平台。
以上是关于node.js爬虫杭州房产销售及数据可视化的主要内容,如果未能解决你的问题,请参考以下文章