🌟欢迎来到 我的博客 —— 探索技术的无限可能!
前言
-
本项目综合了基本数据分析的流程,包括数据采集(爬虫)、数据清洗、数据存储、数据前后端可视化等
-
推荐阅读顺序为:数据采集——>数据清洗——>数据库存储——>基于Flask的前后端交互,有问题的话可以留言,有时间我会解疑~
-
感谢阅读、点赞和关注
开发环境
- 系统:Window 10 家庭中文版。
- 语言:Python(3.9)、MySQL。
- Python所需的库:pymysql、pandas、numpy、time、datetime、requests、etree、jieba、re、json、decimal、flask(没有的话pip安装一下就好)。
- 编辑器:jupyter notebook、Pycharm、SQLyog。
(如果下面代码在jupyter中运行不完全,建议直接使用Pycharm中运行)
文件说明
本项目下面有五个.py的文件,下面分别阐述各个文件所对应的功能:
- 1. data_collection:分别从前程无忧网站和猎聘网上以关键词job_name爬取相关数据。其中,前程无忧爬取的数据主要用来进行相关图表的绘制;而猎聘网上主要为岗位要求文本数据,这部分进行词云的可视化展示。
- 2. data_clean:对爬取到的数据进行清洗,包括去重去缺失值、变量重编码、特征字段创造、文本分词等。
- 3. data_store:将清洗后的数据全部储存到MySQL中,其中对文本数据使用jieba.analyse下的extract_tags来获取文本中的关键词和权重大小,方便绘制词云。
- 4. utils:大多为app调用MySQL数据库中的工具类函数;同时,里面也有引用data_collection、data_clean、data_store等函数,我们也主要使用该工具类进行岗位数据的爬取、清洗和存储。
- 5. app:使用Python一个小型轻量的Flask框架来进行Web可视化系统的搭建,在static中有css和js文件,js中大多为百度开源的ECharts,再通过自定义controller.js来使用ajax调用flask已设定好的路由,将数据异步刷新到templates下的main.html中。
- 6. 如何运行:先运行utils,提前进行数据采集、数据清洗、数据存储操作,之后更改app修改好datatable和job_name,这部分信息务必与utils中输入的保持一致(因为发现app一运行的话就会直接给出网页,所以没法在控制台上同步将变量赋值过去*_*)。
- 7. 温馨提示:由于我在数据采集部分使用了一个用redis搭建的代理IP池,所以一开始运行的话需要将里面的proxies删掉,使用time.sleep即可(使用代理池能防止被封IP,同时可以更快爬取数据,实现可视化操作)。
技术栈
- Python爬虫:(requests和xpath)
- 数据清洗:详细了解项目中数据预处理的步骤,包括去重去缺失值、变量重编码、特征字段创造和文本数据预处理 (pandas、numpy)
- 数据库知识:select、insert等操作,(增删查改&pymysql) 。
- 前后端知识:(HTML、JQuery、JavaScript、Ajax)。
- Flask知识:一个轻量级的Web框架,利用Python实现前后端交互。(Flask)
备注:运行flask得到web链接时,在修改代码后想同步到web前端时一般需要将flask的debug功能打开,而且刷新时建议直接使用ctrl+F5强制刷新,因为浏览器在接收到本地主机部署的数据包后会存放在缓存中,方便下次加载时能快速呈现出来。
系统改进:之后可以尝试部署到网络中,同时,可以对自定义词典进行改进,增加更多岗位的分词信息;另外,也可以设置定时定点爬取数据,增加登录注册页面,让多台主机同时进行工作,实现数据的快速传递和更新。
data_collection
import requests
import re
import json
import time
import pandas as pd
from lxml import etree
proxypool_url = 'http://127.0.0.1:5555/random'
# 定义获取ip_pool中IP的随机函数
def get_random_proxy():
...
return proxies
# 获取每个岗位的字段信息
def job51(datatable, job_name, page):
# 浏览器伪装
...
# 爬取每个页面下所有数据
...
# 保存数据到DataFrame
...
# 保存数据到csv文件中
df.to_csv('xxx.csv')
# 获取每个岗位对应的详细要求文本
def liepin(datatable, job_name, page):
# 浏览器伪装和相关参数
...
# 遍历每一页上的数据
...
# 遍历每一个岗位的数据
...
# 保存数据
df.to_csv('xxx.csv')
data_clean
import jieba
import re
import numpy as np
import pandas as pd
def job51_clean(datatable):
# 读取数据
data = pd.read_csv( )
# 去除重复值
data.drop_duplicates(inplace = True)
# 索引重置
data.index = range(data.shape[0])
# 接下来,我们对薪水这一列的表示方法都统一规范为数字(千/月)
...
# 对地域这一列进行字段重编码
data['地区'] = data['地域'].apply(lambda x: x.split('-')[0])
# 进行省份的划分
...
# 对31个省市进行遍历
...
# 生成人数这一列
...
# 生成学历这一列
...
# 生成经验这一列
...
# 去除无意义的字段
...
# 自定义函数来实现分词和去除停用词操作
def m_cut(tmpstr, stoplist):
return [w.strip() for w in jieba.lcut(tmpstr) if w not in stoplist and len(w) > 1]
def liepin_clean(datatable):
#同def job51_clean(datatable)
data_store
import pandas as pd
import numpy as np
import jieba.analyse as aa
import pymysql
# 连接数据库,需要先在数据库中定义好一张表
def get_con():
con= pymysql.connect(host='localhost', user='用户名', password='密码', database='数据库名', charset='utf8')
cursor = con.cursor()
return con, cursor
# 关闭数据库
def con_close(con, cursor):
if cursor:
cursor.close()
if con:
con.close()
def job51_store(datatable):
# 将每行数据都转变为tuple数据类型,然后遍历把每条数据都添加到sql中,有多次存数因而不使用上方函数
...
def liepin_store(datatable):
# 下面把词云部分数据也存放进数据库中
# 对文本进行去重操作
...
utils
import numpy as np
import pymysql
import time
import datetime
import os
import data_collection
import data_clean
import data_store
# 连接数据库,需要先在数据库中定义好一张表
def get_con():
connect = pymysql.connect(host='localhost', user='用户名', password='密码', database='数据库名', charset='utf8')
cursor = connect.cursor()
return connect, cursor
# 关闭数据库
def con_close(connect, cursor):
if cursor:
cursor.close()
if connect:
connect.close()
# 定义函数
def query(sql):
...
def get_c1_data(datatable):
...
def get_c2_data(datatable):
...
def get_l1_data(datatable):
...
def get_l2_data(datatable):
...
def get_r1_data(datatable):
...
def get_r2_data(datatable):
...
def create_datatable(datatable):
...
return '数据表已创建完毕!'
def exist_table(datatable):
sql = f"show tables"
res = query(sql)
for i in range(len(res)):
if datatable in res[i][0]:
return '数据库存在此表!'
i = input('数据库中不存在此表,是否需要重新创建?是为1,否为0:')
if i == '1':
path = './data/'
os.mkdir(path + datatable)
return create_datatable(datatable)
else:
return '已安全退出!'
if __name__ == '__main__':
datatable = input('请输入你想保存的数据表单名称(英文名称):')
ss = exist_table(datatable)
k = input('你是否想要对数据进行更新或插入数据?是为1,否为0:')
if ss == '数据表已创建完毕' or k == '1':
start_time = datetime.datetime.now()
print(f'系统启动时间为:{start_time}')
print('*' * 50)
print('本系统将依次进行数据采集、数据清洗、数据存储操作,下面先进行数据的采集操作')
# 通过输入岗位名称和页数来爬取对应的网页内容
job_name = input('请输入你想要查询的岗位(中文名称):')
page = input('请输入你想要下载的页数(建议10-100页之间):')
data_collection.job51(datatable, job_name, page)
# 默认在猎聘网站上只爬取8页,一页有40个招聘岗位,而且大多数为文本,这部分信息不要求太多
data_collection.liepin(datatable, job_name, '8')
print('*' * 50)
print('数据采集完毕,下面进行数据的清洗操作')
data_clean.job51_clean(datatable)
data_clean.liepin_clean(datatable)
print('*' * 50)
print('数据清洗完毕,下面进行数据的存储操作~')
data_store.job51_store(datatable)
data_store.liepin_store(datatable)
print('*' * 50)
print('数据存储完毕,接下来需要您修改app.py中的datatable和job_name,之后运行该文件进行该招聘岗位的可视化展示~ლ(╹◡╹ლ)')
end_time = datetime.datetime.now()
print(f'进行数据采集、数据清洗和数据储存共耗费:{end_time - start_time}')
app
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__)
datatable = 'data_mining'
job_name = '数据挖掘'
# 路由解析,每映射到一个路由就调用一个函数
@app.route('/')
def index():
return render_template("main.html")
@app.route('/title')
def get_title1():
return job_name
# 获取系统当前时间,每隔1s刷新一次
@app.route('/time')
def get_time1():
return utils.get_time()
# 对数据库中的数据进行计数、薪资取平均值、省份和学历取众数
@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]})
# 对省份进行分组,之后统计其个数,使用jsonify来将数据传输给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})
# 统计每个学历下公司数量和平均薪资(上下坐标折线图)
@app.route('/l1')
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})
# 统计不同学历下公司所招人数和平均经验(折线混柱图)
@app.route('/l2')
def get_l2_data1():
data = utils.get_l2_data(datatable)
edu, num, exp = [], [], []
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})
# 统计不同类型公司所占的数量(饼图)
@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})
# 使用jieba.analyse下的extract_tags来获取全部文本的关键词和权重,再用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()