链家房价数据获取 & 可视化

1,864 阅读10分钟

如何了解一个城市的房价的区域分布,或者不同的城市房价的区域差异。如何获取一个城市不同板块的房价数据?本文讲述了如何爬取链家各城市板块的房价数据,以及如何如利用地理可视化引擎可视化这些数据。

数据获取


链家网站提供了地图找房功能,我们可以在地图上浏览任意区域的二手房价格信息,通过地图找房我们可以获取每个房源的价格信息和位置信息,同时链家也提供了区县维度,城市板块维度的房价统计信息。

image.png

我们还可以获取各个区县的,板块的范围数据。

image.png

绿色的边界即为板块的边界数据

链家房价 在准确性,权威性上都很好的数据源,而且有完整地理信息数据。链家房价数据在科学研究,房价区域趋势研究也有很大的作用。

爬取方法

如何爬取,链家肯定不会让你轻易将他的数据爬走,肯定做了一些反爬取机制,理论上讲只要网页可以看到的数据都是可以爬取的。那我们研究下如何爬取链接各板块的房价数据。

通过对浏览器的网络的监测我们找到地图数据请求接口

image.png

 
接口参数我们可以看出请求参数城市Id,和经纬度范围获取地图区域内的房价数据。

返回的的数据数据

image.png

正常来讲我只需要把一个城市划分成不同的网格就可以获取一个城市的所有的房价数据,但是事情往往没有那么简单。

我们可以数据有一个authorization 参数,这是参数就是链家的反爬机制。仔细研究发现这是对请求参数做了MD5编码,后端会进行校验只有校验正确才会返回数据。

下面的问题就是找到参数md5编码的方法。

代码跟踪测试我们找到了 参数MD5化的方法

const md5 = function() {
  function e(e, t) {
      var n = (65535 & e) + (65535 & t);
      return (e >> 16) + (t >> 16) + (n >> 16) << 16 | 65535 & n
  }
  function t(e, t) {
      return e << t | e >>> 32 - t
  }
  function n(n, i, a, r, o, s) {
      return e(t(e(e(i, n), e(r, s)), o), a)
  }
  function i(e, t, i, a, r, o, s) {
      return n(t & i | ~t & a, e, t, r, o, s)
  }
  function a(e, t, i, a, r, o, s) {
      return n(t & a | i & ~a, e, t, r, o, s)
  }
  function r(e, t, i, a, r, o, s) {
      return n(t ^ i ^ a, e, t, r, o, s)
  }
  function o(e, t, i, a, r, o, s) {
      return n(i ^ (t | ~a), e, t, r, o, s)
  }
  function s(t, n) {
      t[n >> 5] |= 128 << n % 32,
      t[14 + (n + 64 >>> 9 << 4)] = n;
      var s, l, c, d, u, g = 1732584193, f = -271733879, m = -1732584194, p = 271733878;
      for (s = 0; s < t.length; s += 16)
          l = g,
          c = f,
          d = m,
          u = p,
          g = i(g, f, m, p, t[s], 7, -680876936),
          p = i(p, g, f, m, t[s + 1], 12, -389564586),
          m = i(m, p, g, f, t[s + 2], 17, 606105819),
          f = i(f, m, p, g, t[s + 3], 22, -1044525330),
          g = i(g, f, m, p, t[s + 4], 7, -176418897),
          p = i(p, g, f, m, t[s + 5], 12, 1200080426),
          m = i(m, p, g, f, t[s + 6], 17, -1473231341),
          f = i(f, m, p, g, t[s + 7], 22, -45705983),
          g = i(g, f, m, p, t[s + 8], 7, 1770035416),
          p = i(p, g, f, m, t[s + 9], 12, -1958414417),
          m = i(m, p, g, f, t[s + 10], 17, -42063),
          f = i(f, m, p, g, t[s + 11], 22, -1990404162),
          g = i(g, f, m, p, t[s + 12], 7, 1804603682),
          p = i(p, g, f, m, t[s + 13], 12, -40341101),
          m = i(m, p, g, f, t[s + 14], 17, -1502002290),
          f = i(f, m, p, g, t[s + 15], 22, 1236535329),
          g = a(g, f, m, p, t[s + 1], 5, -165796510),
          p = a(p, g, f, m, t[s + 6], 9, -1069501632),
          m = a(m, p, g, f, t[s + 11], 14, 643717713),
          f = a(f, m, p, g, t[s], 20, -373897302),
          g = a(g, f, m, p, t[s + 5], 5, -701558691),
          p = a(p, g, f, m, t[s + 10], 9, 38016083),
          m = a(m, p, g, f, t[s + 15], 14, -660478335),
          f = a(f, m, p, g, t[s + 4], 20, -405537848),
          g = a(g, f, m, p, t[s + 9], 5, 568446438),
          p = a(p, g, f, m, t[s + 14], 9, -1019803690),
          m = a(m, p, g, f, t[s + 3], 14, -187363961),
          f = a(f, m, p, g, t[s + 8], 20, 1163531501),
          g = a(g, f, m, p, t[s + 13], 5, -1444681467),
          p = a(p, g, f, m, t[s + 2], 9, -51403784),
          m = a(m, p, g, f, t[s + 7], 14, 1735328473),
          f = a(f, m, p, g, t[s + 12], 20, -1926607734),
          g = r(g, f, m, p, t[s + 5], 4, -378558),
          p = r(p, g, f, m, t[s + 8], 11, -2022574463),
          m = r(m, p, g, f, t[s + 11], 16, 1839030562),
          f = r(f, m, p, g, t[s + 14], 23, -35309556),
          g = r(g, f, m, p, t[s + 1], 4, -1530992060),
          p = r(p, g, f, m, t[s + 4], 11, 1272893353),
          m = r(m, p, g, f, t[s + 7], 16, -155497632),
          f = r(f, m, p, g, t[s + 10], 23, -1094730640),
          g = r(g, f, m, p, t[s + 13], 4, 681279174),
          p = r(p, g, f, m, t[s], 11, -358537222),
          m = r(m, p, g, f, t[s + 3], 16, -722521979),
          f = r(f, m, p, g, t[s + 6], 23, 76029189),
          g = r(g, f, m, p, t[s + 9], 4, -640364487),
          p = r(p, g, f, m, t[s + 12], 11, -421815835),
          m = r(m, p, g, f, t[s + 15], 16, 530742520),
          f = r(f, m, p, g, t[s + 2], 23, -995338651),
          g = o(g, f, m, p, t[s], 6, -198630844),
          p = o(p, g, f, m, t[s + 7], 10, 1126891415),
          m = o(m, p, g, f, t[s + 14], 15, -1416354905),
          f = o(f, m, p, g, t[s + 5], 21, -57434055),
          g = o(g, f, m, p, t[s + 12], 6, 1700485571),
          p = o(p, g, f, m, t[s + 3], 10, -1894986606),
          m = o(m, p, g, f, t[s + 10], 15, -1051523),
          f = o(f, m, p, g, t[s + 1], 21, -2054922799),
          g = o(g, f, m, p, t[s + 8], 6, 1873313359),
          p = o(p, g, f, m, t[s + 15], 10, -30611744),
          m = o(m, p, g, f, t[s + 6], 15, -1560198380),
          f = o(f, m, p, g, t[s + 13], 21, 1309151649),
          g = o(g, f, m, p, t[s + 4], 6, -145523070),
          p = o(p, g, f, m, t[s + 11], 10, -1120210379),
          m = o(m, p, g, f, t[s + 2], 15, 718787259),
          f = o(f, m, p, g, t[s + 9], 21, -343485551),
          g = e(g, l),
          f = e(f, c),
          m = e(m, d),
          p = e(p, u);
      return [g, f, m, p]
  }
  function l(e) {
      var t, n = "";
      for (t = 0; t < 32 * e.length; t += 8)
          n += String.fromCharCode(e[t >> 5] >>> t % 32 & 255);
      return n
  }
  function c(e) {
      var t, n = [];
      for (n[(e.length >> 2) - 1] = void 0,
      t = 0; t < n.length; t += 1)
          n[t] = 0;
      for (t = 0; t < 8 * e.length; t += 8)
          n[t >> 5] |= (255 & e.charCodeAt(t / 8)) << t % 32;
      return n
  }
  function d(e) {
      return l(s(c(e), 8 * e.length))
  }
  function u(e, t) {
      var n, i, a = c(e), r = [], o = [];
      for (r[15] = o[15] = void 0,
      a.length > 16 && (a = s(a, 8 * e.length)),
      n = 0; n < 16; n += 1)
          r[n] = 909522486 ^ a[n],
          o[n] = 1549556828 ^ a[n];
      return i = s(r.concat(c(t)), 512 + 8 * t.length),
      l(s(o.concat(i), 640))
  }
  function g(e) {
      var t, n, i = "0123456789abcdef", a = "";
      for (n = 0; n < e.length; n += 1)
          t = e.charCodeAt(n),
          a += i.charAt(t >>> 4 & 15) + i.charAt(15 & t);
      return a
  }
  function f(e) {
      return unescape(encodeURIComponent(e))
  }
  function m(e) {
      return d(f(e))
  }
  function p(e) {
      return g(m(e))
  }
  function _(e, t) {
      return u(f(e), f(t))
  }
  function h(e, t) {
      return g(_(e, t))
  }
  function v(e, t, n) {
      return t ? n ? _(t, e) : h(t, e) : n ? m(e) : p(e)
  }
  return v
}();

function getMd5(e) {
  var t = []
    , i = "";
  for (var a in e)
      t.push(a);
  t.sort();
  for (var a = 0; a < t.length; a++) {
      var r = t[a];
      "filters" !== r && (i += r + "=" + e[r])
  }
  return i ? (
  md5("vfkpbin1ix2rb88gfjebs0f60cbvhedl" + i)) : ""
}

到这里我们数据获取的机制就已经搞定了

这里我们有nodejs作为数据爬取语言,nodejs优势前端代码可以直接在本地跑,一些数据加解密的方法可以直接拿来用。

示例代码

function getErshoufang(extent,city_id, cb) {
  const params = {
    city_id: city_id,
    filters: "{}",
    group_type: "bizcircle",
    max_lat: extent[3].toFixed(5) * 1,
    max_lng: extent[2].toFixed(5) * 1,
    min_lat: extent[1].toFixed(5) * 1,
    min_lng: extent[0].toFixed(5) * 1,
    request_ts: (new Date).getTime(),
    sug_id: "",
    sug_type: "", 
  }
  urllib.request('https://ajax.lianjia.com/map/search/ershoufang/?callback=parserData', {
    method: 'GET',
    data:{
      ...params,
      source:"ljpc",
      authorization:getMd5(params)
    },
    headers: {
      'Referer': 'https://hz.lianjia.com/ditu/',
      'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
    }
  }, function (err, data, res) {
  
    const json =  data ? eval(data.toString()) : {}
    cb(json);
  });
  
}

就一这样的方式抓取了全国30个城市的房价数据,板块级别的房价统计数据。

image.png

数据可视化

数据可视化我们采用AntV 地理可视化引擎L7

官网地址: AntV L7
GItHub 源码: L7 源码
110000.json

接下来我们介绍如何用L7 做出下图可视化

image.png

颜色映射绘制面图层

 scene.PolygonLayer()
   .source(city)
   .color('unit_price', ['#b2182b', '#ef8a62', '#fddbc7', '#d1e5f0', '#67a9cf', '#2166ac'].reverse())
   .shape('fill')
   .active(true)
   .style({
       opacity: 1
    }).render();

绘制标注
**

scene.PointLayer({
        zIndex: 5
      }).source(labeldata, {
        parser: {
          type: 'json',
          x: 'longitude',
          y: 'latitude'
        }
      }).shape('name', 'text').size(15).color('#fff').style({
        // fontFamily: 'Monaco, monospace', // 字体
        fontWeight: 200,
        textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
        textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
        spacing: 2, // 字符间距
        padding: [4, 4], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
        strokeColor: 'white', // 描边颜色
        strokeWidth: 2, // 描边宽度
        strokeOpacity: 1.0
      }).render();

这样我们就完成房价数据的可视化。 demo源码

借助L7 我们也可以实现3D的效果展示

 scene.PolygonLayer()
        .source(city)
        .color('unit_price', ['#b2182b', '#ef8a62', '#fddbc7', '#d1e5f0', '#67a9cf', '#2166ac'].reverse())
        .shape('extrude')
      	.size('unit_price', [1000,1000000])
        .active(true).style({
        opacity: 1
      }).render();

image.png

板块中心点3D圆柱表达
**

scene.PointLayer({
        zIndex: 5
      }).source(labeldata, {
        parser: {
          type: 'json',
          x: 'longitude',
          y: 'latitude'
        }
        }).shape('cylinder')
        .color('unit_price', ['#b2182b', '#ef8a62', '#fddbc7', '#d1e5f0', '#67a9cf', '#2166ac'].reverse())
        .size('unit_price', function(level) {
          return [4, 4, level / 1000];
          })
  
        .render();


image.png


**L7 蚂蚁金服 AntV 地理数据域方向的可视化产品。 L7 基于WebGL的地理数据可视化引擎,目前代码已源 ,新功能特性可以关注 github  star。
官网地址: AntV L7
GItHub 源码: L7 源码