0 前言
?? 這兩年開始畢業(yè)設(shè)計(jì)和畢業(yè)答辯的要求和難度不斷提升,傳統(tǒng)的畢設(shè)題目缺少創(chuàng)新和亮點(diǎn),往往達(dá)不到畢業(yè)答辯的要求,這兩年不斷有學(xué)弟學(xué)妹告訴學(xué)長(zhǎng)自己做的項(xiàng)目系統(tǒng)達(dá)不到老師的要求。
為了大家能夠順利以及最少的精力通過畢設(shè),學(xué)長(zhǎng)分享優(yōu)質(zhì)畢業(yè)設(shè)計(jì)項(xiàng)目,今天要分享的是
?? 招聘網(wǎng)站爬取與大數(shù)據(jù)分析可視化
??學(xué)長(zhǎng)這里給一個(gè)題目綜合評(píng)分(每項(xiàng)滿分5分)
- 難度系數(shù):3分
- 工作量:3分
- 創(chuàng)新點(diǎn):3分
1 課題背景
本項(xiàng)目利用 python 網(wǎng)絡(luò)爬蟲抓取常見招聘網(wǎng)站信息,完成數(shù)據(jù)清洗和結(jié)構(gòu)化,存儲(chǔ)到數(shù)據(jù)庫中,搭建web系統(tǒng)對(duì)招聘信息的薪資、待遇等影響因素進(jìn)行統(tǒng)計(jì)分析并可視化展示。
2 實(shí)現(xiàn)效果
首頁
崗位地圖
類型、詞云
3 Flask框架
簡(jiǎn)介
Flask是一個(gè)基于Werkzeug和Jinja2的輕量級(jí)Web應(yīng)用程序框架。與其他同類型框架相比,F(xiàn)lask的靈活性、輕便性和安全性更高,而且容易上手,它可以與MVC模式很好地結(jié)合進(jìn)行開發(fā)。Flask也有強(qiáng)大的定制性,開發(fā)者可以依據(jù)實(shí)際需要增加相應(yīng)的功能,在實(shí)現(xiàn)豐富的功能和擴(kuò)展的同時(shí)能夠保證核心功能的簡(jiǎn)單。Flask豐富的插件庫能夠讓用戶實(shí)現(xiàn)網(wǎng)站定制的個(gè)性化,從而開發(fā)出功能強(qiáng)大的網(wǎng)站。
本項(xiàng)目在Flask開發(fā)后端時(shí),前端請(qǐng)求會(huì)遇到跨域的問題,解決該問題有修改數(shù)據(jù)類型為jsonp,采用GET方法,或者在Flask端加上響應(yīng)頭等方式,在此使用安裝Flask-CORS庫的方式解決跨域問題。此外需要安裝請(qǐng)求庫axios。
Flask項(xiàng)目結(jié)構(gòu)圖
相關(guān)代碼:
from flask import Flask as _Flask, jsonify, render_template
from flask.json import JSONEncoder as _JSONEncoder
import decimal
import utils
class JSONEncoder(_JSONEncoder):
def default(self, o):
if isinstance(o, decimal.Decimal):
return float(o)
super(_JSONEncoder, self).default(o)
class Flask(_Flask):
json_encoder = JSONEncoder
app = Flask(__name__)
# 這里發(fā)現(xiàn)flask根本不會(huì)調(diào)用我在utils中處理數(shù)據(jù)的代碼,所以直接就在這里定義了兩個(gè)常量
# 如果想要爬取其它招聘崗位信息的話,先運(yùn)行utils中的代碼,然后運(yùn)行app.py代碼,同時(shí),更改下面的datatable和job_name
datatable = 'data_mining'
job_name = '數(shù)據(jù)挖掘'
# 路由解析,每映射到一個(gè)路由就調(diào)用一個(gè)函數(shù)
@app.route('/')
def index():
return render_template("main.html")
@app.route('/title')
def get_title1():
return job_name
# 獲取系統(tǒng)當(dāng)前時(shí)間,每隔1s刷新一次
@app.route('/time')
def get_time1():
return utils.get_time()
# 對(duì)數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行計(jì)數(shù)、薪資取平均值、省份和學(xué)歷取眾數(shù)
@app.route('/c1')
def get_c1_data1():
data = utils.get_c1_data(datatable)
return jsonify({"employ": data[0], "avg_salary": data[1], "province": data[2], "edu": data[3]})
# 對(duì)省份進(jìn)行分組,之后統(tǒng)計(jì)其個(gè)數(shù),使用jsonify來將數(shù)據(jù)傳輸給ajax(中國地圖)
@app.route('/c2')
def get_c2_data1():
res = []
for tup in utils.get_c2_data(datatable):
res.append({"name": tup[0], "value": int(tup[1])})
return jsonify({"data": res})
# 統(tǒng)計(jì)每個(gè)學(xué)歷下公司數(shù)量和平均薪資(上下坐標(biāo)折線圖)
@app.route('/l1')
# 下面為繪制折線圖的代碼,如果使用這個(gè)的話需要在main.html中引入ec_left1.js,然后在controller.js中重新調(diào)用
# def get_l1_data1():
# data = utils.get_l1_data()
# edu, avg_salary = [], []
# for s in data:
# edu.append(s[0])
# avg_salary.append(s[1])
# return jsonify({"edu": edu, "avg_salary": avg_salary})
def get_l1_data1():
data = utils.get_l1_data(datatable)
edu, sum_company, avg_salary = [], [], []
for s in data:
edu.append(s[0])
sum_company.append(int(s[1]))
avg_salary.append(float(s[2]))
return jsonify({"edu": edu, "sum_company": sum_company, "avg_salary": avg_salary})
# 統(tǒng)計(jì)不同學(xué)歷下公司所招人數(shù)和平均經(jīng)驗(yàn)(折線混柱圖)
@app.route('/l2')
def get_l2_data1():
data = utils.get_l2_data(datatable)
edu, num, exp = [], [], []
# 注意sql中會(huì)存在decimal的數(shù)據(jù)類型,我們需要將其轉(zhuǎn)換為int或者float的格式
for s in data:
edu.append(s[0])
num.append(float(s[1]))
exp.append(float(s[2]))
return jsonify({'edu': edu, 'num': num, 'exp': exp})
# 統(tǒng)計(jì)不同類型公司所占的數(shù)量(餅圖)
@app.route('/r1')
def get_r1_data1():
res = []
for tup in utils.get_r1_data(datatable):
res.append({"name": tup[0], "value": int(tup[1])})
return jsonify({"data": res})
# 對(duì)獵聘網(wǎng)上的“崗位要求”文本進(jìn)行分詞后,使用jieba.analyse下的extract_tags來獲取全部文本的關(guān)鍵詞和權(quán)重,再用echarts來可視化詞云
@app.route('/r2')
def get_r2_data1():
cloud = []
text, weight = utils.get_r2_data(datatable)
for i in range(len(text)):
cloud.append({'name': text[i], 'value': weight[i]})
return jsonify({"kws": cloud})
if __name__ == '__main__':
app.run()
4 Echarts
ECharts(Enterprise Charts)是百度開源的數(shù)據(jù)可視化工具,底層依賴輕量級(jí)Canvas庫ZRender。兼容了幾乎全部常用瀏覽器的特點(diǎn),使它可廣泛用于PC客戶端和手機(jī)客戶端。ECharts能輔助開發(fā)者整合用戶數(shù)據(jù),創(chuàng)新性的完成個(gè)性化設(shè)置可視化圖表。支持折線圖(區(qū)域圖)、柱狀圖(條狀圖)、散點(diǎn)圖(氣泡圖)、K線圖、餅圖(環(huán)形圖)等,通過導(dǎo)入 js 庫在 Java Web 項(xiàng)目上運(yùn)行。
相關(guān)代碼:
# 導(dǎo)入模塊
from pyecharts import options as opts
from pyecharts.charts import Pie
#準(zhǔn)備數(shù)據(jù)
label=['民營公司','上市公司','國企','合資','外資(歐美)','外資(非歐美)','創(chuàng)業(yè)公司','事業(yè)單位']
values = [300,300,300,300,44,300,300,300]
# 自定義函數(shù)
def pie_base():
c = (
Pie()
.add("",[list(z) for z in zip(label,values)])
.set_global_opts(title_opts = opts.TitleOpts(title="公司類型分析"))
.set_series_opts(label_opts=opts.LabelOpts(formatter=":{c} n5n3t3z%")) # 值得一提的是,n5n3t3z%為百分比
)
return c
# 調(diào)用自定義函數(shù)生成render.html
pie_base().render()
5 爬蟲
簡(jiǎn)介
Scrapy是基于Twisted的爬蟲框架,它可以從各種數(shù)據(jù)源中抓取數(shù)據(jù)。其架構(gòu)清晰,模塊之間的耦合度低,擴(kuò)展性極強(qiáng),爬取效率高,可以靈活完成各種需求。能夠方便地用來處理絕大多數(shù)反爬網(wǎng)站,是目前Python中應(yīng)用最廣泛的爬蟲框架。Scrapy框架主要由五大組件組成,它們分別是調(diào)度器(Scheduler)、下載器(Downloader)、爬蟲(Spider)和實(shí)體管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。各個(gè)組件的作用如下:
-
調(diào)度器(Scheduler):說白了把它假設(shè)成為一個(gè)URL(抓取網(wǎng)頁的網(wǎng)址或者說是鏈接)的優(yōu)先隊(duì)列,由它來決定下一個(gè)要抓取的網(wǎng)址是 什么,同時(shí)去除重復(fù)的網(wǎng)址(不做無用功)。用戶可以自己的需求定制調(diào)度器。
-
下載器(Downloader):是所有組件中負(fù)擔(dān)最大的,它用于高速地下載網(wǎng)絡(luò)上的資源。Scrapy的下載器代碼不會(huì)太復(fù)雜,但效率高,主要的原因是Scrapy下載器是建立在twisted這個(gè)高效的異步模型上的(其實(shí)整個(gè)框架都在建立在這個(gè)模型上的)。
-
爬蟲(Spider):是用戶最關(guān)心的部份。用戶定制自己的爬蟲(通過定制正則表達(dá)式等語法),用于從特定的網(wǎng)頁中提取自己需要的信息,即所謂的實(shí)體(Item)。 用戶也可以從中提取出鏈接,讓Scrapy繼續(xù)抓取下一個(gè)頁面。
-
實(shí)體管道(Item Pipeline):用于處理爬蟲(spider)提取的實(shí)體。主要的功能是持久化實(shí)體、驗(yàn)證實(shí)體的有效性、清除不需要的信息。
-
Scrapy引擎(Scrapy Engine):Scrapy引擎是整個(gè)框架的核心.它用來控制調(diào)試器、下載器、爬蟲。實(shí)際上,引擎相當(dāng)于計(jì)算機(jī)的CPU,它控制著整個(gè)流程。
官網(wǎng)架構(gòu)圖文章來源:http://www.zghlxwxcb.cn/news/detail-827950.html
相關(guān)代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-827950.html
# -*- coding: utf-8 -*-
import requests
import re
import json
import time
import pandas as pd
from lxml import etree
# 為了防止被封IP,下面使用基于redis的IP代理池來獲取隨機(jī)IP,然后每次向服務(wù)器請(qǐng)求時(shí)都隨機(jī)更改我們的IP(該ip_pool搭建相對(duì)比較繁瑣,此處省略搭建細(xì)節(jié))
# 假如不想使用代理IP的話,則直接設(shè)置下方的time.sleep,并將proxies參數(shù)一并刪除
proxypool_url = 'http://127.0.0.1:5555/random'
# 定義獲取ip_pool中IP的隨機(jī)函數(shù)
def get_random_proxy():
proxy = requests.get(proxypool_url).text.strip()
proxies = {'http': 'http://' + proxy}
return proxies
# 前程無憂網(wǎng)站上用來獲取每個(gè)崗位的字段信息
def job51(datatable, job_name, page):
# 瀏覽器偽裝
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'
}
# 每個(gè)頁面提交的參數(shù),降低被封IP的風(fēng)險(xiǎn)
params = {
'lang': 'c',
'postchannel': '0000',
'workyear': '99',
'cotype': '99',
'degreefrom': '99',
'jobterm': '99',
'companysize': '99',
'ord_field': '0',
'dibiaoid': '0'
}
href, update, job, company, salary, area, company_type, company_field, attribute = [], [], [], [], [], [], [], [], []
# 使用session的好處之一便是可以儲(chǔ)存每次的cookies,注意使用session時(shí)headers一般只需放上user-agent
session = requests.Session()
# 查看是否可以完成網(wǎng)頁端的請(qǐng)求
# print(session.get('https://www.51job.com/', headers=headers, proxies=get_random_proxy()))
# 爬取每個(gè)頁面下所有數(shù)據(jù)
for i in range(1, int(page) + 1):
url = f'https://search.51job.com/list/000000,000000,0000,00,9,99,{job_name},2,{i}.html'
response = session.get(url, headers=headers, params=params, proxies=get_random_proxy())
# 使用正則表達(dá)式提取隱藏在html中的崗位數(shù)據(jù)
ss = '{' + re.findall(r'window.__SEARCH_RESULT__ = {(.*)}', response.text)[0] + '}'
# 加載成json格式,方便根據(jù)字段獲取數(shù)據(jù)
s = json.loads(ss)
data = s['engine_jds']
for info in data:
href.append(info['job_href'])
update.append(info['issuedate'])
job.append(info['job_name'])
company.append(info['company_name'])
salary.append(info['providesalary_text'])
area.append(info['workarea_text'])
company_type.append(info['companytype_text'])
company_field.append(info['companyind_text'])
attribute.append(' '.join(info['attribute_text']))
# time.sleep(np.random.randint(1, 2))
# 保存數(shù)據(jù)到DataFrame
df = pd.DataFrame(
{'崗位鏈接': href, '發(fā)布時(shí)間': update, '崗位名稱': job, '公司名稱': company, '公司類型': company_type, '公司領(lǐng)域': company_field,
'薪水': salary, '地域': area, '其他信息': attribute})
# 保存數(shù)據(jù)到csv文件中
df.to_csv(f'./data/{datatable}/51job_{datatable}.csv', encoding='gb18030', index=None)
# 獵聘網(wǎng)上用來獲取每個(gè)崗位對(duì)應(yīng)的詳細(xì)要求文本
def liepin(datatable, job_name, page):
# 瀏覽器偽裝和相關(guān)參數(shù)
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47'
}
job, salary, area, edu, exp, company, href, content = [], [], [], [], [], [], [], []
# 使用session的好處之一便是可以儲(chǔ)存每次的cookies,注意使用session時(shí)headers一般只需放上user-agent
session = requests.Session()
# print(session.get('https://www.liepin.com/zhaopin/', headers=headers, proxies = get_random_proxy()))
# 通過輸入崗位名稱和頁數(shù)來爬取對(duì)應(yīng)的網(wǎng)頁內(nèi)容
# job_name = input('請(qǐng)輸入你想要查詢的崗位:')
# page = input('請(qǐng)輸入你想要下載的頁數(shù):')
# 遍歷每一頁上的數(shù)據(jù)
for i in range(int(page)):
url = f'https://www.liepin.com/zhaopin/?key={job_name}&curPage={i}'
# time.sleep(np.random.randint(1, 2))
response = session.get(url, headers=headers, proxies = get_random_proxy())
html = etree.HTML(response.text)
# 每頁共有40條崗位信息
for j in range(1, 41):
# job.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/h3/@title')[0])
# info = html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/p[1]/@title')[0]
# ss = info.split('_')
# salary.append(ss[0])
# area.append(ss[1])
# edu.append(ss[2])
# exp.append(ss[-1])
# company.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[2]/p[1]/a/text()')[0])
href.append(html.xpath(f'//ul[@class="sojob-list"]/li[{j}]/div/div[1]/h3/a/@href')[0])
# 遍歷每一個(gè)崗位的數(shù)據(jù)
for job_href in href:
# time.sleep(np.random.randint(1, 2))
# 發(fā)現(xiàn)有些崗位詳細(xì)鏈接地址不全,需要對(duì)缺失部分進(jìn)行補(bǔ)齊
if 'https' not in job_href:
job_href = 'https://www.liepin.com' + job_href
response = session.get(job_href, headers=headers, proxies = get_random_proxy())
html = etree.HTML(response.text)
content.append(html.xpath('//section[@class="job-intro-container"]/dl[1]//text()')[3])
# 保存數(shù)據(jù)
# df = pd.DataFrame({'崗位名稱': job, '公司': company, '薪水': salary, '地域': area, '學(xué)歷': edu, '工作經(jīng)驗(yàn)': exp, '崗位要求': content})
df = pd.DataFrame({'崗位要求': content})
df.to_csv(f'./data/{datatable}/liepin_{datatable}.csv', encoding='gb18030', index=None)
6 最后
到了這里,關(guān)于大數(shù)據(jù)畢設(shè)分享 招聘網(wǎng)站爬取與大數(shù)據(jù)分析可視化 - python 分析 可視化 flask的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!