简介:这份资源是Django+Python+Echarts招聘数据可视化分析项目的完整源码,面向计算机相关专业学生、毕业设计或期末大作业开发者,帮助快速搭建一套可运行的数据分析展示系统。项目以Django为后端框架,Python负责数据采集与处理,Echarts完成前端图表渲染,覆盖招聘数据清洗、统计与可视化呈现等典型环节,适合作为课程设计参考或二次开发基础。压缩包共537个文件,约32.32MB,包含109个html页面、102个js脚本、40个py源码、24个css样式及75个gif素材,另有dll、xml、exe等运行依赖与配置项,结构完整、模块划分清晰。目前已有396人学习下载,且该作品以96分高分通过期末考核。下载后简单部署即可运行,读者可据此掌握Django项目组织方式、Echarts图表配置与招聘数据分析思路,节省从零搭建的时间成本。
1. 招聘数据可视化大作业:从一份 zip 到能跑起来的分析系统
每年期末季,总有一批同学被"招聘数据可视化分析"这个大作业卡住。题目本身不复杂——爬一批招聘岗位数据,做几个图表,写个分析结论。但真动手时问题就来了:数据从哪来、用什么存、图表怎么跟前端对接、Django 里怎么组织这些代码。很多人最后交上去的东西,要么是几张静态截图拼的 PPT,要么是跑不起来的半成品。
这份以 Django + Python + Echarts 为核心的招聘数据可视化分析源码,解决的正是这条链路:Python 负责数据采集和清洗,Django 提供 Web 框架和数据接口,Echarts 在前端把薪资分布、学历要求、城市热度这些维度画出来。它适合两类人——赶期末大作业的学生,以及想用一个完整小项目练手 Django 全栈的入门者。下面我按实际搭建顺序,把每个环节拆开讲清楚。
2. 环境搭建与 Django 项目骨架:别在第一步就翻车
2.1 Python 与依赖安装的稳妥路径
很多人第一步就卡在 python 安装上。Windows 用户去 python 官网下载安装包时,记得勾选"Add Python to PATH",否则后面命令行里敲 python 会提示找不到命令。装完之后验证一下:
python --version pip --version如果 pip 版本太旧,先升级再装依赖,能避免不少包解析失败的问题:
python -m pip install --upgrade pip接下来装 Django 和数据处理常用的库。招聘数据可视化这个场景,核心依赖就几个:Django 本身、pandas 做数据清洗、requests 或 scrapy 做采集、pymysql 或 mysqlclient 连数据库。
pip install django pandas requests pymysql这里有个血泪经验:mysqlclient 在 Windows 上编译经常报错,新手直接用 pymysql 更省事,在 Django 的__init__.py里加两行伪装成 MySQLdb 就行:
# 项目 __init__.py import pymysql pymysql.install_as_MySQLdb()参数说明:install_as_MySQLdb()的作用是让 Django 以为你在用 MySQLdb,实际底层走的是纯 Python 的 pymysql,省去编译环节。如果你用 SQLite 做开发库,这一步可以跳过,但期末作业一般要求 MySQL,提前配好省得后期迁移。
2.2 创建 Django 项目与 app 的标准动作
环境好了之后,创建项目和 app。django 创建 app 这个操作新手容易搞混目录层级,记住:先建项目,再在项目根目录下建 app。
django-admin startproject recruitment_viz cd recruitment_viz python manage.py startapp jobs建完之后目录结构是这样的:
recruitment_viz/ ├── manage.py ├── recruitment_viz/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── jobs/ ├── models.py ├── views.py └── ...接着在settings.py里注册 app,配置数据库连接和模板路径:
# settings.py 关键片段 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'jobs', # 注册我们的 app ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'recruitment_db', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', } } TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # 模板目录 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]参数说明:NAME是数据库名,需要提前在 MySQL 里CREATE DATABASE recruitment_db。DIRS指向项目根目录下的 templates 文件夹,Echarts 的 HTML 模板放这里。APP_DIRS: True表示也会去每个 app 下的 templates 目录找模板,两种方式都行,建议统一放项目级 templates,方便管理。
配置完执行迁移,把 Django 自带的表建起来:
python manage.py migrate python manage.py createsuperusercreatesuperuser是建后台管理员账号,后面往数据库里导数据、检查数据对不对,都靠这个后台。到这里骨架就搭好了,能跑起来看到 Django 的欢迎页,说明环境没问题。
3. 数据采集与模型设计:招聘数据怎么存才合理
3.1 招聘数据字段的取舍
做招聘数据可视化,第一步要想清楚存哪些字段。常见做法是采集岗位名称、公司名称、薪资范围、工作城市、学历要求、经验要求、公司规模、岗位描述这几项。字段不是越多越好,采集字段太多,清洗成本翻倍,而且很多字段最后根本用不上。
我一般会先定一个最小可用字段集,对应 Django 的 model:
# jobs/models.py from django.db import models class JobInfo(models.Model): job_name = models.CharField(max_length=200, verbose_name='岗位名称') company = models.CharField(max_length=200, verbose_name='公司名称') salary = models.CharField(max_length=100, verbose_name='薪资范围') city = models.CharField(max_length=50, verbose_name='工作城市') education = models.CharField(max_length=50, verbose_name='学历要求') experience = models.CharField(max_length=50, verbose_name='经验要求') company_size = models.CharField(max_length=50, verbose_name='公司规模') publish_date = models.DateField(null=True, verbose_name='发布时间') class Meta: db_table = 'job_info' verbose_name = '招聘信息' def __str__(self): return self.job_name参数说明:salary用 CharField 而不是数值类型,因为招聘网站上的薪资经常是"15-25K·13薪"这种格式,直接存字符串,后面在 Python 里解析成最低和最高两个数值。publish_date允许为空,有些采集源拿不到发布时间。db_table指定表名,方便和采集脚本对接。
3.2 采集脚本与数据入库
采集部分,如果只是期末作业,用 requests + BeautifulSoup 抓几个页面的数据就够了,不需要上 scrapy 那么重。核心逻辑是:请求列表页拿到详情链接,再逐个请求详情页解析字段,最后批量写入数据库。
# scripts/fetch_jobs.py import os import django import requests from bs4 import BeautifulSoup os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'recruitment_viz.settings') django.setup() from jobs.models import JobInfo def parse_salary(salary_text): """把 '15-25K' 解析成 (15, 25),解析失败返回 (0, 0)""" try: salary_text = salary_text.replace('K', '').replace('k', '') low, high = salary_text.split('-') return float(low), float(high) except Exception: return 0, 0 def save_job(item): """单条数据入库,用 get_or_create 避免重复""" JobInfo.objects.get_or_create( job_name=item['job_name'], company=item['company'], defaults={ 'salary': item['salary'], 'city': item['city'], 'education': item['education'], 'experience': item['experience'], 'company_size': item['company_size'], } ) if __name__ == '__main__': # 这里替换成实际的目标页面 url = 'https://example.com/jobs?page=1' headers = {'User-Agent': 'Mozilla/5.0'} resp = requests.get(url, headers=headers, timeout=10) soup = BeautifulSoup(resp.text, 'html.parser') # 解析逻辑根据实际页面结构调整 for card in soup.select('.job-card'): item = { 'job_name': card.select_one('.job-title').text.strip(), 'company': card.select_one('.company-name').text.strip(), 'salary': card.select_one('.salary').text.strip(), 'city': card.select_one('.city').text.strip(), 'education': card.select_one('.education').text.strip(), 'experience': card.select_one('.experience').text.strip(), 'company_size': card.select_one('.company-size').text.strip(), } save_job(item) print('采集完成')逻辑说明:脚本开头设置DJANGO_SETTINGS_MODULE并调用django.setup(),这样脱离 Django 命令行也能用 ORM。parse_salary把薪资字符串转成数值,后面画薪资分布图要用。get_or_create用岗位名 + 公司名做去重键,重复采集不会产生脏数据。
参数说明:timeout=10防止请求卡死,headers里的 User-Agent 必须带,否则很多站点直接返回 403。实际采集时记得加time.sleep(1)控制频率,别把人家服务器打挂了。
数据入库后,去 Django admin 后台注册一下 model,方便查看:
# jobs/admin.py from django.contrib import admin from .models import JobInfo @admin.register(JobInfo) class JobInfoAdmin(admin.ModelAdmin): list_display = ('job_name', 'company', 'salary', 'city', 'education') search_fields = ('job_name', 'company') list_filter = ('city', 'education')注册完访问/admin,用之前创建的超级用户登录,就能看到采集进来的数据了。这一步很关键,数据不对后面图表全是错的,先在这里肉眼检查一遍。
4. 数据接口与 Echarts 对接:图表数据从哪来
4.1 用 Django 视图输出 JSON 接口
Echarts 需要的是 JSON 格式的数据,所以 Django 这边要写几个接口,把数据库里的数据聚合好返回。常见做法是用JsonResponse,配合 ORM 的values和annotate做分组统计。
# jobs/views.py from django.http import JsonResponse from django.db.models import Count, Avg from .models import JobInfo def city_distribution(request): """城市岗位数量分布,用于柱状图""" data = (JobInfo.objects .values('city') .annotate(count=Count('id')) .order_by('-count')[:10]) result = { 'cities': [item['city'] for item in data], 'counts': [item['count'] for item in data], } return JsonResponse(result) def education_distribution(request): """学历要求分布,用于饼图""" data = (JobInfo.objects .values('education') .annotate(count=Count('id')) .order_by('-count')) result = [{'name': item['education'], 'value': item['count']} for item in data] return JsonResponse(result, safe=False) def salary_by_experience(request): """不同经验要求的平均薪资,用于折线图""" data = (JobInfo.objects .values('experience') .annotate(avg_salary=Avg('salary_numeric')) .order_by('experience')) result = { 'experiences': [item['experience'] for item in data], 'salaries': [round(item['avg_salary'], 1) if item['avg_salary'] else 0 for item in data], } return JsonResponse(result)逻辑说明:values('city').annotate(count=Count('id'))等价于 SQL 的GROUP BY city,按城市分组统计岗位数。order_by('-count')[:10]取前 10 个城市,避免图表太挤。education_distribution返回的格式是 Echarts 饼图直接能用的[{name, value}]结构,所以safe=False允许返回列表。
参数说明:salary_numeric是我在 model 里额外加的一个数值字段,采集时用parse_salary算出来存进去,方便做平均薪资统计。如果不想加字段,也可以在 Python 里现算,但数据量大时性能差。
4.2 Echarts 前端渲染与接口联调
接口有了,前端页面用 Echarts 渲染。先在模板里引入 Echarts,注意用国内 CDN 或者把 echarts.min.js 下载到 static 目录,别依赖不稳定的外链。
<!-- templates/city_chart.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>城市岗位分布</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> </head> <body> <div id="cityChart" style="width: 900px; height: 500px;"></div> <script> var chart = echarts.init(document.getElementById('cityChart')); fetch('/api/city_distribution/') .then(res => res.json()) .then(data => { chart.setOption({ title: { text: '热门城市岗位数量 TOP10' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.cities, axisLabel: { rotate: 30 } }, yAxis: { type: 'value', name: '岗位数' }, series: [{ type: 'bar', data: data.counts, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#83bff6' }, { offset: 1, color: '#188df0' } ]) } }] }); }); </script> </body> </html>逻辑说明:echarts.init绑定 DOM 容器,fetch请求 Django 接口拿到数据后setOption渲染。柱状图用LinearGradient做渐变色,这是 echarts 柱状图设置渐变色最常见的写法,比纯色好看不少。axisLabel.rotate: 30解决城市名太长重叠的问题,这是 python 画图横坐标太密集的同类问题,Echarts 里靠旋转角度解决。
参数说明:trigger: 'axis'表示鼠标悬停显示整条轴的数据,柱状图常用。type: 'category'是类目轴,对应城市名;type: 'value'是数值轴,对应岗位数。饼图的话把 series 的 type 改成'pie',数据直接用[{name, value}]格式,中间的字通过title或label配置。
路由配置别忘了:
# recruitment_viz/urls.py from django.contrib import admin from django.urls import path from jobs import views urlpatterns = [ path('admin/', admin.site.urls), path('api/city_distribution/', views.city_distribution), path('api/education_distribution/', views.education_distribution), path('api/salary_by_experience/', views.salary_by_experience), ]联调时如果图表空白,先打开浏览器 F12 看 Network 里接口返回了什么,再看 Console 有没有报错。十有八九是接口返回的数据结构和 Echarts 期望的对不上,或者 JSON 序列化时遇到日期类型报错。
5. 避坑与排查:这些坑我替你踩过了
5.1 数据采集返回 403 或空数据
现象:requests 请求返回状态码 403,或者返回的 HTML 里解析不出任何岗位卡片。
原因:目标站点做了反爬,检查 User-Agent 是否缺失,或者请求频率太高被临时封了 IP。
解决:请求头里补全 User-Agent、Referer,采集循环里加time.sleep(1)到time.sleep(3)的随机延迟。如果还是不行,换一个采集目标,期末作业没必要死磕一个反爬严格的站点。
5.2 Django 迁移时报 mysqlclient 编译错误
现象:执行python manage.py migrate时报错,提示缺少 Visual C++ 编译环境或 mysql_config not found。
原因:mysqlclient 是 C 扩展,Windows 上需要编译工具链,Linux 上需要 mysql 开发库。
解决:改用 pymysql,在项目__init__.py里加pymysql.install_as_MySQLdb()。Linux 上如果坚持用 mysqlclient,先apt install default-libmysqlclient-dev再装。
5.3 Echarts 图表不显示或显示空白
现象:页面加载了,容器有大小,但图表区域一片空白。
原因:常见有三种——容器没有设置宽高、Echarts 初始化时 DOM 还没渲染完、接口数据格式不对。
解决:容器必须显式设置width和height,不能靠 CSS 撑开。初始化放在DOMContentLoaded之后,或者把 script 放 body 末尾。接口数据用console.log打出来,对照 Echarts 官方示例的数据结构检查。
5.4 薪资字段解析报错导致入库中断
现象:采集脚本跑到某一条数据时抛异常,整个脚本挂掉。
原因:薪资格式不统一,有的写"面议",有的写"15K以上",split('-')直接崩。
解决:parse_salary里用 try-except 包住,解析失败返回(0, 0),并在入库前过滤掉薪资为 0 的异常数据。采集脚本里每条数据的处理都单独 try-except,一条失败不影响整体。
5.5 中文乱码问题
现象:数据库里存的中文显示成问号,或者页面上图表标题乱码。
原因:数据库字符集不是 utf8mb4,或者连接时没指定字符集。
解决:建库时用CREATE DATABASE recruitment_db CHARACTER SET utf8mb4,Django 数据库配置里加'OPTIONS': {'charset': 'utf8mb4'}。模板文件保存为 UTF-8 编码,HTML 里声明<meta charset="utf-8">。
6. 让图表真正有分析价值:三个进阶技巧
6.1 薪资区间分桶而不是直接画原始值
直接画每个岗位的薪资,柱子密密麻麻看不出规律。更好的做法是把薪资分桶,比如 0-5K、5-10K、10-15K、15-20K、20K 以上,统计每个区间的岗位数量。这样出来的柱状图能一眼看出市场薪资集中在哪个区间。
def salary_range_distribution(request): """薪资区间分布""" bins = [(0, 5), (5, 10), (10, 15), (15, 20), (20, 999)] labels = ['0-5K', '5-10K', '10-15K', '15-20K', '20K以上'] counts = [] for low, high in bins: count = JobInfo.objects.filter( salary_low__gte=low, salary_low__lt=high ).count() counts.append(count) return JsonResponse({'labels': labels, 'counts': counts})参数说明:salary_low是采集时解析出来的最低薪资数值字段。分桶边界可以根据实际数据分布调整,如果大部分岗位在 10-20K,就把这个区间拆细一点。
6.2 用地图展示城市分布
Echarts 中国地图比柱状图更直观,但需要引入地图的 JSON 数据。常见做法是下载 china.json 放到 static 目录,注册地图后渲染。
fetch('/static/map/china.json') .then(res => res.json()) .then(mapData => { echarts.registerMap('china', mapData); chart.setOption({ visualMap: { min: 0, max: 100, inRange: { color: ['#e0f3f8', '#4575b4'] } }, series: [{ type: 'map', map: 'china', data: cityData // [{name: '北京', value: 50}, ...] }] }); });注意城市名要和地图 JSON 里的名称完全一致,比如"北京"不能写成"北京市",否则地图上不会着色。这是 echarts 中国地图最常见的翻车点。
6.3 接口加缓存避免重复查询
每次刷新页面都重新查数据库,数据量大时明显卡顿。Django 自带缓存框架,给统计接口加个 5 分钟缓存:
from django.core.cache import cache def city_distribution(request): cache_key = 'city_distribution' result = cache.get(cache_key) if result is None: data = (JobInfo.objects .values('city') .annotate(count=Count('id')) .order_by('-count')[:10]) result = { 'cities': [item['city'] for item in data], 'counts': [item['count'] for item in data], } cache.set(cache_key, result, 300) # 缓存 5 分钟 return JsonResponse(result)参数说明:300是缓存秒数,数据不常变的话可以设长一点。开发阶段建议先不加缓存,否则改了数据看不到效果,容易怀疑人生。
最后说个我自己的习惯:每次改完采集脚本或接口,先跑一遍python manage.py shell,在交互环境里手动调一下 ORM 查询,确认数据对了再往页面上接。这个习惯帮我省了无数次"图表不对但不知道是数据问题还是前端问题"的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取