智联招聘信息爬虫与分析程序代码ZXQMQZQ-2025-9-14
·
import tkinter as tk
from tkinter import ttk, messagebox
import requests
from lxml import etree
import time
import pymysql
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import seaborn as sns
from wordcloud import WordCloud
import jieba
from PIL import Image, ImageTk
import threading
import re
import matplotlib.font_manager as fm
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 数据库配置
DB_CONFIG = {
'host': 'localhost',
'user': 'root',
'password': 'ye17876586815',
'database': 'job_data',
'charset': 'utf8mb4'
}
# 创建数据库和表
def init_database():
try:
# 连接MySQL服务器
connection = pymysql.connect(
host=DB_CONFIG['host'],
user=DB_CONFIG['user'],
password=DB_CONFIG['password'],
charset='utf8mb4'
)
with connection.cursor() as cursor:
# 创建数据库
cursor.execute(
f"CREATE DATABASE IF NOT EXISTS {DB_CONFIG['database']} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
cursor.execute(f"USE {DB_CONFIG['database']}")
# 创建招聘信息表
create_table_sql = """
CREATE TABLE IF NOT EXISTS job_info (
id INT AUTO_INCREMENT PRIMARY KEY,
job_title VARCHAR(255) NOT NULL,
industry VARCHAR(100),
company VARCHAR(255),
location VARCHAR(100),
salary VARCHAR(50),
experience VARCHAR(50),
education VARCHAR(50),
recruit_count VARCHAR(50),
publish_time DATETIME,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
cursor.execute(create_table_sql)
connection.commit()
print("数据库初始化成功")
except Exception as e:
print(f"数据库初始化失败: {e}")
finally:
if connection:
connection.close()
# 爬虫类
class JobSpider:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0",
"Referer": "https://www.zhaopin.com/"
}
self.base_url = "https://www.zhaopin.com/sou/jl765/kw01O00U80EG06G03F01N0/p{}?srccode=401801"
def scrape_page(self, page_num):
"""爬取指定页码的招聘信息"""
url = self.base_url.format(page_num)
try:
time.sleep(1 + (page_num % 3) * 0.5) # 1-2秒随机延迟
response = requests.get(url, headers=self.headers)
response.encoding = 'utf-8'
if response.status_code != 200:
return None
html_data = etree.HTML(response.text)
job_blocks = html_data.xpath('//*[@id="positionList-hook"]/div/div[1]/div[*]')
if not job_blocks:
return None
jobs = []
for block in job_blocks:
try:
# 提取岗位名称
job_title = block.xpath('.//div[1]/div[1]/div[1]/a/text()')
job_title = job_title[0].strip() if job_title else ''
# 提取薪资要求
salary = block.xpath('.//div[1]/div[1]/div[1]/p/text()')
salary = salary[0].strip() if salary else ''
# 提取学历要求
education = block.xpath('.//div[1]/div[1]/div[3]/div[3]/text()')
education = education[0].strip() if education else ''
# 提取工作经验
experience = block.xpath('.//div[1]/div[1]/div[3]/div[2]/text()')
experience = experience[0].strip() if experience else ''
# 提取公司名称
company = block.xpath('.//div[1]/div[2]/div[1]/a/text()')
company = company[0].strip() if company else ''
# 提取行业
industry = block.xpath('.//div[1]/div[2]/div[2]/div[3]/text()')
industry = industry[0].strip() if industry else ''
# 提取地区
location = block.xpath('.//div[1]/div[1]/div[3]/div[1]/span/text()')
location = location[0].strip() if location else ''
# 提取发布时间 (这里需要根据实际网页结构调整)
publish_time = "2022-03-05 00:00:00" # 示例数据
jobs.append([job_title, industry, company, location, salary,
experience, education, "招若干人", publish_time])
except Exception as e:
print(f"解析单个岗位信息时出错: {e}")
continue
return jobs
except Exception as e:
print(f"爬取第{page_num}页时发生错误: {e}")
return []
def save_to_database(self, jobs):
"""将招聘信息保存到数据库"""
try:
connection = pymysql.connect(**DB_CONFIG)
with connection.cursor() as cursor:
sql = """
INSERT INTO job_info
(job_title, industry, company, location, salary, experience, education, recruit_count, publish_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
cursor.executemany(sql, jobs)
connection.commit()
return True
except Exception as e:
print(f"保存数据到数据库失败: {e}")
return False
finally:
if connection:
connection.close()
# 数据分析和可视化类
class DataAnalyzer:
def __init__(self):
self.connection = pymysql.connect(**DB_CONFIG)
def get_job_data(self):
"""从数据库获取招聘数据"""
try:
query = "SELECT job_title, industry, company, location, salary, experience, education FROM job_info"
df = pd.read_sql(query, self.connection)
return df
except Exception as e:
print(f"获取数据失败: {e}")
return pd.DataFrame()
def analyze_salary_by_city(self, df):
"""分析各城市平均薪资"""
# 提取薪资数字
def extract_salary(salary_str):
if not salary_str or pd.isna(salary_str) or salary_str == '':
return None
# 处理不同薪资格式
try:
if '千/月' in salary_str:
numbers = re.findall(r'(\d+\.?\d*)', salary_str)
if len(numbers) >= 2:
return (float(numbers[0]) + float(numbers[1])) / 2
elif len(numbers) == 1:
return float(numbers[0])
elif '万/月' in salary_str:
numbers = re.findall(r'(\d+\.?\d*)', salary_str)
if len(numbers) >= 2:
return (float(numbers[0]) + float(numbers[1])) / 2 * 10
elif len(numbers) == 1:
return float(numbers[0]) * 10
elif '万/年' in salary_str:
numbers = re.findall(r'(\d+\.?\d*)', salary_str)
if len(numbers) >= 2:
return (float(numbers[0]) + float(numbers[1])) / 2 / 12 * 10
elif len(numbers) == 1:
return float(numbers[0]) / 12 * 10
elif '元/天' in salary_str:
numbers = re.findall(r'(\d+\.?\d*)', salary_str)
if numbers:
return float(numbers[0]) * 30 / 1000 # 按30天计算,转换为千/月
except:
return None
return None
df['avg_salary'] = df['salary'].apply(extract_salary)
df = df.dropna(subset=['avg_salary'])
# 按城市分组计算平均薪资
if not df.empty:
salary_by_city = df.groupby('location')['avg_salary'].mean().sort_values(ascending=False)
return salary_by_city
else:
return pd.Series()
def analyze_education_requirements(self, df):
"""分析学历要求分布"""
education_dist = df['education'].value_counts()
return education_dist
def analyze_experience_requirements(self, df):
"""分析工作经验要求分布"""
experience_dist = df['experience'].value_counts()
return experience_dist
def analyze_industry_distribution(self, df):
"""分析行业分布"""
industry_dist = df['industry'].value_counts().head(10) # 取前10个行业
return industry_dist
def generate_word_cloud(self, df):
"""生成岗位词云"""
text = ' '.join(df['job_title'].dropna().tolist())
if not text:
return None
# 使用jieba进行中文分词
words = ' '.join(jieba.cut(text))
wordcloud = WordCloud(
font_path='simhei.ttf',
width=800,
height=400,
background_color='white'
).generate(words)
return wordcloud
# GUI应用程序
class JobAnalysisApp:
def __init__(self, root):
self.root = root
self.root.title("智联招聘信息爬取与数据可视化分析系统")
self.root.geometry("1200x700")
# 初始化组件
self.spider = JobSpider()
self.analyzer = DataAnalyzer()
# 创建左侧导航栏
self.create_sidebar()
# 创建主内容区域
self.main_frame = ttk.Frame(self.root)
self.main_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)
# 显示初始页面
self.show_data_display()
def create_sidebar(self):
"""创建左侧导航栏"""
sidebar = ttk.Frame(self.root, width=200)
sidebar.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)
sidebar.pack_propagate(False)
# 标题
title_label = ttk.Label(sidebar, text="智联招聘分析系统", font=("SimHei", 14, "bold"))
title_label.pack(pady=20)
# 导航按钮
nav_buttons = [
("爬取信息展示", self.show_data_display),
("岗位行业分析", self.show_industry_analysis),
("岗位要求分析", self.show_requirements_analysis),
("岗位词云分析", self.show_word_cloud),
("薪资分析", self.show_salary_analysis),
("地区分析", self.show_region_analysis),
("开始爬取数据", self.start_crawling)
]
for text, command in nav_buttons:
btn = ttk.Button(sidebar, text=text, command=command, width=20)
btn.pack(pady=5)
def clear_main_frame(self):
"""清除主内容区域"""
for widget in self.main_frame.winfo_children():
widget.destroy()
def show_data_display(self):
"""显示数据展示页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="爬取的招聘信息", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 创建表格框架
table_frame = ttk.Frame(self.main_frame)
table_frame.pack(fill=tk.BOTH, expand=True)
# 创建滚动条
scrollbar = ttk.Scrollbar(table_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 创建表格
columns = ("岗位名称", "所属行业", "公司", "地点", "薪资", "工作经验", "学历要求", "人数", "发布时间")
tree = ttk.Treeview(table_frame, columns=columns, show="headings", yscrollcommand=scrollbar.set)
# 设置列标题
for col in columns:
tree.heading(col, text=col)
tree.column(col, width=100)
# 从数据库获取数据
try:
connection = pymysql.connect(**DB_CONFIG)
with connection.cursor() as cursor:
cursor.execute(
"SELECT job_title, industry, company, location, salary, experience, education, recruit_count, publish_time FROM job_info LIMIT 100")
rows = cursor.fetchall()
for row in rows:
tree.insert("", tk.END, values=row)
except Exception as e:
print(f"获取数据失败: {e}")
ttk.Label(self.main_frame, text="获取数据失败或暂无数据").pack(pady=20)
finally:
if connection:
connection.close()
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=tree.yview)
def show_industry_analysis(self):
"""显示行业分析页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="岗位行业分析", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 获取数据并分析
df = self.analyzer.get_job_data()
if df.empty:
ttk.Label(self.main_frame, text="暂无数据,请先爬取数据").pack(pady=20)
return
industry_dist = self.analyzer.analyze_industry_distribution(df)
if industry_dist.empty:
ttk.Label(self.main_frame, text="无行业数据可供分析").pack(pady=20)
return
# 创建图表
fig, ax = plt.subplots(figsize=(10, 6))
industry_dist.plot(kind='bar', ax=ax, color='skyblue')
ax.set_title('行业分布TOP10')
ax.set_xlabel('行业')
ax.set_ylabel('岗位数量')
plt.xticks(rotation=45)
plt.tight_layout()
# 在Tkinter中显示图表
canvas = FigureCanvasTkAgg(fig, self.main_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def show_requirements_analysis(self):
"""显示岗位要求分析页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="岗位要求分析", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 获取数据并分析
df = self.analyzer.get_job_data()
if df.empty:
ttk.Label(self.main_frame, text="暂无数据,请先爬取数据").pack(pady=20)
return
# 创建子图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 学历要求分析
education_dist = self.analyzer.analyze_education_requirements(df)
if not education_dist.empty:
education_dist.plot(kind='pie', autopct='%1.1f%%', ax=ax1)
ax1.set_title('学历要求分布')
else:
ax1.text(0.5, 0.5, '无学历要求数据', ha='center', va='center')
ax1.set_title('学历要求分布')
# 工作经验要求分析
experience_dist = self.analyzer.analyze_experience_requirements(df)
if not experience_dist.empty:
experience_dist.plot(kind='bar', ax=ax2, color='lightcoral')
ax2.set_title('工作经验要求分布')
ax2.set_xlabel('工作经验')
ax2.set_ylabel('岗位数量')
plt.xticks(rotation=45)
else:
ax2.text(0.5, 0.5, '无工作经验数据', ha='center', va='center')
ax2.set_title('工作经验要求分布')
plt.tight_layout()
# 在Tkinter中显示图表
canvas = FigureCanvasTkAgg(fig, self.main_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def show_word_cloud(self):
"""显示词云分析页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="岗位词云分析", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 获取数据并生成词云
df = self.analyzer.get_job_data()
if df.empty:
ttk.Label(self.main_frame, text="暂无数据,请先爬取数据").pack(pady=20)
return
wordcloud = self.analyzer.generate_word_cloud(df)
if wordcloud:
# 显示词云
fig, ax = plt.subplots(figsize=(10, 6))
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis('off')
ax.set_title('岗位关键词词云')
plt.tight_layout()
# 在Tkinter中显示图表
canvas = FigureCanvasTkAgg(fig, self.main_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
else:
ttk.Label(self.main_frame, text="无法生成词云,数据不足").pack(pady=20)
def show_salary_analysis(self):
"""显示薪资分析页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="薪资分析", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 获取数据并分析
df = self.analyzer.get_job_data()
if df.empty:
ttk.Label(self.main_frame, text="暂无数据,请先爬取数据").pack(pady=20)
return
salary_by_city = self.analyzer.analyze_salary_by_city(df)
if salary_by_city.empty:
ttk.Label(self.main_frame, text="无有效的薪资数据可供分析").pack(pady=20)
return
# 创建图表
fig, ax = plt.subplots(figsize=(10, 6))
salary_by_city.plot(kind='bar', ax=ax, color='lightgreen')
ax.set_title('各城市平均薪资分布')
ax.set_xlabel('城市')
ax.set_ylabel('平均薪资(千/月)')
plt.xticks(rotation=45)
plt.tight_layout()
# 在Tkinter中显示图表
canvas = FigureCanvasTkAgg(fig, self.main_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def show_region_analysis(self):
"""显示地区分析页面"""
self.clear_main_frame()
title_label = ttk.Label(self.main_frame, text="地区分析", font=("SimHei", 16, "bold"))
title_label.pack(pady=10)
# 获取数据并分析
df = self.analyzer.get_job_data()
if df.empty:
ttk.Label(self.main_frame, text="暂无数据,请先爬取数据").pack(pady=20)
return
# 分析地区分布
region_dist = df['location'].value_counts().head(10)
if region_dist.empty:
ttk.Label(self.main_frame, text="无地区数据可供分析").pack(pady=20)
return
# 创建图表
fig, ax = plt.subplots(figsize=(10, 6))
region_dist.plot(kind='bar', ax=ax, color='orange')
ax.set_title('地区分布TOP10')
ax.set_xlabel('地区')
ax.set_ylabel('岗位数量')
plt.xticks(rotation=45)
plt.tight_layout()
# 在Tkinter中显示图表
canvas = FigureCanvasTkAgg(fig, self.main_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def start_crawling(self):
"""开始爬取数据"""
# 在新线程中执行爬取任务,避免阻塞UI
def crawl_thread():
progress_window = tk.Toplevel(self.root)
progress_window.title("爬取进度")
progress_window.geometry("300x100")
progress_label = ttk.Label(progress_window, text="正在爬取数据,请稍候...")
progress_label.pack(pady=10)
progress_bar = ttk.Progressbar(progress_window, mode='indeterminate')
progress_bar.pack(fill=tk.X, padx=20, pady=10)
progress_bar.start()
total_pages = 5 # 爬取5页数据
all_jobs = []
for page in range(1, total_pages + 1):
jobs = self.spider.scrape_page(page)
if jobs is None:
break
all_jobs.extend(jobs)
if all_jobs:
success = self.spider.save_to_database(all_jobs)
if success:
messagebox.showinfo("成功", f"成功爬取并保存{len(all_jobs)}条数据")
else:
messagebox.showerror("错误", "保存数据到数据库失败")
else:
messagebox.showwarning("警告", "未爬取到任何数据")
progress_window.destroy()
# 启动爬取线程
threading.Thread(target=crawl_thread).start()
# 主程序入口
if __name__ == "__main__":
# 初始化数据库
init_database()
# 创建主窗口
root = tk.Tk()
app = JobAnalysisApp(root)
root.mainloop()
更多推荐
所有评论(0)