項目目錄
項目要求
利用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°reefrom=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°reefrom=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°reefrom=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°reefrom=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°reefrom=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來搞定;
因爲我們拿到的所有關鍵字,在之前用了空白去替換,也就是說,做出來的詞雲圖會有瑕疵,也就需要我們去想辦法去過濾一遍。其中的邏輯就交給你們自己想想該怎麼去過濾兒。我就負責給你們思路;
- 因爲詞雲圖會根據你元素出現的次數來判斷大小,所以,我們需要統計每個元素出現的次數
- 其次,你還要過濾空白,還有一些特殊字符
最後給你們展示一下詞雲圖的效果;
總結經驗
這個項目,實用性還是很強,用到的知識點也很廣泛,對鞏固基礎也有很好的效果。
唯一美中不足的地方就是爬行速度太慢,建議使用分佈式或者多線程模式爬取。
謝謝觀看 ~~~