?Echarts統計拉勾網招聘信息(scrapy 爬取)

前言

圖片描述

今天是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

  • 請求的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...

相關文章
相關標籤/搜索