资讯专栏INFORMATION COLUMN

node.js 爬取招聘信息分析各职业钱途(爬虫+动态IP代理+数据可视化分析)

546669204 / 457人阅读

摘要:成功爬取了拉钩网上多个招聘岗位的具体信息后,数据可视化并得出分析结果如下从整体看,北上广深杭这五个城市前端工程师招聘岗位,北京是遥遥领先,是深圳的两倍,是广州的三倍,其次到上海,深圳,杭州,广州居末。

前前言

本文首发于 github blog

不想看爬虫过程只想看职位钱途数据分析请看这里:
前端招聘岗位分析
C++招聘岗位分析
JAVA招聘岗位分析
PHP招聘岗位分析
Python招聘岗位分析

想看源码或想自己爬一个请看这里:本文github源码

前言

早在一年前大学校招期间,为了充实下简历,就写了个node爬虫,可惜当时能力有限,工程存在一定的局限性,不好意思拿出来装逼分享。

一年过去了,现在能力依然有限,但是脸皮却练厚了,于是就有了这篇文章。

题纲

关于爬虫,主流技术是用python,然而随着node的出现,对于对python了解有限的前端同学,用node来实现一个爬虫也不失为一个不错的选择。

当然无论是python爬虫还是node爬虫或者其他品种的爬虫,其实除了语言特性之外,其思路基本大同小异。下面我就为大家详细介绍下node爬虫的具体思路与实现,内容大概如下:

爬前准备

选择目标

分析可收集数据与目标可爬取入口

爬虫

爬取JSON数据

爬取HTML文档,提取有用信息

Mongodb 数据存储

并发控制

动态IP代理(防止IP被禁)

数据可视化展示

爬前准备 选择目标

既然要写爬虫,当然要爬一些利益相关的数据比较好玩啦。爬取招聘网站的招聘信息,来看看互联网圈子里各个工种的目前薪酬状况及其发展前景,想来是不错的选择。

经我夜观天下,掐指一算,就选拉勾网吧。

分析可收集数据

一个职位招聘信息,一般来说,我们关注的重点信息会是:

薪酬(毫无疑问,重中之重)

工作城市

学历要求

工作年限要求

雇主公司

公司领域

公司规模

带着想要收集的信息,首先,进入拉勾官网,搜索web前端岗位,能看到

很好,我们想要的信息基本都有了。

分析目标可爬取入口 PC端入口

F12 分析请求资源,可得https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false&isSchoolJob=0
post 请求体

{
    first:false,
    pn:1,
    kd:`web前端`
}

响应JSON数据

完美!!! 数据格式都已经帮我们整理好了,直接爬就行了。

但,完美的数据总不会这么轻易让你得到,经我用 nodepython,还有postman 携带浏览器全部header信息一一测试,均发现:

好吧,此路不通。(此接口反爬虫机制不明,有研究的大神请留言=_=)

所谓条条大路通罗马,此路不通,咱绕路走。

移动端入口

经过一番探索,发现 拉勾移动端站点 空门大开!

提示: 一般有点技术含量的网站都可能会存在不同强度的反爬虫机制,而一般其移动端站点的反爬虫机制相对于PC站点较弱,是一个不错的着手点。再不行的话,还可以去其app端抓包分析是否存在想要的请求哦。

GET请求: https://m.lagou.com/search.js...
响应信息:

很好,虽然数据信息有点少,但是总算是一个能爬的接口了。

爬虫

好了,分析也分析完了,现在正式设计爬虫程序。

JSON数据爬取

首先,把请求的路径与参数多带带抽离。

let spider = {
    requestUrl : "http://m.lagou.com/search.json",
    query: {
        city: "",
        pageNum: "",
        job: "",
    },
    ...
}

发出请求,此处的服务端构造请求使用 superagent,当然,用 request 等类似的包也可以,并无限定。

let spider = {
    ....
/**
 * 发起单个请求
 * @return {> | >} 请求成功resolve原始数据,否则reject
  **/
    request() {
        return new Promise((resolve,reject)=>{
            superagent
            .get(this.requestUrl)
            .query({
                city: this.query.city,
                pageNo: this.query.pageNum,
                positionName: this.query.job
            }).end((err, res)=>{
                let dataList = [];
                if (err || !res || !res.ok) {
                    console.error(err);
                    reject("request failed!")
                } else  {
                    dataList = res.body.content.data.page.result
                    if (dataList.length === 0) {
                        // 当请求结果数组长度为0,即认为已经到末页,结束爬虫
                        reject("finish");                     
                    } else {
                        resolve(dataList)
                    }
                } 
            })
        })
    },
 

处理数据

let spider = {
    ....
/**
 * 处理爬取到的原始数据,提取出所需的数据
 * @param {} - companyList : 原始数据
 * @return {>} resolve处理过的数据
  **/
    handleCallbackData(companyList) {
       
        //处理数据
         let arr = companyList.map((item) => {
            let salary = item.salary.split("-");
            
            //工资两种情况:”10k以上“ or "10k-15k", 平均工资取中位数
            aveSalary = salary.length == 1 ? parseInt(salary[0])*1000 : (parseInt(salary[0]) + parseInt( salary[1] ) )*500;

            //过滤出所需数据
            return {
                companyFullName: item.companyFullName,
                positionId : item.positionId ,
                salary:aveSalary ,
                city:item.city ,

                field: "",
                companySize:"",
                workYear:"" ,
                qualification: "",
            }
        });

        return Promise.resolve(arr)
    }

保存数据,此处数据库使用mongodbORM使用 moogoose

save2db(jobList) {
    return new Promise((resolve, reject)=>{
        Job.create(jobList,function (err,product) {
            if (err) {
                console.error(err.errmsg)
                err.code == 11000 && resolve("丢弃重复数据")
                reject(err);
            } else {
                resolve("save data to database successfully")
            }
        })    
    })
},

HTML 数据解析爬取

从上述的json数据其实我们可以看到,JSON返回的信息十分有限,那么我们需要爬取更多的信息,就需要在招聘详情页解析 html 后提取出所需的信息
随便打开一个移动端的招聘详情页https://m.lagou.com/jobs/3638173.html,目测出url结构很简单,就是jobs/{{positionId}}.html

从详情页中可以找出 JSON 数据中缺少的数据项:工作年限要求,学历要求,雇主公司领域,雇主公司融资情况,雇主公司规模大小。

爬取方法和上述爬取 JSON 数据相差无几,主要差别就是数据解析部分,这里需要用到cherrio来解析 爬取到的HTML,从而更简单地提取必要信息。

    handleCallbackData({res, jobId}) {
        var $ = cheerio.load(res.text);

        let workYear = $("#content > div.detail > div.items > span.item.workyear > span").text(),
            qualification = $("#content > div.detail > div.items > span.item.education").text().trim(),
            field = $("#content > div.company.activeable > div > div > p").text().trim().split(/s*/s*/)[0]
            companySize = $("#content > div.company.activeable > div > div > p").text().trim().split(/s*/s*/)[2];

        /* 如果这四项数据都没有提取到,很有可能是被拉勾的反爬虫机制拦截了 */
        if ( !(workYear || qualification || field || companySize) ) {
            console.log(res.text)
            return Promise.reject({code:-1, msg:"wrong response!", jobId});
        }

        return {
            id: jobId,
            jobInfo: {
                workYear,
                qualification,
                field,
                // financeStage,
                companySize,
            }
        }
    },
并发控制

做过爬虫的都知道,爬虫的请求并发量是必须要做的,为什么要控制并发?

控制其爬取频率,以免没爬几个就网站被封IP了。

控制爬虫应用运行内存,不控制并发的话一下子处理N个请求,内存分分钟爆炸。

实现并发控制可以使用npm包 async.mapLimit,这里为了自由度更大我使用了自己实现的 15 行代码实现并发控制。

具体代码如下:

let ids = [2213545,5332233, ...], // 招聘岗位详情id列表
    limit = 10, // 并发数
    runningRequestNum = 0 , // 当前并发数
    count = 0; // 累计爬取数据项计数
    
mapLimit(ids, limit, async (jobId)=>{
    let requestUrl = `http://m.lagou.com/jobs/${jobId}.html?source=home_hot&i=home_hot-6` ;
    let delay = parseInt(Math.random() * 2000);

    let currentIndex = count++;
    runningRequestNum++

    await sleep( delay );  // 避免爬太快被封ip,休眠一两秒
    
    let result = await spiderHTML.run({
                    requestUrl,
                    jobId,
                    proxyIp
                })
    console.log(`当前并发数`, runningRequestNum)
    runningRequestNum--
    
    return result;
}).then(mapResult => {
    // 并发控制下将 ids 全部迭代完毕
    // do something 
})



然而,即便严格控制了请求频率,我们还是不可避免地中招了。

对于反爬虫措施比较暴躁的网站来说,一个IP爬取太过频繁,被识别成机器爬虫几乎是不可避免的。

一般来讲,我们最简单直接的方法就是:换IP。这个IP访问频率太高了被反爬拦截到,换个IP就行了嘛。

动态IP代理

单个IP爬虫对于反爬较为严厉的网站是走不通的。那么我们需要用到动态IP池,每次爬取时从IP池中拉取一个IP出来爬数据。

道理很简单,
1秒内1个IP访问了100个页面,即便是单身20多年的手速也无法企及。只能是机器爬虫无疑。
但1秒内100个IP访问100个页面,平均每个IP一秒内访问了1个页面,那基本不会被反爬干掉

怎么搭建动态IP池?

首先我们得有一个IP源,动态IP池的补充都从这里拉取,这个网上搜一下"免费代理IP"就有很多出来,选其中一个,收费的IP源比较稳定可靠,免费的就一分钱一分货了。

其次,每次从IP源中拉取的IP都是无法确认其是否可用的,我们必须筛选一遍,提取出可用的IP。(PS: 此处和步骤4目的一直,如果IP源较为可靠,可以省略)

设计从IP池中拉取单个IP的策略,使得每个IP使用频率均匀,尽量避免单个IP使用频率过高而失效。

移除失效IP。尽管设计了拉取策略,但依旧不可避免某些IP失效,此时需要将其移出IP池废弃。

动态IP池工作流程:

st=>start: 提取一个可用IP
e=>end: 根据策略返回一个可用IP
isEnought=>condition: 池中IP数量是否足够
fetch=>operation: 从IP源拉取IP
valid=>operation: 筛选出有效IP并存入IP池
st->isEnought(yes)->e
isEnought(no,right)->fetch(right)->valid(right)->isEnought

具体实现代码其实和上面的爬虫差不多,无非就是爬岗位变成了爬IP而已,具体实现源码在这,就不在这写了。

数据可视化分析

我们最终折腾爬虫,无非就是想要看爬到的数据到底说明了什么。
成功爬取了拉钩网上多个招聘岗位的具体信息后,数据可视化并得出分析结果如下:


从整体看,北上广深杭这五个城市前端工程师招聘岗位,北京是遥遥领先,是深圳的两倍,是广州的三倍,其次到上海,深圳,杭州,广州居末。

从需求量大概可以看出,整体互联网产业发达程度是 北 > 上 > 深 > 杭 > 广


由平均工资曲线图可以看到,每隔2K算一档的话,北京一档,上海一档,杭州深圳一档,空一档,广州吊车尾,杭州竟然比深圳高了300,这就代表着深圳虽然招聘需求比杭州大,但两者薪酬待遇其实差不多。

从不同薪酬的招聘数量也能看出一些很大的区别,招聘提供薪资水平中,普遍数量最多的是10k-20k这个水平,但,北京牛逼,招聘岗位60%以上都是20K以上的。我们具体来看看,各个城市对高端人才(提供薪酬20k以上)的招聘比例,那就可以看出明显区别了:

北京:招聘的薪资水平是"20k以上",大概是招聘总数的59.7%

上海:招聘的薪资水平是"20k以上",大概是招聘总数的41.3%

深圳:招聘的薪资水平是"20k以上",大概是招聘总数的29.2%

杭州:招聘的薪资水平是"20k以上",大概是招聘总数的30.4%,和深圳相差不大

广州:招聘的薪资水平是"20k以上",大概是招聘总数的……10.4%。


基本可以看到一个明显的趋势,公司规模越大,能提供的薪酬越高,不差钱。
另外,从不同规模的公司的前端招聘数量来看,北京又一枝独秀,大公司招聘需求很高。

但从全国来看,不同规模的公司(除了15人以下的)招聘数量基本在同一水平,基本说明:大公司少,但是每个公司招聘的人多;小公司多,但是每个公司招聘的人少。好像这是句废话。


从图上看,工作经历在1-5年的现在需求最旺盛,并且理所当然地,工作资历越高,薪资越高。
其中3-5年的最吃香,广州有点奇怪,1-3年的最吃香?综合上面的多项数据,感觉像是1-3年工资比3-5年低所以广州互联网公司多招1-3年

当然,这里存在这一个幸存者偏差,拉勾上大部分的都是社招性质的招聘,而应届生和1年经验的大部分都跑校招去了吧,所以数量低也不出奇。


移动互联网占据了大半壁江山,剩下之中,金融,电子商务,企业服务,数据服务在同一层次。另外,物联网,智能硬件各有一招聘岗位,薪酬都是5K...嗯虽说node现在也可以做物联网了(还别说,我还真的用node搞过硬件串口通信Orz),但是终究不是主流技术,数据展示表明,前端基本与硬件绝缘。

薪酬待遇倒是都在同一水平上,“大数据”工资倒是一枝独秀,但是数据量太少,参考价值不大。

总结:北京钱多机会多当之无愧第一档;上海稍逊一筹;杭州深圳又低一筹;广州真的是差了两个身位。 而对于前端来说,北京 移动互联网 大公司,钱多!坑多!速来!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93896.html

相关文章

  • 前程无忧岗位数据爬取+Tableau视化分析

    摘要:实际上,前程无忧招聘网站上与数据有关的只有几百页,而我们爬取了页的所有数据,因此在后面进行数据处理时需要把无关的数据剔除掉。 目录 一、项目背景 二、数据爬取 1、相关库的导入与说明 2、获取二级页面链接 1)分析一级页面url特征 2)构建一级url库 3)爬取所有二级url链接 3、获取...

    233jl 评论0 收藏0
  • ?Echarts统计拉勾网招聘信息(scrapy 爬取

    摘要:因为本人在成都从事前端,所以这次爬取的关键词既是成都,前端。仅仅有这个是不够的,因为貌似拉勾网有反爬虫,没有好像得不到数据这个还待论证,至少我这边是。 前言 showImg(https://segmentfault.com/img/bV1g4S?w=700&h=490); 今天是2018的第一天,首先祝各位小伙伴元旦快乐!又到了新的一年,虽然离春节还有一段时间,但是程序狗打工不易啊,不...

    genefy 评论0 收藏0
  • ?Echarts统计拉勾网招聘信息(scrapy 爬取

    摘要:因为本人在成都从事前端,所以这次爬取的关键词既是成都,前端。仅仅有这个是不够的,因为貌似拉勾网有反爬虫,没有好像得不到数据这个还待论证,至少我这边是。 前言 showImg(https://segmentfault.com/img/bV1g4S?w=700&h=490); 今天是2018的第一天,首先祝各位小伙伴元旦快乐!又到了新的一年,虽然离春节还有一段时间,但是程序狗打工不易啊,不...

    Jingbin_ 评论0 收藏0
  • 区块链招聘信息爬取分析

    摘要:最近在研究区块链,闲来无事抓取了拉勾网上条区块链相关的招聘信息。拉勾网的反爬虫做的还是比较好的,毕竟自己也知道这种做招聘信息聚合的网站很容易被爬,而且比起妹子图这种网站,开发的技术水平应该高不少。 最近在研究区块链,闲来无事抓取了拉勾网上450条区块链相关的招聘信息。过程及结果如下。 拉勾网爬取 首先是从拉勾网爬取数据,用的requests库。拉勾网的反爬虫做的还是比较好的,毕竟自己也...

    kelvinlee 评论0 收藏0
  • node.js 89行爬虫爬取智联招聘信息

    摘要:智联其实一共写了两次,有兴趣的可以在源码看看,第一版的是回调版,只能一次一页的爬取。 写在前面的话,    .......还是不写了,直接上效果图。附上源码地址 github.lonhon showImg(https://segmentfault.com/img/bVUM3F?w=714&h=543);showImg(https://segmentfault.com/img/bVUM...

    _ivan 评论0 收藏0

发表评论

0条评论

546669204

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<