使用scrapy爬取前程無憂所有大數據崗位並做出數據可視化

項目要求

利用python編寫爬蟲程序,從招聘網上爬取數據,將數據存入到MongoDB中,將存入的數據作一定的數據清洗後分析數據,最後做數據可視化。

工具

軟件

python 3.7
pycharm 2020.1.2

具體知識點

python基礎知識
scrapy框架知識點
pyecharts 1.5
MongoDB

具體要求

數據源

前程無憂

爬取字段

職位名稱、薪資水平、招聘單位、工作地點、工作經驗、學歷要求、工作內容(崗位職責)、任職要求(技能要求)

數據存儲

將爬取到的數據保存在MongoDB中

數據分析與可視化

具體要求
(1) 分析“數據分析”、“大數據開發工程師”、“數據採集”等崗位的平均工資、最高工資、最低工資,並作條形圖將結果展示出來。

(2)分析“數據分析”、“大數據開發工程師”、“數據採集”等大數據相關崗位在成都、北京、上海、廣州、深圳的崗位數,並做條形圖將結果展示出來。

(3)分析大數據相關崗位1-3年工作經驗的薪資水平(平均工資、最高工資、最低工資),並做出條形圖展示出來;

(4)將數據採集崗位要求的技能做出詞雲圖

具體步驟

分析網頁

點擊進入網頁
我們先來看看網頁構造。再來分析思路:
由於我們需要的大數據崗位的分佈數據,所以我們就直接搜索條件,分析大數據崗位在全國的一個分佈情況。
在這裏插入圖片描述
我的思路是,既然需要字段,而且這個頁面上的所有字段並沒有我們需要的全部,那我們就需要進入到每一個網址裏面去分析我們的字段。先來看看進去後是什麼樣子。
在這裏插入圖片描述
我們需要的字段都在這裏面了,所以,我們就可以開始動手寫代碼了。

實現代碼

抓取全部崗位的網址

我們之前說過,要進入到每一個網址去,那麼就必然需要每一個進去的入口,而這個入口就是這個:
在這裏插入圖片描述
新建一個爬蟲項目:

scrapy startproject qianchengwuyou

然後打開我們的項目,進入瞅瞅會發現啥都沒有,我們再cd到我們的項目裏面去開始一個爬蟲項目

scrapy genspider qcwy https://search.51job.com/

當然,這後邊的網址就是你要爬取的網址。

首先在開始敲代碼之前,還是要設置一下我們的配置文件settings.py中寫上我們的配置信息:

# 關閉網頁機器人協議
ROBOTSTXT_OBEY = False
# mongodb地址
MONGODB_HOST='127.0.0.1'
# mongodb端口號
MONGODB_PORT = 27017
# 設置數據庫名稱
MONGODB_DBNAME = '51job'
# 存放本數據的表名稱
MONGODB_DOCNAME = 'jobTable'
# 請求頭信息
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
'User_Agent' :'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
# 下載管道
ITEM_PIPELINES = {
 'qianchengwuyou.pipelines.QianchengwuyouPipeline': 300,
}
# 下載延時
DOWNLOAD_DELAY = 1

然後再去我們的pipelines.py中開啓我們的爬蟲保存工作

from scrapy.utils.project import get_project_settings
settings = get_project_settings()
import pymongo
class QianchengwuyouPipeline:
    def __init__(self):
        host = settings['MONGODB_HOST']
        port = settings['MONGODB_PORT']
        self.client = pymongo.MongoClient(host=host,port=port)
        self.db = self.client[settings['MONGODB_DBNAME']]
        self.coll = self.db[settings['MONGODB_DOCNAME']]
    def process_item(self, item, spider):
        data = dict(item)
        self.coll.insert(data)
        return item
    def close(self):
        self.client.close()

定義好pipelines.py之後,我們還需要去items.py中去定義好我們需要爬取的字段,用來向pipelines.py中傳輸數據

import scrapy
class QianchengwuyouItem(scrapy.Item):
    zhiweimingcheng = scrapy.Field()
    xinzishuipin = scrapy.Field()
    zhaopindanwei = scrapy.Field()
    gongzuodidian = scrapy.Field()
    gongzuojingyan = scrapy.Field()
    xueli = scrapy.Field()
    yaoqiu = scrapy.Field()
    jineng = scrapy.Field()

然後就可以在我們的qcwy.py中開始敲我們的代碼了;

在敲代碼之前,還是要先分析一下網頁結構。打開審查工具,看看我們需要的網址用xpath語法該怎麼寫:
在這裏插入圖片描述
我們需要拿到的是這個超鏈接,然後才能用框架去自動進入這個超鏈接提取超鏈接裏面的內容。
再來分析分析他的文檔樹結構是什麼樣子的:
在這裏插入圖片描述
可以很清晰的看到,這個整個欄目都在div class='el'下,而且所有的招聘崗位都在這下面,所以,我們爲了能夠拿到所有的url,就可以去定位他的上一級標籤,然後拿到所有子標籤。再通過子標籤去拿裏面的href屬性。

所以,xpath語法就可以這樣寫:

//*[@id='resultList']/div[@class='el']/p/span/a/@href

我們來打印一下試試:

import scrapy
from qianchengwuyou.items import QianchengwuyouItem
class QcwySpider(scrapy.Spider):
    name = 'qcwy'
    allowed_domains = ['https://www.51job.com/']
    start_urls = ['https://search.51job.com/list/000000,000000,0130%252C7501%252C7506%252C7502,01%252C32%252C38,9,99,%2520,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare=']

    def parse(self, response):
        all_urls = response.xpath("//*[@id='resultList']/div[@class='el']/p/span/a/@href").getall()
        for url in all_urls:
            print(url)

接着我們再寫一個啓動函數:
在當前項目的任何位置新建一個main.py(名字隨便你起,體現出啓動倆字就行),然後寫上這兩行代碼:

from scrapy.cmdline import execute
execute("scrapy crawl qcwy".split())

這個意思就是從scrapy包的cmdline下導入execute模塊,然後,用這個模塊去運行當前項目;
運行結果:
在這裏插入圖片描述
我們拿到所有超鏈接之後,還不夠。要記住,我們需要的所有頁面的超鏈接。所以,我們再來分析分析每一頁之間的規律。注意看最上方的url:

https://search.51job.com/list/000000,000000,0130%252C7501%252C7506%252C7502,01%252C32%252C38,9,99,%2B,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=

當我們點擊下一頁的時候,看看url發生了哪些變化:

https://search.51job.com/list/000000,000000,0130%252C7501%252C7506%252C7502,01%252C32%252C38,9,99,%2B,2,2.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=

有沒有發現,中間的1.html變成了2.html;

我們再來看看,一共有多少頁:
在這裏插入圖片描述
93頁。那我們直接把93輸入進去替換掉會怎麼樣:
在這裏插入圖片描述
果不其然,規律就是這個樣子的。那你知道怎麼去獲取全部網頁的xpath了吧

但是

我跳轉下一頁不喜歡這樣跳,這樣跳,每一次我還得去找規律,一大串網址麻煩的不要不要的,所以,給你們說一種新方法;

我們來看看下一頁的url,用xpath該怎麼去獲取:
在這裏插入圖片描述
看到這個標籤之後,再看看我打的箭頭,知道我要幹什麼了吧。我們可以直接寫xpath語法去獲取這個url,如果有,就交給解析函數去解析當前頁的網址,沒有的話就結束函數的運行:
看看xpath語法該咋寫:

//div[@class='p_in']//li[last()]/a/@href

所以我們只需要做如下判斷:

next_page = response.xpath("//div[@class='p_in']//li[last()]/a/@href").get()
if next_page:
    yield scrapy.Request(next_page,callback=self.parse,dont_filter=True)

如果有下一頁,就繼續交給parse()函數去解析網址;

我們再回到之前的問題,我們拿到當前頁的招聘網址之後,就要進入到這個網址裏面;所以,我們就需要一個解析頁面的函數去解析我們的字段;
所以我們全部的代碼如下:

import scrapy
from qianchengwuyou.items import QianchengwuyouItem
class QcwySpider(scrapy.Spider):
    name = 'qcwy'
    allowed_domains = ['https://www.51job.com/']
    start_urls = ['https://search.51job.com/list/000000,000000,0130%252C7501%252C7506%252C7502,01%252C32%252C38,9,99,%2520,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare=']

    def parse(self, response):
        all_urls = response.xpath("//*[@id='resultList']/div[@class='el']/p/span/a/@href").getall()
        for url in all_urls:
            yield scrapy.Request(url,callback=self.parse_html,dont_filter=True)
        next_page = response.xpath("//div[@class='p_in']//li[last()]/a/@href").get()
        if next_page:
            yield scrapy.Request(next_page,callback=self.parse,dont_filter=True)

我們就單獨再寫一個parse_html()函數去解析頁面就行了;
這樣就能抓取到93頁全部招聘網址;

字段提取

我們隨便點進去一個招聘信息瞅一瞅;
在這裏插入圖片描述
我們需要的是這裏面的字段信息;
還是老樣子,打開審查工具看看構造是什麼樣的;
在這裏插入圖片描述
我們很大一部分的信息都在這個標籤裏面,所以,我們就可以單獨在這個標籤中拿到我們需要的信息。所以,我這兒就直接把xpath語法貼出來,至於怎麼分析怎麼寫,上面講過了;

zhiweimingcheng = response.xpath("//div[@class='cn']/h1/text()").getall()[0]
xinzishuipin = response.xpath("//div[@class='cn']//strong/text()").get()
zhaopindanwei = response.xpath("//div[@class='cn']//p[@class='cname']/a[1]/@title").get()
gongzuodidian = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[0]
gongzuojingyan = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[1]
xueli = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[2]

我們除了這些字段,還需要任職要求和技能要求。我們再看看,下面的職位信息這個標籤中的內容;
在這裏插入圖片描述
我們需要的內容都在這個標籤裏面。但是,這些技能標籤我們該怎麼去提取呢。我也沒想到辦法,所以我乾脆就不提取這裏面的技能,我們重新打開一個招聘信息看看;
在這裏插入圖片描述
看見沒有,有關鍵字。所以我們就可以把這個關鍵字拿來當我們需要的技能標籤。

但是又有細心的小夥伴發問了,有的沒有關鍵字這一欄咋辦,那豈不是找不到標籤會報語法錯誤。
so,你是忘了你學過的try except語法了嗎。

所以,我們抓取全部的字段,就可以這樣來寫:

   def parse_html(self,response):
        item = QianchengwuyouItem()
        try:
            zhiweimingcheng = response.xpath("//div[@class='cn']/h1/text()").getall()[0]
            xinzishuipin = response.xpath("//div[@class='cn']//strong/text()").get()
            zhaopindanwei = response.xpath("//div[@class='cn']//p[@class='cname']/a[1]/@title").get()
            gongzuodidian = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[0]
            gongzuojingyan = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[1]
            xueli = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[2]
            yaoqius = response.xpath("//div[@class='bmsg job_msg inbox']//text()").getall()
            yaoqiu_str = ""
            for yaoqiu in yaoqius:
                yaoqiu_str+=yaoqiu.strip()
            jineng = ""
            guanjianzi = response.xpath("//p[@class='fp'][2]/a/text()").getall()
            for i in guanjianzi:
                jineng+=i+" "
        except:
            zhiweimingcheng=""
            xinzishuipin=""
            zhaopindanwei = ""
            gongzuodidian = ""
            gongzuojingyan =""
            xueli = ""
            yaoqiu_str = ""
            jineng = ""

當然,這些字段抓取到了然後呢,總得需要去想個辦法保存到MongoDB中吧,所以,我們就可以通過之前定義好的items來保存數據;
so,你看了這麼久,終於等到主頁的全部代碼了,我也廢話講了這麼一大片了:

# -*- coding: utf-8 -*-
import scrapy
from qianchengwuyou.items import QianchengwuyouItem
class QcwySpider(scrapy.Spider):
    name = 'qcwy'
    allowed_domains = ['https://www.51job.com/']
    start_urls = ['https://search.51job.com/list/000000,000000,0130%252C7501%252C7506%252C7502,01%252C32%252C38,9,99,%2520,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare=']

    def parse(self, response):
        all_urls = response.xpath("//*[@id='resultList']/div[@class='el']/p/span/a/@href").getall()
        for url in all_urls:
            yield scrapy.Request(url,callback=self.parse_html,dont_filter=True)
        next_page = response.xpath("//div[@class='p_in']//li[last()]/a/@href").get()
        if next_page:
            yield scrapy.Request(next_page,callback=self.parse,dont_filter=True)
    def parse_html(self,response):
        item = QianchengwuyouItem()
        try:
            zhiweimingcheng = response.xpath("//div[@class='cn']/h1/text()").getall()[0]
            xinzishuipin = response.xpath("//div[@class='cn']//strong/text()").get()
            zhaopindanwei = response.xpath("//div[@class='cn']//p[@class='cname']/a[1]/@title").get()
            gongzuodidian = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[0]
            gongzuojingyan = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[1]
            xueli = response.xpath("//div[@class='cn']//p[@class='msg ltype']/text()").getall()[2]
            yaoqius = response.xpath("//div[@class='bmsg job_msg inbox']//text()").getall()
            yaoqiu_str = ""
            for yaoqiu in yaoqius:
                yaoqiu_str+=yaoqiu.strip()
            jineng = ""
            guanjianzi = response.xpath("//p[@class='fp'][2]/a/text()").getall()
            for i in guanjianzi:
                jineng+=i+" "
        except:
            zhiweimingcheng=""
            xinzishuipin=""
            zhaopindanwei = ""
            gongzuodidian = ""
            gongzuojingyan =""
            xueli = ""
            yaoqiu_str = ""
            jineng = ""
        finally:
            item["zhiweimingcheng"] = zhiweimingcheng
            item["xinzishuipin"] = xinzishuipin
            item["zhaopindanwei"] = zhaopindanwei
            item["gongzuodidian"] = gongzuodidian
            item["gongzuojingyan"] = gongzuojingyan
            item["xueli"] = xueli
            item["yaoqiu"] = yaoqiu_str
            item["jineng"] = jineng
        yield item

然後,保存數據就簡單了,我們去pipelines.py中連接到我們的MongoDB,然後保存數據進去;由於保存數據太簡單,百度上一搜就能搜到,我就直接貼代碼出來了:

from scrapy.utils.project import get_project_settings
settings = get_project_settings()
import pymongo
class QianchengwuyouPipeline:
    def __init__(self):
        host = settings['MONGODB_HOST']
        port = settings['MONGODB_PORT']
        self.client = pymongo.MongoClient(host=host,port=port)
        self.db = self.client[settings['MONGODB_DBNAME']]
        self.coll = self.db[settings['MONGODB_DOCNAME']]
    def process_item(self, item, spider):
        data = dict(item)
        self.coll.insert(data)
        return item
    def close(self):
        self.client.close()

然後,開始我們的爬蟲啓動;

由於這個爬蟲不是分佈式,所以,要一條一條的請求,會很慢很慢,你可以在這個時間去喫個飯,看個電影都行;
最後看看我們拿到的數據是什麼樣子的;
在這裏插入圖片描述
一共有4600條數據;整整爬了一個多小時,後期改進;

可視化

分析“數據分析”、“大數據開發工程師”、“數據採集”等崗位的平均工資、最高工資、最低工資,並作條形圖將結果展示出來

新建一個文件,名字自己起,我的叫數據可視化.py
然後需要連接mongodb和做條形圖,就導入下面的這些庫,re用來提取一些不方便取出來的數據

import pymongo
import re
from pyecharts.charts import Bar
from pyecharts import options as opts
myclient = pymongo.MongoClient("127.0.0.1",port=27017)
mydb = myclient["51job"]
mytable = mydb["jobTable"]

先來分析分析需要哪些東西(這兒我就先分析一個,其他兩個是一樣的道理);

  • 首先,崗位名稱是數據分析
  • 然後要三個變量來保存最低、最高、平均薪資。

因爲崗位是數據分析,但是招聘崗位不可能就這四個字。所以我們需要用到模糊查詢,類似於mysql中的like。求工資,平均工資直接就最低+最高然後除2,所以,這兒就用正則語法來提取有最低和最高工資的標籤欄。
多個條件用$and連接;

看看條件怎麼寫吧;

shujufenxi_query = {"$and":
                 [{"zhiweimingcheng": {"$regex": "數據分析"}},
                  {"xinzishuipin": {"$regex": "萬/月"}}
                  ]
}

然後定義三個列表,用來保存三種數據,後面用來給柱狀圖傳參;
所以代碼如下:

def shujufenxi(query):
    min_salary_list = []
    max_salary_list = []
    average_salary_list = []
    addr_list = []
    for i in mytable.find(query):
        min_salary = i["xinzishuipin"].split("-")[0]
        # print(min_salary)
        max_salary = re.findall(r'([\d+\.]+)', (i["xinzishuipin"].split("-")[1]))[0]
        average_salary = "{:.1f}".format((float(min_salary) + float(max_salary)) / 2)
        company = i["gongzuodidian"]
        print(company)
        min_salary_list.append(min_salary)
        max_salary_list.append(max_salary)
        average_salary_list.append(average_salary)
        addr_list.append(company)
    bar = Bar(
        init_opts=opts.InitOpts(width="1800px", height="800px"),
    )
    bar.set_global_opts(
        title_opts=opts.TitleOpts(title="數據分析工程師薪資", subtitle="單位  萬/月"),
        xaxis_opts=opts.AxisOpts(axislabel_opts={"rotate": 45}),
    )
    bar.add_xaxis(addr_list)
    bar.add_yaxis("最高薪資", max_salary_list)
    bar.add_yaxis("最低薪資", min_salary_list)
    bar.add_yaxis("平均薪資", average_salary_list)
    bar.render("數據分析工程師.html")

其他兩個道理是一樣的道理,我就不寫代碼了;自己慢慢摸索吧。
我們來看看數據展示的效果圖;由於數據量太大,展示出來已經面目全非了,後期改進;
在這裏插入圖片描述

分析“數據分析”、“大數據開發工程師”、“數據採集”等大數據相關崗位在成都、北京、上海、廣州、深圳的崗位數,並做條形圖將結果展示出來

其實這個圖和前面做的圖步驟也差不多;
我就直接貼代碼上去了;

import pymongo
import re
from pyecharts.charts import Bar
from pyecharts import options as opts
myclient = pymongo.MongoClient("127.0.0.1",port=27017)
mydb = myclient["51job"]
mytable = mydb["jobTable"]

query_chengdu = {"$and":
                     [{"gongzuodidian":{"$regex":"成都"}},
                       {"zhiweimingcheng": {"$regex": "數據分析"}}
                      ]
                 }
query_beijing = {"$and":
                     [{"gongzuodidian":{"$regex":"北京"}},
                       {"zhiweimingcheng": {"$regex": "數據分析"}}
                      ]
                 }
query_shanghai = {"$and":
                     [{"gongzuodidian":{"$regex":"上海"}},
                       {"zhiweimingcheng": {"$regex": "數據分析"}}
                      ]
                 }
query_guangzhou = {"$and":
                     [{"gongzuodidian":{"$regex":"廣州"}},
                       {"zhiweimingcheng": {"$regex": "數據分析"}}
                      ]
                 }
query_shenzhen = {"$and":
                     [{"gongzuodidian":{"$regex":"深圳"}},
                       {"zhiweimingcheng": {"$regex": "數據分析"}}
                      ]
                 }
chengdu_num = 0
beijing_num = 0
shanghai_num = 0
guangzhou_num = 0
shenzhen_num = 0
for i in mytable.find():
    if "成都" in i["gongzuodidian"]:
        chengdu_num+=1
    elif "北京" in i["gongzuodidian"]:
        beijing_num+=1
    elif "上海" in i["gongzuodidian"]:
        shanghai_num+=1
    elif "廣州" in i["gongzuodidian"]:
        guangzhou_num+=1
    elif "深圳" in i["gongzuodidian"]:
        shenzhen_num+=1
print(chengdu_num,beijing_num,shanghai_num,guangzhou_num,shenzhen_num)
addr = ["成都","北京","上海","廣州","深圳"]
num = [chengdu_num,beijing_num,shanghai_num,guangzhou_num,shenzhen_num]

bar = Bar(init_opts=opts.InitOpts(width="1800px", height="800px"))
bar.set_global_opts(
    title_opts=opts.TitleOpts(title="數據分析師地區崗位個數", subtitle="單位  個"),
    xaxis_opts=opts.AxisOpts(axislabel_opts={"rotate": 45}),
)
bar.add_xaxis(addr)
bar.add_yaxis("數據分析師地區崗位個數", num)
bar.render("數據分析師地區崗位個數.html")

看看運行結果啥樣的:
在這裏插入圖片描述

分析大數據相關崗位1-3年工作經驗的薪資水平(平均工資、最高工資、最低工資),並做出條形圖展示出來

這些圖都沒啥水平,無非就是查詢條件不一樣;
看看結果啥樣的:
在這裏插入圖片描述
也是亂的一批,數據太多,過濾之後也是這個鬼樣子;

將數據採集崗位要求的技能做出詞雲圖

還記得我們之前寫的那個關鍵詞嗎,對,沒錯,我們就是要用這個來做詞雲圖。
詞雲圖還是使用pyecharts自帶的WordCloud來搞定;
因爲我們拿到的所有關鍵字,在之前用了空白去替換,也就是說,做出來的詞雲圖會有瑕疵,也就需要我們去想辦法去過濾一遍。其中的邏輯就交給你們自己想想該怎麼去過濾兒。我就負責給你們思路;

  • 因爲詞雲圖會根據你元素出現的次數來判斷大小,所以,我們需要統計每個元素出現的次數
  • 其次,你還要過濾空白,還有一些特殊字符

最後給你們展示一下詞雲圖的效果;
在這裏插入圖片描述

總結經驗

這個項目,實用性還是很強,用到的知識點也很廣泛,對鞏固基礎也有很好的效果。
唯一美中不足的地方就是爬行速度太慢,建議使用分佈式或者多線程模式爬取。

謝謝觀看 ~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章