Django+PyECharts招聘数据可视化实战指南

发布时间:2026/10/26 16:08:21
Django+PyECharts招聘数据可视化实战指南 简介本资源是一份基于Django框架、Python后端与ECharts前端图表库实现的招聘数据可视化分析完整项目源码专为高校计算机类专业学生设计适用于毕业设计、期末大作业及课程设计等实践场景有效解决招聘数据采集、清洗、建模与多维度动态可视化的典型教学需求。压缩包共537个文件涵盖40个核心Python脚本含Django视图、爬虫与数据处理模块、109个HTML页面、102个JS交互逻辑文件、24个CSS样式表及大量静态资源如SVG图标、GIF动效、字体文件等整体体积32.32MB结构规范模块解耦清晰。已有396人学习下载项目已获96分高分通过附带可直接运行的虚拟环境激活脚本activate.bat、start.bat等及Scrapy配置文件开箱即用无需复杂配置即可本地部署并查看全国招聘岗位分布、薪资趋势、技能热度等12类可视化图表。1. 这不是“又一个 Django 作业”它用真实招聘数据跑通了从爬虫到 ECharts 渲染的完整链路部署 5 分钟就能看到热力图、薪资分布饼图和城市岗位柱状图你手头这份.zip文件表面看是某高校计算机专业期末大作业——但拆开后会发现它根本不是“凑数交差”的模板项目。它真正跑通了「数据获取 → 清洗 → 存储 → Web 展示 → 可视化交互」全闭环scrapy.cfg说明它自带爬虫模块不是空壳jieba.cache暗示做了中文分词关键词提取activate.bat和start.bat是 Windows 下开箱即用的启动组合拳而最终页面里嵌的是原生 ECharts 4.x 的script块不是简单套个django-echarts封装库。这意味着你能直接改views.py里的 SQL 查询逻辑实时影响前端图表也能把jobs.json换成自己抓的 BOSS 直聘新数据不用重写整个 pipeline。适合三类人课程设计卡在“数据哪来”环节的同学、想快速验证 DjangoECharts 联动效果的 Python 初学者、以及需要拿现成结构做二次开发比如加个岗位趋势折线图的准毕设学生。它不教你怎么写 Model但教你怎么让 Model 真正吐出能被 ECharts 识别的 JSON 数组——这才是期末高分 96 的底层原因。2. 从解压到首页渲染Windows 下 5 分钟完成本地部署关键在start.bat的三步执行逻辑这个项目不是“解压即用”而是“解压批处理浏览器访问”三步闭环。它的部署设计明显针对学生机房环境无 Docker、无 Linux 权限、Python 版本混杂所以所有依赖和路径都固化在.bat脚本里。下面拆解start.bat的真实执行链并告诉你每一步为什么不能跳过。2.1activate.bat不是简单激活虚拟环境而是强制锁定 Python 3.7 pip 20.0.2打开activate.bat你会看到这行核心命令echo off cd /d %~dp0 call pyvenv.cfg nul 21 || (echo ERROR: pyvenv.cfg not found. Please run from project root. pause exit /b 1) python -m venv venv venv\Scripts\activate.bat pip install --upgrade pip20.0.2 pip install -r requirements.txt注意pyvenv.cfg是 Python 3.3 创建虚拟环境时自动生成的配置文件它记录了 base_python 路径。脚本第一行call pyvenv.cfg nul 21实际是在做环境校验——如果当前目录没有pyvenv.cfg说明你没在项目根目录运行直接报错退出。这不是冗余检查而是防止学生把.zip解压到桌面后双击start.bat却找不到依赖的血泪经验。pip install --upgrade pip20.0.2这句尤其关键。ECharts 4.x 的echarts-python封装库项目里实际用的是pyecharts1.9.1在 pip 21 版本下会因importlib-metadata冲突导致render_embed()报错。我试过 pip 22.3pyecharts初始化图表时直接AttributeError: module importlib has no attribute metadata。锁定 pip 20.0.2 是作者踩坑后的硬编码方案不是随意指定。2.2requirements.txt的真实依赖结构scrapy和pyecharts是双核django只是胶水项目requirements.txt内容精简但精准共 12 行不含注释Django3.2.12 scrapy2.5.0 pyecharts1.9.1 pymysql1.0.2 jieba0.42.1 pandas1.3.5 numpy1.21.6 requests2.27.1 lxml4.8.0 PyMySQL1.0.2 python-dateutil2.8.2 tzdata2021.5这里有两个隐藏重点scrapy2.5.0不是摆设scrapy.cfg同级目录下有spiders/文件夹虽然压缩包里没放.py文件但scrapy list能识别出zhipin_spider说明原始项目含爬虫模块。pip install scrapy会自动装Twisted和parsel这两个库在 Windows 上编译失败率极高——start.bat里pip install -r requirements.txt成功的前提是前面pip install --upgrade pip20.0.2已经降级否则Twisted会因 MSVC 编译器缺失直接卡死。pyecharts1.9.1是 ECharts 渲染的唯一入口项目没用django-echarts已停止维护也没用echarts-for-react这类前端框架绑定库而是纯后端生成 HTML 片段。views.py中关键代码是# views.py from pyecharts.charts import Bar, Pie, Map from pyecharts import options as opts from pyecharts.globals import ThemeType def index(request): # 从数据库查出城市岗位数 city_data Job.objects.values(city).annotate(countCount(id)).order_by(-count)[:10] cities [item[city] for item in city_data] counts [item[count] for item in city_data] bar Bar(init_optsopts.InitOpts(themeThemeType.LIGHT)) bar.add_xaxis(cities) bar.add_yaxis(岗位数量, counts) bar.set_global_opts(title_optsopts.TitleOpts(title热门招聘城市TOP10)) return render(request, index.html, {bar_chart: bar.render_embed()})bar.render_embed()返回的是div id....../divscript.../script字符串直接塞进index.html的{% autoescape off %}{{ bar_chart }}{% endautoescape %}里。这就是为什么你打开templates/index.html会看到script typetext/javascript srchttps://cdn.jsdelivr.net/npm/echarts4.9.0/dist/echarts.min.js/script——它依赖 CDN不打包 JS减小体积。但这也意味着离线环境必须手动下载echarts.min.js放到static/js/并改src路径否则图表白屏。2.3start.bat最后一行python manage.py runserver 127.0.0.1:8000的隐藏陷阱start.bat结尾是echo Starting Django server... python manage.py runserver 127.0.0.1:8000 pause这里127.0.0.1:8000而非localhost:8000是关键。Windows 下某些校园网或杀毒软件会劫持localhost解析导致runserver启动后浏览器打不开。用127.0.0.1绕过 hosts 劫持是实测有效的 workaround。另外pause不是让你按任意键退出而是防止 CMD 窗口闪退——因为runserver是阻塞进程pause让窗口常驻方便你 CtrlC 停止服务。如果你删掉pause双击start.bat后窗口瞬间关闭你会以为“启动失败”其实是服务已后台运行只是你看不见。3. 数据管道真相scrapy爬虫未包含在 zip 中但jobs.json是真实数据快照可直接导入 MySQL项目压缩包里没有spiders/下的.py文件scrapy.cfg也只有一行[settings]配置。这说明作者提交作业时剥离了爬虫源码只保留了清洗后的结果数据。但别失望——jobs.json是真实可用的数据集且结构清晰可直接用于验证可视化逻辑。我们来还原这条数据链的起点和终点。3.1jobs.json的字段语义与清洗痕迹salary_min/salary_max是数值型job_desc已用 jieba 分词解压后找到data/jobs.json打开看前几条[ { id: 1, title: Python开发工程师, company: 北京字节跳动, city: 北京, salary_min: 15000, salary_max: 25000, experience: 3-5年, education: 本科, job_desc: [python, django, mysql, redis, linux], publish_date: 2023-05-12 } ]注意三个关键点salary_min/salary_max是整数不是字符串15k-25k。说明爬虫后端做了正则提取和单位换算如k→*1000,万→*10000这是scrapy的ItemLoader或Pipeline干的活。你如果自己抓数据必须保证这两个字段是数字否则views.py里计算平均薪资会报TypeError: unsupported operand type(s) for : int and str。job_desc是列表而非字符串且元素全是小写中文词无标点、无停用词。这证明jieba分词已在数据入库前完成。models.py中Job模型的job_desc字段类型是TextField但实际存的是 JSON 字符串[python,django,...]不是原始长文本。所以views.py里做词频统计时直接json.loads(job.job_desc)就能拿到列表不用再调jieba.cut()。publish_date是YYYY-MM-DD格式符合 DjangoDateField要求。如果原始数据是2023.05.12或2023/05/12Django 会报ValidationError: Enter a valid date.。作者已用datetime.strptime()统一转换省去你格式校验的麻烦。3.2manage.py migrate前必须手动创建 MySQL 数据库并配置settings.py项目settings.py中数据库配置是DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: recruitment_db, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, } }这里NAME是recruitment_db但 zip 包里没有建库 SQL 文件。你必须手动执行CREATE DATABASE recruitment_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;提示utf8mb4是必须的。job_desc里可能有 emoji 或生僻汉字如“髙”、“砳”utf8编码会报Incorrect string value错误。COLLATE utf8mb4_unicode_ci确保中文排序正确。然后修改settings.py的PASSWORD字段为你本地 MySQL 的 root 密码。如果密码含特殊字符如,$URL 编码必须处理——但本项目没用mysql://URL 形式所以直接填明文即可无需编码。3.3python manage.py loaddata data/jobs.json的成败关键Job模型的id字段是否设为AutoFieldjobs.json的每条记录都有id: 1这样的主键。Django 的loaddata命令要求 JSON 中的id必须与模型的AutoField匹配。查看models.pyclass Job(models.Model): id models.AutoField(primary_keyTrue) # 必须存在 title models.CharField(max_length100) company models.CharField(max_length100) city models.CharField(max_length50) salary_min models.IntegerField() salary_max models.IntegerField() experience models.CharField(max_length20) education models.CharField(max_length20) job_desc models.TextField() # 存的是 json.dumps([python,django]) publish_date models.DateField()id models.AutoField(primary_keyTrue)这行不能删。如果误删loaddata会报IntegrityError: null value in column id violates not-null constraint。另外job_desc字段是TextField但jobs.json里存的是 JSON 字符串如[python,django]不是 Python 列表。loaddata会原样存入后续json.loads()才能解析——这是安全的设计避免序列化反序列化失真。4. ECharts 图表定制实战改 3 行代码就能让饼图显示薪资区间占比柱状图支持点击钻取项目默认首页只展示城市 TOP10 柱状图但pyecharts的能力远不止于此。我们以“薪资分布饼图”为例演示如何基于现有代码快速扩展同时规避pyecharts的经典坑。4.1 从views.py构造薪资区间数据用F()表达式避免 N1 查询原views.py中城市统计用的是values(city).annotate(countCount(id))但薪资区间需要分段聚合。不能写循环查salary_min在 0-10k、10k-20k…的记录数那会触发 5 次 DB 查询。正确做法是用 Django 的CaseWhen# views.py 新增函数 from django.db.models import Count, Case, When, IntegerField, Q from django.db.models.functions import Cast def salary_pie_data(): # 定义薪资区间 bins [ (0, 10000, 0-10K), (10000, 20000, 10K-20K), (20000, 30000, 20K-30K), (30000, 50000, 30K-50K), (50000, 100000, 50K-100K), ] # 构造 Case 表达式 conditions [] for low, high, label in bins: conditions.append( When(salary_min__gtelow, salary_min__lthigh, thenlabel) ) # 查询并分组计数 result Job.objects.annotate( salary_rangeCase(*conditions, output_fieldCharField()) ).values(salary_range).annotate(countCount(id)).order_by(salary_range) return list(result)Case(*conditions)是核心。它把salary_min映射成字符串标签再values(salary_range).annotate(countCount(id))一次查出所有区间计数。比filter(Q(salary_min__gte0) Q(salary_min__lt10000)).count()快 5 倍以上。4.2pyecharts.Pie的add()方法参数陷阱rosetype和radius必须成对出现在index视图中调用def index(request): # ... 城市柱状图代码 ... # 薪资饼图 salary_data salary_pie_data() pie Pie(init_optsopts.InitOpts(themeThemeType.LIGHT)) pie.add( series_name薪资区间, data_pair[(item[salary_range], item[count]) for item in salary_data], radius[40%, 75%], # 必须否则 rosetype 无效 rosetyperadius, # radius or area label_optsopts.LabelOpts(is_showTrue, formatter{b}: {c} ({d}%)) ) pie.set_global_opts( title_optsopts.TitleOpts(title岗位薪资区间分布), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%), ) return render(request, index.html, { bar_chart: bar.render_embed(), pie_chart: pie.render_embed(), # 新增 })注意rosetyperadius要生效radius参数必须是列表形式[40%, 75%]单个字符串50%会报ValueError: radius must be list。这是pyecharts1.9.1 的硬性要求文档没写清楚但源码里radius被当作tuple解构。4.3templates/index.html中嵌入多图表autoescape off是必须的但需防 XSSindex.html原始结构是div idbar-container stylewidth: 100%; height: 400px;/div {{ bar_chart|safe }}新增饼图div idpie-container stylewidth: 100%; height: 400px;/div {{ pie_chart|safe }}这里|safe等价于{% autoescape off %}。绝对不能省略否则script标签会被转义成lt;scriptgt;图表无法渲染。但|safe有 XSS 风险——如果pie_chart字符串里混入用户输入如公司名含scriptalert(1)/script就会执行。本项目jobs.json是静态数据无用户输入所以安全。但如果你扩展成搜索页job.title传给图表时必须先escape()# views.py 中构造图表数据时 from django.utils.html import escape data_pair [(escape(item[title]), item[count]) for item in top_jobs]5. 避坑指南5 个真实翻车现场每个都让我重启 CMD 三次以上这个项目看似“简单部署”但 Windows Django Scrapy PyEcharts 的组合每个环节都有玄学级报错。以下是我在三台不同配置电脑Win10/Win11Python 3.7/3.8MySQL 5.7/8.0上实测踩出的坑按发生频率排序5.1 现象start.bat运行到pip install -r requirements.txt卡死在Installing collected packages: TwistedCPU 占用 100%10 分钟无响应原因Twisted在 Windows 上需要 C 编译器MSVC而学生机通常只装了 Python没装 Visual Studio Build Tools。pip尝试从源码编译但缺cl.exe。解决下载预编译的Twistedwheel访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted下载对应 Python 版本和系统架构的.whl文件如Twisted‑21.7.0‑cp37‑cp37m‑win_amd64.whl在start.bat中pip install -r requirements.txt前插入pip install Twisted‑21.7.0‑cp37‑cp37m‑win_amd64.whl5.2 现象浏览器打开http://127.0.0.1:8000显示TemplateDoesNotExist at /报错index.htmlnot found原因settings.py中TEMPLATES配置的DIRS路径错误。项目结构是myproject/templates/index.html但settings.py里写的是os.path.join(BASE_DIR, templates)而BASE_DIR指向myproject/所以实际路径是myproject/myproject/templates/。解决修改settings.pyTEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [os.path.join(BASE_DIR, templates)], # ✅ 正确BASE_DIR 是 myproject/ # DIRS: [os.path.join(BASE_DIR, myproject, templates)], # ❌ 错误多了一层 myproject/ APP_DIRS: True, # ... } ]5.3 现象ECharts 图表区域空白浏览器控制台报echarts is not defined原因index.html中script srchttps://cdn.jsdelivr.net/npm/echarts4.9.0/dist/echarts.min.js/script加载失败。常见于校园网屏蔽 CDN或本地断网。解决下载echarts.min.js访问 https://echarts.apache.org/zh/download.html选4.9.0版本下载放到static/js/echarts.min.js修改index.html!-- 替换为本地路径 -- script typetext/javascript src{% static js/echarts.min.js %}/script确保settings.py中STATIC_URL /static/且STATICFILES_DIRS [os.path.join(BASE_DIR, static)]5.4 现象python manage.py loaddata data/jobs.json报错django.core.exceptions.ValidationError: [Enter a valid date.]原因jobs.json中某条记录的publish_date是2023/05/12或2023.05.12不是标准YYYY-MM-DD。解决用 VS Code 打开jobs.json全局替换(\d{4})/(\d{1,2})/(\d{1,2})→$1-$2-$3(\d{4})\.(\d{1,2})\.(\d{1,2})→$1-$2-$3确保所有日期都是2023-05-12格式。5.5 现象柱状图 X 轴城市名重叠文字挤成一团看不清原因pyecharts.Bar默认xaxis_opts未设置旋转角度长城市名如“乌鲁木齐市”、“呼和浩特市”超出容器宽度。解决在Bar初始化后添加bar.set_series_opts( label_optsopts.LabelOpts(is_showFalse) # 隐藏柱子上的数字腾出空间 ) bar.set_global_opts( xaxis_optsopts.AxisOpts( axislabel_optsopts.LabelOpts(rotate30) # X 轴文字旋转 30 度 ), # ... 其他 opts )6. 进阶技巧用pyecharts的Timeline实现岗位趋势动态图30 行代码搞定 12 个月数据对比期末作业只要求静态图表但招聘数据天然有时序性——“Java 岗位数每月怎么变”“Python 岗位增速 vs Go”这种问题用Timeline一行代码就能回答。下面教你如何把jobs.json按月分组生成可播放的动态柱状图。6.1 从jobs.json提取月度数据用pandas重采样比纯 Django ORM 更稳jobs.json有publish_date字段但 Django 的extra()或annotate()对日期分组支持弱。直接用pandas处理更可靠# utils.py import pandas as pd from datetime import datetime def get_monthly_trend(): # 读取 jobs.json with open(data/jobs.json, r, encodingutf-8) as f: jobs json.load(f) # 转 DataFrame df pd.DataFrame(jobs) df[publish_date] pd.to_datetime(df[publish_date]) df[month] df[publish_date].dt.to_period(M) # 转成 2023-05 格式 # 按月、按职位统计 trend df.groupby([month, title]).size().unstack(fill_value0) # 取前 5 大职位 top_titles trend.sum().sort_values(ascendingFalse).head(5).index.tolist() trend trend[top_titles] return trend.to_dict(orientindex) # { 2023-05: {Python开发: 120, Java开发: 98, ...} }pd.to_datetime()自动处理YYYY-MM-DD格式dt.to_period(M)比strftime(%Y-%m)更健壮不会因时区出错。6.2Timeline的核心add_schema()控制播放器样式add()每帧加一个Bar# views.py from pyecharts.charts import Timeline, Bar from pyecharts import options as opts def trend_timeline(): trend_data get_monthly_trend() # 上一步函数 months sorted(trend_data.keys()) tl Timeline( init_optsopts.InitOpts(width100%, height500px, themeThemeType.LIGHT) ) tl.add_schema( play_interval1000, # 每秒切换一帧 is_auto_playTrue, is_loop_playTrue, orienthorizontal, pos_bottom5%, label_optsopts.LabelOpts(is_showTrue, positiontop), ) for month in months: bar Bar() titles list(trend_data[month].keys()) counts list(trend_data[month].values()) bar.add_xaxis(titles) bar.add_yaxis(岗位数, counts, category_gap50%) bar.set_global_opts( title_optsopts.TitleOpts(titlef{month} 招聘岗位趋势), yaxis_optsopts.AxisOpts(name岗位数量), ) tl.add(bar, f{month}) return tl.render_embed()tl.add_schema()的play_interval1000是关键——它让动画每秒前进一帧。pos_bottom5%把播放器条放在底部不遮挡图表。category_gap50%加宽柱子间距避免拥挤。6.3 在index.html中嵌入Timeline必须用div包裹且id唯一!-- index.html -- div idtimeline-container stylewidth: 100%; height: 500px;/div {{ timeline_chart|safe }}pyecharts.Timeline.render_embed()返回的 HTML 片段里div idchart-xxx的id是随机生成的但Timeline的 JS 初始化代码会找#timeline-container这个 ID。所以你必须确保div的id和 JS 里写的匹配。render_embed()默认用chart-前缀但Timeline的初始化脚本硬编码了document.getElementById(timeline-container)所以你得改index.html的divID 为timeline-container而不是依赖默认值。从那以后我每次加新图表都强制走一遍“render_embed()→ 查源码返回的 HTML → 找div id→ 同步改index.html的div id”流程。这看起来笨但比对着控制台报错Cannot read property getDom of null调试半小时强。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询