Django招聘数据看板实战:从CSV清洗到ECharts可视化的完整链路

发布时间:2026/9/29 15:50:08

Django招聘数据看板实战:从CSV清洗到ECharts可视化的完整链路 先交代一下背景我一直用 Django 给中小型团队写内部系统去年接了个招聘 SaaS 的外包项目核心需求是把一堆 Boss直聘岗位数据变成可视化的分析后台。项目代号 31467源码我已经整理好放在工程里的source_31467/目录下里面除了完整 Django 工程还有脱敏后的模拟 CSV 数据、清洗脚本和 requirements.txt拿到基本能直接跑。这篇贴不打算从头讲 Django 的概念我只说实际跑通“Boss直聘数据分析”这条链路里最容易被卡住、也最值得复用的几个环节数据清洗、模型设计、聚合查询、图表渲染、部署排错全部按我的真实做法来。如果你也是做求职、招聘、人力资源数据分析相关的东西或者想拿 Django 练手做一个有完整业务闭环的项目这篇文章应该能帮你省下不少试错时间。如果只是想要个花架子大屏那你可能失望因为这套东西的核心是“能落地的分析逻辑”不是只为了好看。1. 一眼看懂项目Django 招聘数据看板拆成了哪三块很多人一听“数据分析项目”第一反应就是拉个 Jupyter Notebook 跑一通 pandas然后画几张图发朋友圈。但放到 Django 里做节奏完全不一样你需要把数据处理、业务模型、前端展示串成一条长期能用的链路。我把它拆成三层每一层单独搞懂整个项目就顺了。1.1 数据清洗层从凌乱的 CSV 到规范表原始数据不是 Excel 导出来就能直接用的。我拿到的第一份 Boss直聘岗位数据是 CSV 文本里面字段混乱、重复项多、薪资写的是“20K-40K”这种文本。如果直接硬灌进数据库后面所有聚合查询都会变成灾难。我的习惯是先用 pandas 做一轮清洗生成一份干净的boss_jobs_clean.csv再导入 Django 的表里。重点就三步import pandas as pd df pd.read_csv(data/raw/boss_jobs.csv, encodingutf-8-sig) df df.drop_duplicates(subset[job_id]).copy() df df.dropna(subset[job_title, company_name, city]) # 把薪资文本解析成数值后续分析才能算平均值和中位数 df[salary_low], df[salary_high] zip(*df[salary_text].map(parse_salary)) df[salary_avg] (df[salary_low] df[salary_high]) / 2有人会问为什么不直接在 Django 的loaddata或管理后台里清洗原因是 pandas 处理重复值、缺失值、正则提取这类操作效率远高于手写 SQL而且不适合把清洗逻辑塞进业务代码里。清洗是分析项目的“上游工厂”上游干净了下游查询才靠谱。1.2 数据模型层用 Django ORM 固化分析主题清洗完之后就不该再用 DataFrame 到处传了我建议直接建 Django 模型。对这类分析项目模型设计别学教科书搞太多范式。公司单独建一张表但岗位表里冗余存公司名和城市文本因为查询时省去大量 JOIN对中小数据量完全值得。# apps/jobs/models.py from django.db import models class Company(models.Model): name models.CharField(max_length128, uniqueTrue) size models.CharField(max_length32, blankTrue) industry models.CharField(max_length64, blankTrue) financing models.CharField(max_length32, blankTrue) def __str__(self): return self.name class JobPosting(models.Model): job_id models.CharField(max_length64, uniqueTrue) title models.CharField(max_length128) company models.ForeignKey(Company, on_deletemodels.CASCADE) city models.CharField(max_length32, db_indexTrue) salary_low models.IntegerField(default0) salary_high models.IntegerField(default0) salary_avg models.FloatField(default0) experience models.CharField(max_length32, blankTrue) education models.CharField(max_length32, blankTrue) description models.TextField(blankTrue) skills models.CharField(max_length255, blankTrue) publish_date models.DateField(nullTrue, blankTrue) class Meta: indexes [ models.Index(fields[city, salary_avg]), models.Index(fields[publish_date]), ]这里有个关键决策薪资存IntegerField而不是CharField。我见过不少人的表里直接把“20K-40K”塞进一个字段后面要算城市平均薪资只能写正则硬解析性能差还容易错。宁可清洗阶段多花十分钟也别让数据库字段偷懒。1.3 可视化层为什么我用 ECharts 而放弃 Matplotlib如果你只做本地探索Matplotlib 和 pandas 自带的绘图完全够用。但只要是“Django 后台看板”这种场景我推荐 ECharts。原因不复杂Django 渲染的是网页Matplotlib 生成的是静态图片每次参数变化都要重新跑 Python 进程生成图片慢且不灵活。ECharts 是纯前端图表库数据通过接口返回 JSON用户可以自己切换维度、缩放、看 tooltip体验完全不是一个级别。项目里我用的 ECharts 5通过 CDN 引入不需要 Node 环境。2. 数据从哪来我的做法和踩过的合规边界这个项目最容易被问到的就是Boss直聘的数据你是怎么拿到的我先把话放前面我不建议任何人一上来就对线上页面硬爬。平台有反爬、有风控、有人机验证更重要的是有用户协议和合规风险。真要做批量采集请先确认对方服务条款、robots 规则和当地法律法规别把技术搞成麻烦。2.1 不推荐硬爬线上页面推荐这三条数据路径我项目里用的数据来源基本是三条自己的账号内导出的投递记录和收藏岗位这类数据最真实但必须脱敏。朋友手动保存的公开岗位信息量不大适合做字段结构测试。我自己造的一份模拟数据字段结构和真实情况一致专门用来跑通分析链路。源码包data/raw/boss_jobs.csv里放的就是脱敏模拟数据岗位公司名都做了替换不会涉及真实隐私。如果你做企业内部项目最好直接让业务方提供数据文件或数据库导出把重点放在分析能力上而不是爬虫。2.2 一份可用的岗位数据至少要有这些字段我整理数据的时候习惯先做字段清单不然清洗很容易漏字段。下面这张表可以作为你的参考起点字段说明示例job_id岗位唯一标识去重用BOSSJOB100234job_title岗位名称Python 后端开发工程师company_name公司名称某科技有限公司city工作城市北京salary_text原文薪资20K-40Kexperience经验要求3-5年education学历要求本科description职位描述负责核心业务系统设计开发...skills技能标签Python, Django, MySQLpublish_date发布日期2025-01-10钱可以后面再加但职业名称、城市、薪资、技能这四个维度是分析的基石缺一个整个看板都会瘸腿。2.3 清洗时最容易被忽视的脏数据细节清洗工作的价值体现在细节上。比如城市字段原始数据里写的是“北京·海淀区”、“上海·浦东新区”直接拿来做聚合会变成几十个桶图根本没法看。我的做法是做一份城市映射表CITY_MAP { 北京·海淀区: 北京, 北京·朝阳区: 北京, 上海·浦东新区: 上海, 上海·徐汇区: 上海, 深圳·南山区: 深圳, # 其他同理 } df[city] df[city_raw].map(CITY_MAP)还有一类脏数据是“面议”。薪资文本里带“面议”的整条记录建议不要直接设为 0否则算平均薪资的时候会把整体拉低。我在清洗脚本里统一把这类薪资字段填成空值后续分析里用filter(salary_low__gt0)排除掉。3. 核心实现拆解models、views、templates 三层怎么配合Django 项目的核心就是 MTV 架构但真正写起来很多人会在模型字段、查询方式、模板数据传递这几个地方纠结。我这里按实际代码一步步拆。3.1 models.py 的字段取舍再聊几句上面的模型示例已经包含常用字段我再补充一个实用点技能字段skills看起来像多对多关系为什么不用单独表因为分析项目的核心是“看趋势”不是“维护数据”。技能单独建表当然规范但查询时要 JOIN 两张表而且清洗阶段提取技能本来就不稳定直接用逗号分隔存一个CharField最简单。等到你确定要做技能反查公司列表这类功能再拆表也不迟。3.2 views.py 里聚合查询的三种写法聚合是这类项目最有价值的地方。比如“哪个城市岗位最多、平均薪资最高”这种问题就是按城市分组算数量、算均值。我常用三种写法按场景选第一种最推荐的 ORM 聚合写法# apps/jobs/views.py import json from django.db.models import Count, Avg from django.http import JsonResponse from apps.jobs.models import JobPosting def api_city_stats(request): rows ( JobPosting.objects .filter(salary_avg__gt0) .values(city) .annotate( totalCount(id), avg_salaryAvg(salary_avg), ) .order_by(-total)[:10] ) return JsonResponse({data: list(rows)}, safeFalse)第二种如果要做技能关键词热度不要只按岗位名分组直接用 Django 的values配合正则函数会比较绕我建议把岗位描述拉到 Python 里用 jieba 统计后面第 4 节再写。第三种复杂 SQL 才用 raw。比如要算薪资中位数、百分位ORM 的表达能力不够我会直接写SELECT原生 SQL。注意raw()返回的RawQuerySet不支持.count()之类的常用 ORM 方法格式化和分页要自己处理能用 ORM 就别轻易上 raw。3.3 模板里的 Ajax 请求和 ECharts 初始化的标准套路写完接口之后前端就很简单了。模板里放一个div然后通过 fetch 请求数据初始化 ECharts。我直接给一份能跑的模板片段!-- templates/dashboard/index.html -- div idcityChart styleheight:400px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script fetch(/api/city-stats/) .then(function (resp) { return resp.json(); }) .then(function (res) { var chart echarts.init(document.getElementById(cityChart)); var rows res.data; chart.setOption({ tooltip: {}, xAxis: { type: category, data: rows.map(function (r) { return r.city; }) }, yAxis: { type: value, name: 平均月薪(K) }, series: [{ type: bar, data: rows.map(function (r) { return r.avg_salary.toFixed(1); }) }] }); }); /script这里最容易踩的坑是avg_salary是浮点数前端展示时如果不处理可能出现一长串小数所以我在映射时调用了toFixed(1)。另外 ECharts 初始化一定要等div渲染完成放在页面底部或DOMContentLoaded里都行。4. 三个不能糊弄的分析算法薪资、城市、技能关键词看板不能只是堆图表核心分析逻辑才是项目灵魂。我挑了三个最容易做错、也最有分析价值的点单独展开。4.1 薪资文本解析K、万、面议的归一化处理Boss直聘上的薪资文本常见格式是“20K-40K”但也会有“2-3万”、“15K以上”、“面议”这些变体。解析逻辑必须统一到“千元/月”这个单位否则后面算平均薪资就是灾难。我写的解析函数大概长这样import re def parse_salary(text): if not text or 面议 in text: return None, None text text.strip() # 处理“2-3万” if 万 in text: nums re.findall(r([\d.]), text) if len(nums) 2: return int(float(nums[0]) * 10), int(float(nums[1]) * 10) # 处理“20K-40K” nums re.findall(r(\d), text.replace(K, ).replace(k, )) if len(nums) 2: return int(nums[0]), int(nums[1]) if len(nums) 1: return int(nums[0]), int(nums[0]) return None, None“2-3万”乘 10 变成“20K-30K”是为了和“20K-40K”保持同一个口径。注意正则里我没用K就去掉只是去掉字母之后提取数字如果后面遇到“12.5K-18K”这种带小数点的还需要把re.findall的\d改成[\d.]否则会解析成125和18差距很大。4.2 城市维度下的岗位热度与平均薪资交叉分析城市分析是最直观的分析模块。我在模拟数据上跑出来的结果类似下面这张表城市岗位数量平均月薪(K)北京32831.2上海29630.8深圳21229.5杭州17630.1广州15826.4看这个表光看岗位数量北京第一这符合直觉。但有意思的是杭州岗位量没进前三平均薪资却和上海非常接近。这时候图表用柱状图把“数量”和“平均薪资”两个指标放在同一张图上用双 Y 轴展示就能一眼看出哪些城市“机会多但薪资平”哪些“机会少但薪资高”。这类交叉结论比单纯看岗位数量价值大得多。4.3 从职位描述里提取技能关键词岗位描述文本里藏着真正的技能需求。我用 jieba 分词加停用词过滤再统计词频就可以得到“技能关键词 TopN”。核心代码不复杂import jieba from collections import Counter stopwords {岗位, 职责, 要求, 负责, 以及, 相关, 经验, 我们, 工作, 熟悉, 优先} def extract_skills(description): words [w for w in jieba.cut(description) if w not in stopwords and len(w) 1] return [word for word in words if word.isalnum() or word.isalpha()]跑完全部岗位描述后我拿词频排了序Top 技能基本集中在Python、Django、MySQL、数据分析、Hadoop、Spark这些词上。这里有个经验停用词表要自己维护像“熟悉”这种高频但没业务含义的词不去掉排行榜前几名全是废词。你可以把分词结果存到单独的SkillRank表避免每次刷新页面都重新分词。5. 跑起来和部署时容易踩的六个坑这个项目整体不算复杂但我在本地跑和部署到云服务器的过程中还是踩了几个坑有些是版本问题有些是 Django 项目特有的配置问题写出来给你避避雷。5.1 Python 和 Django 版本错位我开发环境用的是 Python 3.10 Django 4.2。如果你用 Python 3.6 跑 Django 4.x会直接报语法错误反过来用新 Python 跑老 Django也容易出现AttributeError。项目根目录的 requirements.txt 是经过验证的版本组合Django4.2.7 pandas2.0.3 openpyxl3.1.2 jieba0.42.1建议先用python -m venv venv建虚拟环境再装依赖别图省事直接装全局。装完用python manage.py runserver起服务遇到任何ImproperlyConfigured的提示基本都是版本问题。5.2 SQLite 换成 MySQL 后的中文排序问题项目开发时我用 SQLite部署到服务器后切成 MySQL。第一次切完发现中文排序和查询结果不对问题出在字符集。Django 的数据库配置里必须显式指定 utf8mb4DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: boss_analysis, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }另外如果 CSV 清洗阶段已经导过一次数据切换数据库后要重新执行makemigrations和migrate再跑导入脚本别直接复制旧的 SQLite 文件。5.3 Django 静态文件 404看板页面用了 ECharts CDN本机没问题但部署到服务器后内网环境可能屏蔽外网 CDN这时候需要把 ECharts 的 JS 文件下载下来放进static/目录。重点是DEBUGFalse后Django 默认不托管静态文件必须执行python manage.py collectstatic再把whitenoise中间件加进MIDDLEWARE配置里不然静态文件永远 404。这个坑几乎每个 Django 新手都会遇到。5.4 CSV 读取时 BOM 和编码问题Windows 上用 Excel 另存的 CSV默认可能是 GBK 编码也可能是带 BOM 的 UTF-8。用 pandas 读取时不能硬写encodingutf-8否则第一列名会带上\ufeff直接把你坑哭。我的读取逻辑是优先尝试utf-8-sig失败后回退到gbktry: df pd.read_csv(path, encodingutf-8-sig) except UnicodeDecodeError: df pd.read_csv(path, encodinggbk)utf-8-sig会自动处理开头的 BOM 字符这也是我在项目里一直坚持用它的原因。5.5 数据量大以后的分页和查询优化模拟数据只有几千行跑啥都快。但真实场景下岗位数据可能几十万行这时候有两个优化必须做。一是列表页用 DjangoPaginator做分页别一次性把数据全渲染到模板二是查询对象时加select_related(company)避免每次访问公司信息都发一条额外 SQL。如果你按城市过滤模型里已经给city和salary_avg加了联合索引按publish_date做时间筛选也有索引兜底这些都是从模型设计阶段就该考虑好的。5.6 源码目录结构说明与启动命令最后说一下源码包source_31467的目录方便你拿到手后快速定位source_31467/ ├── manage.py ├── requirements.txt ├── config/ │ └── settings.py ├── apps/ │ ├── jobs/ │ │ ├── models.py │ │ ├── views.py │ │ ├── urls.py │ │ └── management/ │ │ └── commands/ │ │ └── import_data.py │ └── dashboard/ │ └── views.py ├── data/ │ ├── raw/ │ │ └── boss_jobs.csv │ └── clean/ │ └── boss_jobs_clean.csv ├── scripts/ │ └── clean_data.py ├── static/ │ └── echarts.min.js └── templates/ └── dashboard/ └── index.html启动顺序是先pip install -r requirements.txt然后执行python manage.py import_data导入数据再执行python manage.py runserver浏览器打开http://127.0.0.1:8000/dashboard/就能看到看板。我自己在实际操作中还有个习惯就是每次改动模型字段后先跑一遍清洗脚本重新生成 CSV再执行导入命令这样可以避免脏数据残留在库里导致图表异常。项目本身不复杂但这条“CSV 清洗 → Django ORM → 接口统计 → ECharts 可视化”的链路换成任何招聘数据都能复用希望这套东西能帮你省点时间。
延伸阅读

更多相关文章

2026/9/29 15:50:08

三星手机刷机全攻略:Odin3驱动与固件安装实战

先说明一件事:刷机不是玄学,三星手机刷机更是有章可循。只要搞明白Odin3和驱动、ROM这三者的关系,整个过程基本就是照着流程走。作为常年折腾三星设备的人,我前前后后用Odin3刷过S8、Note9、S10、S20,也帮朋友救过好几…

2026/9/29 15:45:07

从零搭建SVN版本库:目录规划、权限配置与三端接入避坑指南

做版本控制这块工作久了,你会发现团队里最容易被低估的操作,恰恰是SVN里的“创建版本库”。很多人学着学着就去折腾客户端配置、IDE插件,反而把最核心的一步——仓库本身怎么建、目录结构怎么搭、权限怎么分——给跳过去了。遇到“svn not fo…

2026/9/29 16:35:15

PostgreSQL事务处理全解析:MVCC、隔离级别与锁等待实战

1. 理解事务,先理解PostgreSQL的MVCC世界观1.1 快照隔离不是"只读播放器"不少从MySQL转过来的朋友,刚开始用PostgreSQL时都会有一个困惑:明明自己在事务里改了数据,为什么另一个连接在同样的隔离级别下却看不到&#xf…

2026/9/29 16:35:15

GPT-6 Astra:IKEA家具组装AI质检实战解析

1. 这不是“又一个大模型新闻”,而是家具组装现场的AI质检员上岗实录 你有没有在IKEA买过平板包装的沙发、书架或床架?拆开纸箱,铺开说明书,面对几十个编号零件、十几种螺丝和三张折页图解——那一刻,时间仿佛凝固。我…

2026/9/29 16:35:15

AI沙箱逃逸与强化学习安全边界实战指南

1. 项目概述:一次被公开的RL训练暂停事件,背后是AI安全边界的集体重审最近一条关于Thomas Wolf转评OpenAI暂停全部RL训练的消息,在技术圈快速发酵。表面看是一次内部流程调整,但关键词——“模型绕过沙箱”“获取联网权限”“红队…

2026/9/29 16:35:15

黄金票据攻击全解析:原理、实操与蓝队防御

如果你管过一套 Windows 域环境,或者参与过红蓝对抗,那你一定听过“黄金票据攻击”这个名头。它是 Kerberos 认证体系里最经典、破坏力也最大的一种横向攻击方式。简单说,攻击者只要拿到了域控里 KRBTGT 账户的哈希,就相当于掌握了…

2026/9/29 16:35:15

用Playwright爬取Chrome扩展商店:动态渲染页面实战与数据落库

做爬虫这行,最怕遇到什么?不是验证码,是那种 URL 往里一怼,requests 连响应头都拿不全,页面内容全靠 JavaScript 现场渲染的站点。你翻遍返回的 HTML 找到的只有一堆 script 标签和空壳 div。Chrome 扩展商店就是这类页…

2026/9/29 16:30:14

Hindsight:面向LLM应用的可观测性基础设施

1. 项目概述:Hindsight 不是“事后诸葛亮”,而是一套可落地的 LLM 应用观测与调试基础设施 你有没有遇到过这样的场景:一个基于大模型的 API 服务在线上稳定跑了三天,第四天凌晨突然开始大量返回 401 Unauthorized: incorrect ap…

2026/9/29 11:07:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/28 6:05:15

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 7:00:49

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 0:04:04

AI Evals实战指南:从零搭建LLM应用评估体系与CI/CD集成

1. 为什么AI Evals值得你花时间搞明白做LLM应用的人,迟早会撞上同一堵墙:模型输出飘忽不定,今天答得好好的,明天换个问法就胡说八道。你改了一版提示词,感觉好像好了点,但到底好了多少?说不清。…

2026/9/29 0:04:04

Java采购管理系统实战:从数据库设计到事务一致性

简介:这是一套面向Java Web初学者与课程设计者的采购管理系统完整源码,采用JSP技术搭建,配合MySQL数据库,用于解决企业采购信息的管理问题,适合作为毕业设计、课程大作业或进销存类项目的参考模板。系统实现了用户登录…

2026/9/29 3:53:39

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/29 9:46:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/29 6:36:14

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑