利用爬虫生成二手房热力图
Keep Team Lv4

  曾经在《搭建在线Selenium执行环境》中树了一个Flag。

可以通过将脚本上传至服务器上,让服务器自动挂机执行。一夜爬遍某乎也不是梦了(还不用担心妈妈让我关电脑了)

  唉,Flag立得倒是挺轻松的,先看看我的小云已经有了那些功能吧,当前前端已能在线编辑并运行Python脚本,后端可通过Docker执行Selenium爬虫脚本,似乎万事具备,只欠整合了。那今天就来填坑吧,看看怎么让服务器(云)挂机执行爬虫吧。

定目标

  万事开头都得定目标,那我也定个小目标吧,刚好最近领导想买房子,但是害怕以后增值空间不大,毕竟现在伪口岸也挺多的。那就写个爬虫爬爬某家的二手房数据吧,用数据来看看到底那些区域最红火,那些区域有价无市。

写脚本

  爬虫脚本编写很简单,这里就不展开了,简单来说就是打开、搜索、获取、点击、再搜索、再获取,并重复以上的操作,直到脚本的完成。

  在编写脚本的时候,发现采集下来的数据量很大,而且频繁爬取某家的数据,担心我的小云被关进小黑屋 以后再也不能偷爬某家的数据了,因此需要将数据缓存起来,可以选择Redis也可以选择MySQL,而我选择了MariaDB(MySQL的分支版本)。这样完成的脚本大概就像下面这样子(仅主函数代码):

driver = initDriver()
helper = SQLHelper()
helper.clearData()
try:
    for page in range(1,101): driver.get("https://{}.lianjia.com/ershoufang/pg{}".format(city, page))
        cards = driver.find_elements_by_xpath('//div[@class="info clear"]')
        for card in cards:
            house = card.find_element_by_xpath('.//div[@class="flood"]/div[@class="positionInfo"]/a[1]').get_attribute('textContent')
            region = card.find_element_by_xpath('.//div[@class="flood"]/div[@class="positionInfo"]/a[2]').get_attribute('textContent')
            info = card.find_element_by_xpath('.//div[@class="address"]/div[@class="houseInfo"]').get_attribute('textContent').split("|")
            price = card.find_element_by_xpath('.//div[@class="priceInfo"]/div[@class="totalPrice"]/span').get_attribute('textContent')
            helper.insertNewHouse(region, house, info[1].strip().rstrip("平米"),info[4].strip(), price)
        helper.autoUpdateHouseInfo()
finally:
    helper.close()
    close(driver)

  如代码所述,采集后会将关键信息保存进数据库。脚本有了,那就该让脚本能自动运行起来了。

定时器Cron

  要自动唤起脚本,可以用Shell定时脚本如:

sleep 3h;python yourscrip.py

  在其基础上加上while...do....done就可以实现循环定时执行了。可是这个方法有个问题,就是睡眠的时间无法固定。

  举个例子,我希望每1小时执行一次脚本,所以用了sleep 1h,可是脚本执行是需要时间的,也就是说第二次执行的时间相较于第一次执行的时间并不是只过去了1个小时,而是过去了1小时加上脚本执行的耗时,而这个时差会累积的,循环次数越多,则误差越大。

  再者,我实际希望脚本能在凌晨3点执行,这样对服务器负载会轻一点。那么应该sleep多少呢?是不是一脸懵逼?综合以上两个原因,用sleep并不适合当前的场景。

  Cron是Linux的定时任务执行服务,它会根据配置定时启动任务执行,类似Windows的定时任务。详细介绍可参考菜鸟教程或自行百度。但是cron有一个巨大的坑,差不多有下图这么大,后面再单独写一个关于cron填坑实录吧😂。

天坑

  配置好cron,安置好准备好的脚本,就可以坐等小目标实现了。不过,好像差点什么。唉?唉?是不是还差个前端显示啊,不然爬完的数据还是数据啊,居然忘了这个这么重要的事情。

热力图

  前端怎么显示数据呢,柱状图?曲线?还是表格?哈哈哈,都不是,本人选择了最简单的热力图。

叉腰

  热力图选择高德地图来实现的,并复用了《搭建在线Selenium执行环境》中已实现的前后端数据通道。利用BootStrap4做了一个简单的前端界面,最终实现的效果还不错。

热力图

  高德热力图有一个小坑,他官网上能搜到多个版本的热力图数据方式,但其实只有一种能成功。传入的数据一定要是数组结构,且每个点需要是哈希结构,js大致如下:

var heatlayer = null;

function init(){
    heatlayer = initMap();
}

function loaddata(type){
    let wss = new WebSocket('wss地址');
    wss.addEventListener('close', onCloseWss);
    wss.addEventListener('message', onMessageWss);
    wss.onopen = function(){
        wss.send('发起数据请求');
    };
}

function onCloseWss(event){
}

function onMessageWss(event){
    let hdata = JSON.parse(event.data);
    heatlayer.setDataSet({data: hdata});
    heatlayer.show();
}

function initMap(){
    let map = new AMap.Map('map', {
        resizeEnable: true,
        center: [104.066593,30.657789],
        zoom: 11
    });
    map.setMapStyle('amap://styles/d04f6e9640ff8cc28c800c925a2dd146');
    if (!isSupportCanvas()) {
        alert('The heat map is applicable only to browsers that support canvas. The browser you are using cannot use the heat map function. Please try another browser.');
    }
    let heatlayer;
    map.plugin(["AMap.Heatmap"], function () {
        heatlayer = new AMap.Heatmap(map, {
            radius: 25,
            opacity: [0, 0.8]
        });
    });
    return heatlayer;
}

function isSupportCanvas() {
    let elem = document.createElement('canvas');
    return !!(elem.getContext && elem.getContext('2d'));
}

  前端点击按钮的时候,会触发loaddata函数,进一步通过websocket去后台获取JSON格式的数据,并转换为数组传递给Map。

结束

  最后从热力图上大致看出,果然天府新区在我大成都热得不是一点半点呢(当然滑动地铁,还能发现其他更有意思的事儿呢)。得赶紧向领导汇报下成果了,希望今年云服务器的钱可以报销了。