今天是2018的第一天,首先祝各位小伙伴元旦快乐!
又到了新的一年,虽然离春节还有一段时间,可是程序狗打工不易啊,不关注薪资怎么行。今天要作的就是用图表统计一下如今各公司的薪资情况(虽然不少公司不能按照招聘上他们给的薪资来给)。javascript
本次使用scrapy来作数据爬取,这是一个python的框架。由于本人在成都从事web前端,因此此次爬取的关键词既是:成都,web前端。html
scrapy startproject lagou
首先经过运行命令,获得一个爬虫项目的基础结构。前端
接着按照scrapy的中文教程,经过在java
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,提取字符串就能够了,无奈这种方法并不行。python
起初也不知道,就用xpath一直找,后来发现找不到会报错,这些各类错误对于我这个爬虫萌新仍是懵逼的。仔细查看他的network发现,他的招聘信息都是在另外的ajax请求当中,而且仍是整理好的。git
由于本人工做1年多,因此主要关注点是3年如下及3-5年,就提早选好了,城市和工做年限。该请求的传参是formdata,其中first是首页(其实写代码的时候并无注意这个参数,因此一直传的是true,貌似也没什么影响),pn是当前页数,kd是关键词。github
因而乎就去文档查阅了一下,如何在scrapy中循环发送formdata请求。最终获得这样一段能够执行的代码。web
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在这里主要是接收获取的数据。ajax
仅仅有这个是不够的,由于貌似拉勾网有反爬虫,没有header好像得不到数据(这个还待论证,至少我这边是)。而后再settings.py文件中作了一些配置,配置主要有:json
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 = 'utf-8'
ROBOTSTXT_OBEY = False
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比较好读。由于这一块才开始学习,相信之后会越写越好的,新的一年,加油!
昨天又把爬虫优化了一下,去掉了以前的延时,增长了动态用户代理和动态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) }
暂时这样,经过以后的学习,还会不断的优化。
展现效果: