前言
今天是2018的第一天,首先祝各位小伙伴元旦快乐!
又到了新的一年,虽然离春节还有一段时间,但是程序狗打工不易啊,不关注薪资怎么行。今天要做的就是用图表统计一下现在各公司的薪资状况(虽然很多公司不能按照招聘上他们给的薪资来给)。
数据爬取
本次使用scrapy来做数据爬取,这是一个python的框架。因为本人在成都从事web前端,所以这次爬取的关键词既是:成都,web前端。
scrapy startproject lagou
首先通过运行命令,得到一个爬虫项目的基础结构。
接着按照scrapy的中文教程,通过在
start_urls = [
"https://www.lagou.com/jobs/list_web%E5%89%8D%E7%AB%AF?labelWords=sug&fromSearch=true&suginput=web"
]
spider中的start_urls配置好,应该就能把拉勾网页面拉取下来,然后再分析dom,提取字符串就可以了,无奈这种方法并不行。
起初也不知道,就用xpath一直找,后来发现找不到会报错,这些各种错误对于我这个爬虫萌新还是懵逼的。仔细查看他的network发现,他的招聘信息都是在另外的ajax请求当中,并且还是整理好的。
因为本人工作1年多,所以主要关注点是3年以下及3-5年,就提前选好了,城市和工作年限。该请求的传参是formdata,其中first是首页(其实写代码的时候并没有注意这个参数,所以一直传的是true,貌似也没什么影响),pn是当前页数,kd是关键词。
于是乎就去文档查阅了一下,如何在scrapy中循环发送formdata请求。最终得到这样一段可以执行的代码。
def start_requests(self):
url = "https://www.lagou.com/jobs/positionAjax.json?gj=3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B%2C3-5%E5%B9%B4&xl=%E6%9C%AC%E7%A7%91&px=default&city=%E6%88%90%E9%83%BD&needAddtionalResult=false&isSchoolJob=0"
for i in range(1, 14):
formdata = {'first': 'true', 'pn': str(i), 'kd': 'web前端'}
yield scrapy.FormRequest(str(url), callback=self.parseJson, formdata=formdata)
start_requests是发送post请求的方法,FormRequest这个方法接收请求url,传递数据formdata,以及回调函数parseJson。parseJson在这里主要是接收获取的数据。
仅仅有这个是不够的,因为貌似拉勾网有反爬虫,没有header好像得不到数据(这个还待论证,至少我这边是)。然后再settings.py文件中做了一些配置,配置主要有:
- 请求的header(主要是这几项)
DEFAULT_REQUEST_HEADERS={
Accept:application/json, text/javascript, */*; q=0.01
Host:www.lagou.com
Origin:https://www.lagou.com
Referer:https://www.lagou.com/jobs/list_web%E5%89%8D%E7%AB%AF?px=default&gj=3%E5%B9%B4%E5%8F%8A%E4%BB%A5%E4%B8%8B,3-5%E5%B9%B4&city=%E6%88%90%E9%83%BD
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
}
- FEED_EXPORT_ENCODING(因为爬取到的中文是unicode字符)
FEED_EXPORT_ENCODING = 'utf-8'
- ROBOTSTXT_OBEY(这是一个爬虫机器的协议,如果是true,表示遵守,有些网站禁止爬取的话,这个如果是true就爬不到了)
ROBOTSTXT_OBEY = False
- DOWNLOAD_DELAY(延时,这个也是去避免被反爬虫,我这边直接设置了比较长的时间,也没有去测试多少合适,因为不设置也是会报错的)
DOWNLOAD_DELAY = 10
基础的配置项配置完毕之后,就是写数据存储的模型了,因为我只想去简单统计一下,所以只存了薪资和工资这两个字段,想要统计更多的信息,就直接继续加就好了,这个比较简单,在items.py中编写
class LaGou(scrapy.Item):
salary = scrapy.Field()
company = scrapy.Field()
经过这几项配置,运行命令
scrapy crawl lagou -o a.json
就可以得到一份a.json,里面就是成都web前端相关,工作年限为0-5年的数据信息了。有了这份数据,接下来要做的就是数据处理了。
数据处理
在之前的a.json当中,大致可以得到一份之下的数据,总计195条
[
{"salary": "8k-16k", "company": "xx有限公司"},
......
]
为了前端处理方便,直接改为js文件加一个变量引入html,即
var a = [
{"salary": "8k-16k", "company": "xx有限公司"},
......
]
这组数据的薪资是一个范围,不方便我统计,于是为了便于操作数据把薪资取平均值,并统计提供相同的薪资的公司数目。
js代码如下:
var arr = data.map(function (value) {
return value.salary && value.salary.replace(/k|K/g, "").split('-').reduce(function (pV, nV) {
return pV + nV / 2
}, 0)
}).reduce(function (pV, nV) {
nV in pV ? pV[nV]++ : (pV[nV] = 1);
return pV;
}, {})
//这里的data既是上边的a变量
这段代码主要作用是把薪资范围计算成平均数,然后再统计数组中相同的平均数的个数。代码写的随意,可读性较差,见谅。这段代码处理过后,可得到类似如下数据:
{'8':1,'8.5':3}
key是薪资均值,value是个数。
于是将key,value分别存入数组。这里遇到一个问题,就是开始我是这样操作的
var xData=[...Object.keys(arr)]
var yData=[...Object.values(arr)]
这么做有一个问题就是浏览器对于对象的遍历规则,导致输出的数组,小数都到了最外边(比如这样[1,2,1.5]),这样在echarts下的图表是乱序的。也没有想到好的办法去解决,就是对数组进行一次排序,然后再根据排好的key生成相对应的value数组,最终代码:
var xData = [...Object.keys(arr).sort(function (a, b) {
return a - b
})]
var yData = xData.map(function (v) {
return arr[v]
})
echarts比较简单不赘述。将这两组横纵坐标输入echarts,得到最终效果:
总结
本次做这个统计很多地方没想清楚怎么更好的去表现,所以做的很简单,其实细致一点还可以去分类统计,按照公司融资情况,领域等等内容,只要数据拿到都好说。另外很多地方可能写的不够好,主要我目前也不太会写,比如之前反爬虫那块,貌似去做动态的用户代理也能行,但我还是增加了延时,选择了比较笨的方法。另外也不会python,但还好python比较好读。因为这一块才开始学习,相信以后会越写越好的,新的一年,加油!
update 2018/01/03
昨天又把爬虫优化了一下,去掉了之前的延时,增加了动态用户代理和动态IP代理,解决了之前爬虫的效率问题,也扩大了数据量。
动态IP代理
通过网上搜索免费的ip代理,获取了如下一组ip:
PROXIES = [
{'ip_port': '106.39.179.244:80'},
{'ip_port': '65.52.223.99:80'},
{'ip_port': '1.52.248.207:3128'},
{'ip_port': '45.77.198.207:3128'},
{'ip_port': '177.125.119.16:8080'},
{'ip_port': '174.138.65.233:3128'},
]
该IP过一段时间可能会失效,请自行搜索,如http://www.xicidaili.com/。
在middlewares.py中声明该IP,之后声明动态IP代理类
import random
class ProxyMiddleware(object):
def process_request(self, request, spider):
proxy = random.choice(PROXIES)
request.meta['proxy'] = "http://%s" % proxy['ip_port']
print("**************ProxyMiddleware no pass************" + proxy['ip_port'])
在settings.py文件中声明该中间件
DOWNLOADER_MIDDLEWARES = {
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 110,
'tutorial.middlewares.ProxyMiddleware': 100,
}
动态用户代理
在middlewares.py中声明动态用户代理类
class RandomUserAgent(object):
"""Randomly rotate user agents based on a list of predefined ones"""
def __init__(self, agents):
self.agents = agents
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist('USER_AGENTS'))
def process_request(self, request, spider):
# print "**************************" + random.choice(self.agents)
request.headers.setdefault('User-Agent', random.choice(self.agents))
同样在settings.py的中间件里声明
DOWNLOADER_MIDDLEWARES = {
'tutorial.middlewares.RandomUserAgent': 1,
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 110,
'tutorial.middlewares.ProxyMiddleware': 100,
}
再次运行scrapy crawl lagou,即可得到新的数据。
增加薪资筛选
在原有基础上增加了对于工作年限和公司规模的筛选,并计算了平均值。
更新代码如下:
// 指定图表的配置项和数据
initData();
function initData() {
average = 0;
arr = temData.map(function (value) { //之前正则筛选字符串有点问题,没有考虑到有些公司格式为10k以上这种。
return value.salary && value.salary.replace(/[k|K\u4e00-\u9fa5]/g, "").split('-').reduce(function (pV, nV, i, array) {
if (array.length > 1) {
average = Number(average) + pV + nV / 2
return pV + nV / 2
} else {
average = +average + Number(nV)
return nV
}
// return array.length > 1 ? pV + nV / 2 : nV
}, 0)
}).reduce(function (pV, nV) {
nV in pV ? pV[nV]++ : (pV[nV] = 1);
return pV;
}, {})
average = (average / temData.length).toFixed(2)
}
暂时这样,通过之后的学习,还会不断的优化。
展示效果:
源码地址:https://github.com/jiwenjiang...