手记

从零开始写一个node爬虫(下)—— 数据分析篇

  综述

  上一篇我们通过node + cheerio爬取了猎聘网上近4000条关于前端开发岗的招聘信息,在这一篇呢,我们对这些信息做一个数据分析与统计,一起看看吧。
  想要源代码的,这里有整个项目的Github入口:Github入口
  上一篇——数据采集篇的入口

  这些是我们爬取到的数据,可以看到数据中有薪酬,有学历要求,也有招聘的岗位是初中高级还是资深或专家,同时也有关于招聘公司,工作地点等等,我们就从这些不同的维度对这3960条数据进行分析。

  第一,先来看看岗位工作地点的地区分布。

//  地区分布分析
    areaAnalysis () {
        let area = this.data.map(item => item.area),
            _self = this;
        
        area.forEach(item => {

            let flag = true;
            for(let i in _self.areaMap) {
                item.indexOf(i) > -1 ? (_self.areaMap[i] ++, flag = false) : null;
            }
            flag && _self.areaMap['其他'] ++;
        });
    }

  加一点修饰,比如用一下我们最常见的echart.js来将我们这些数据做可视化。

  可以看到岗位工作地点北上广深杭占了近80%,其中北京和上海的前端岗均占比超20%。

  第二,再来看看学历要求。

//  学历要求分析
eduAnalysis () {

    let edu = this.data.map(item => item.experience),
        _self = this;
    
    edu.forEach(item => {
        let flag = true;
        for(let i in _self.eduMap) {
            if(item.indexOf(i) > -1) {
                _self.eduMap[i] ++;
                flag = false;
                break;
            }
        }
        flag && _self.eduMap['其他'] ++;
    });
}


  最低学历要求超75%是本科,所以高考不管怎样,最好是能到本科,清华也是本科,当然,大专要求的占比也有接近17%,也不算特别低,大专的同学机会也还好,技术水平是关键。

  第三,再来看看薪酬水平。

//  薪酬水平分析
rewardAnalysis () {

    let _self = this;
    let reward = this.data.map(item => {

        if(item.reward.indexOf('万') > -1) {
            let average = item.reward.replace('万','').split('-');
            return ( Number(average[0]) + Number(average[0]) ) / 2;
        }
        return item.reward;
    });
    let arr = [];
    for(let i in _self.rewardMap) {

        i !== '面议' && arr.push(i);
    }
    reward.forEach((item, index) => {

        if(typeof item === 'string' && item.indexOf('面议') > -1) _self.rewardMap['面议'] ++;
        else if (item >= 100){

            _self.rewardMap['100万及以上'] ++;
        }
        else{
            _self.rewardMap[arr[Math.floor(item / 10)]] ++;
        } 
    });

}

  看看占比:

  可以看到,薪酬水平在10-19万,20-29万的维度占比是最高的,分别达到44.17%和19.95%。而更上一层楼的30+整体占比和也接近15%,当然也有不少比例并未直接列出薪酬而是面议薪酬,看着这个数据,笔者不禁感叹,又拖后腿了。

  第四,再来看看前端级别需求。

//  前端级别需求
rankAnalysis () {
    let rank = this.data.map(item => item.name),
        _self = this;

    rank.forEach(item => {

        let flag = true;
        for(let i in _self.rank) {
            i !== '初中级' && item.indexOf(i) > -1 ? (_self.rank[i] ++, flag = false) : null;
        }
        flag && _self.rank['初中级'] ++;
    });
}

  看看占比

  当然这里的初中级之所以占比这么高,是因为程序的判定就是招聘的信息中未有高级、资深、专家或主管的,自动被归为初中级,实际可能比这个低一些,但高级及以上的人才是目前很多公司急需也很缺少的人才。

  第五,再来看看公司的标签。

//  公司标签分析
tagAnalysis () {
    let companyTag = this.data.map(item => item.company.tag),
        _self = this;

    companyTag.forEach(item => {

        for(let i in _self.companyTag) {
            item.indexOf(i) > -1 ? _self.companyTag[i] ++ : null;
        }
    });
}

  这里列出的标签有:

//  公司标签 - 只按照标签计算,会超过总工作数,初步预估总标签数是总工作数的几倍
    companyTag: {
        '股票期权': 0,
        '带薪年假': 0,
        '弹性工作': 0,
        '年度旅游': 0,
        '节日礼物': 0,
        '交通补助': 0,
        '五险一金': 0,
        '六险一金': 0,
        '七险一金': 0,
        '团队聚餐': 0,
        '定期体检': 0,
        '休闲餐点': 0,
        '休闲餐点': 0,
        '500强': 0,
        '外派津贴': 0,
        '周末双休': 0,
        '免费班车': 0,
        '领导好': 0,
        '老板NICE': 0,
        '扁平管理': 0,
        '人性化管理': 0,
        '住房补贴': 0,
        '年底双薪': 0,
        '上市公司': 0,
        '公司规模大': 0,
        '发展空间大': 0,
        '美资企业': 0,
        '不加班': 0
    },

  再来看看这些标签的占比:

  五险一金、六险一金、七险一金、老板好、领导NICE,管理人性化、扁平、各种补贴,各种礼物,各种聚餐,各种休假,各种标签琳琅满目,当然其中占比较多的还是诸如发展空间大、带薪年假、五险一金等常规福利。当然也出现了少量的七险一金的标签,当然这部分也是只有大厂才出得起的。

  当然值得一提的是,不加班作为一种福利的标签被放在了这里,而占比也是非常的感人——0.02%,再加上之前的996ICU,可见加班是目前互联网乃至软件行业的常态。

  最后,再来看看代码吧。

主要的dataAnalysis.js + 一个字典js

import { dataDictionary } from './dataDictionary.js'

export class DataAnalysis {

    constructor () {

        this.init();
        this.all();
    }
    init () {

        //  地区枚举初始化
        this.areaMap = dataDictionary.areaMap;
        //  学历要求分析
        this.eduMap = dataDictionary.eduMap;
        //  薪酬水平分析
        this.rewardMap = dataDictionary.reward;
        //  前端级别需求
        this.rank = dataDictionary.rank;
        //  公司标签分析
        this.companyTag = dataDictionary.companyTag;
    }
    //  地区分布分析
    areaAnalysis () {
        let area = this.data.map(item => item.area),
            _self = this;
        
        area.forEach(item => {

            let flag = true;
            for(let i in _self.areaMap) {
                item.indexOf(i) > -1 ? (_self.areaMap[i] ++, flag = false) : null;
            }
            flag && _self.areaMap['其他'] ++;
        });
    }
    //  学历要求分析
    eduAnalysis () {

        let edu = this.data.map(item => item.experience),
            _self = this;
        
        edu.forEach(item => {
            let flag = true;
            for(let i in _self.eduMap) {
                if(item.indexOf(i) > -1) {
                    _self.eduMap[i] ++;
                    flag = false;
                    break;
                }
            }
            flag && _self.eduMap['其他'] ++;
        });
    }
    //  薪酬水平分析
    rewardAnalysis () {

        let _self = this;
        let reward = this.data.map(item => {

            if(item.reward.indexOf('万') > -1) {
                let average = item.reward.replace('万','').split('-');
                return ( Number(average[0]) + Number(average[0]) ) / 2;
            }
            return item.reward;
        });
        let arr = [];
        for(let i in _self.rewardMap) {

            i !== '面议' && arr.push(i);
        }
        reward.forEach((item, index) => {

            if(typeof item === 'string' && item.indexOf('面议') > -1) _self.rewardMap['面议'] ++;
            else if (item >= 100){

                _self.rewardMap['100万及以上'] ++;
            }
            else{
                _self.rewardMap[arr[Math.floor(item / 10)]] ++;
            } 
        });

    }
    //  前端级别需求
    rankAnalysis () {
        let rank = this.data.map(item => item.name),
            _self = this;

        rank.forEach(item => {

            let flag = true;
            for(let i in _self.rank) {
                i !== '初中级' && item.indexOf(i) > -1 ? (_self.rank[i] ++, flag = false) : null;
            }
            flag && _self.rank['初中级'] ++;
        });
    }
    //  公司标签分析
    tagAnalysis () {
        let companyTag = this.data.map(item => item.company.tag),
            _self = this;

        companyTag.forEach(item => {

            for(let i in _self.companyTag) {
                item.indexOf(i) > -1 ? _self.companyTag[i] ++ : null;
            }
        });
    }
    all () {
        let _self = this;
        fetch('./cache/jobs.txt')
        .then(res => res.json())
        .then(data => {
            _self.data = data;
            //  地区分布分析
            this.areaAnalysis();
            //  学历要求分析    
            this.eduAnalysis();
            //  前端级别需求
            this.rankAnalysis();
            //  公司标签分析
            this.tagAnalysis();
            //  薪酬水平分析
            this.rewardAnalysis();
        });
    }
}

再看看页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>爬虫测试</title>
    <style>
        html, body, div, p, h1, h2, h3{
            margin: 0;
            padding: 0;
        }
        body{
            background-color: rgb(235, 240, 247);
        }
        .pie{
            margin: 0 auto;
            width: 720px;
            height: 600px;
        }
        .tag{
            width: 1200px;
            height: 800px;
        }
    </style>
</head>
<body>
    <div class='pie area'></div>
    <div class='pie edu'></div>
    <div class='tag reward'></div>
    <div class='pie rank'></div>
    <div class='tag companyTag'></div>
    
    <script src='./dataAnalysis/echarts.min.js'></script>
    <script type="module">

        import { DataAnalysis } from './dataAnalysis/dataAnalysis.js';
        import { PicChart } from './dataAnalysis/picChart.js';

        let data = new DataAnalysis();
        console.log(data);
        
        setTimeout(function () {
            
            new PicChart(JSON.parse(JSON.stringify(data.areaMap)), '.area', 'areaMap');
            new PicChart(JSON.parse(JSON.stringify(data.eduMap)), '.edu', 'eduMap');
            new PicChart(JSON.parse(JSON.stringify(data.rewardMap)), '.reward', 'rewardMap');
            new PicChart(JSON.parse(JSON.stringify(data.rank)), '.rank', 'rank');
            new PicChart(JSON.parse(JSON.stringify(data.companyTag)), '.companyTag', 'companyTag');
            
        }, 1000);

    </script>
</body>
</html>

  基本上初步的分析就到这了,当然爬虫嘛,能实时或定时爬取最新信息,并做分析输出报表,才是上上之选,轮子都造好给你了,这里这部分就留给你去拓展了哈。

1人推荐
随时随地看视频
慕课网APP

热门评论

为什么用vscode 运行 index.html 报错,提示:

Uncaught SyntaxError: Unexpected identifier

dataAnalysis/dataAnalysis.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.


查看全部评论