资讯详情

资讯详情

基于Django+大数据的应届生求职系统开发全流程解析

快毕业那会儿选毕设题目我盯上了“基于Django大数据的应届生求职系统”。说实话当时就是看中这个题目能一次把后端框架、数据库设计、数据采集、数据可视化全串起来做完发现确实很值。现在这套项目源码、文档、环境配置、答辩演示我手里都留了一份这段时间也有不少学弟学妹问这条线怎么做今天干脆把整条路从头到尾写清楚。这篇分享围绕完整交付方案展开核心是用Django做求职业务系统企业可以发职位、学生可以注册投递简历另一边把职位数据通过爬虫和模拟数据收集起来清洗后做统计分析最后用ECharts在页面里展示“岗位技能词频、城市薪资、学历需求”等结果。适合正在做毕设的学生参考也适合想快速上手Django数据分析的开发者。下面我从选型、建模、功能实现到部署答辩按实操顺序一条条讲。1. 项目整体设计与选题思路1.1 为什么这个题目适合当毕设选毕设题目最怕三件事工作量不够被老师质疑、技术点太杂自己写不完、答辩时没东西演示。“应届生求职系统”正好是那种看着传统、但扩展空间很大的题目原因是它自带一套完整业务闭环用户注册登录、简历管理、职位搜索、投递申请、收藏职位、后台审核、数据分析看板每一个模块都是一个可以展开讲的技术点。而大数据部分又给了天然的加分段能展示数据采集、清洗、统计分析、可视化一条完整流程比单纯写增删改查的“XX管理系统”有质感得多。我实际做的时候把角色拆成了学生用户、企业用户、系统管理员三类。学生端核心是维护简历、找职位、投递和收藏企业端核心是发职位、查看收到的简历管理员负责审核职位、管理用户还要看到整个平台的数据统计比如各城市职位数量、薪资分布、技能关键词热度。这个角色划分直接决定了后面数据表和权限逻辑怎么设计必须一开始就定清楚。1.2 系统功能地图与核心流程整体页面我大概做了这些首页职位推荐数据概览、职位列表页搜索筛选、职位详情页、用户注册登录页、个人中心简历、投递记录、收藏、企业后台职位管理、投递管理、管理员后台审核、统计、导出、数据看板页。核心业务流转是这样学生注册完善简历 → 检索职位 → 查看职位详情 → 投递简历 → 企业登录查看投递列表 → 企业调整投递状态待处理/已查看/已通过/未通过→ 学生收到状态推送。这个流程每个环节都有状态变化写代码时要特别注意状态字段的设计不要用动态字符串直接建一个状态枚举省得后面逻辑判断乱掉。1.3 提前要想明白的三个问题第一数据从哪来。我在项目里混合了两种方式一部分用爬虫抓公开招聘信息另一部分按真实分布特征模拟补全保证数据库里有几万条职位数据这样后面做统计才有意义。第二面试官或答辩老师常问“你这个系统的大数据体现在哪”所以数据分析结果不能只留在Jupyter里自嗨一定要做成线上页面并且准备好“数据量多少、处理链路是什么、结果如何验证”这三句话的说辞。第三前后端要不要分离。我最后选择了Django模板Ajax的轻量方案没有硬上Vue和DRF理由是毕设周期有限模板渲染出来效果直观、部署也简单没必要单纯为了技术新而加倍工作量。2. 技术选型与架构设计2.1 Django版本与工程结构环境这块我锁定的是Python 3.10 Django 4.2。为什么不追最新版因为Django 4.2是LTS版本第三方库兼容性好网上的资料也最多遇到报错很容易搜到答案。工程结构我拆成三个业务应用目录大概是job_system/ ├── manage.py ├── config/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── asgi.py ├── users/ # 用户、简历 │ ├── models.py │ ├── views.py │ └── forms.py ├── jobs/ # 职位、投递、收藏 │ ├── models.py │ ├── views.py │ └── urls.py ├── analysis/ # 数据统计与可视化接口 │ ├── models.py │ ├── services.py │ └── urls.py ├── static/ ├── templates/ └── data/ # 原始数据、清洗脚本每个应用通过python manage.py startapp创建各自只负责自己的模型和视图别把代码全堆在根目录里。数据清洗脚本我单独放data目录不参与Django主流程保证项目结构清爽。2.2 “大数据”到底怎么做才合理“大数据”这个词在毕设里可大可小关键是结合自己的真实环境选择可行的路线。我整理了三档方案方案技术组合适合数据量部署难度答辩效果轻量级推荐Pandas MySQL ECharts10万条以内低单机跑通链路清晰每步都能讲中量级Spark本地模式百万条左右中需要装JVM/Spark技术栈更有分量重量级Hadoop/Hive集群千万级以上高不适合毕设容易给自己挖坑我实际选择的是第一档Pandas做清洗和统计MySQL存数据后端提供JSON统计接口前端ECharts渲染图表。数据量控制在3到5万条职位数据启动分析任务后几秒钟就出结果现场演示不用等体验很关键。如果你想体现更“大数据”的能力可以在论文里补一步“当数据量增长时如何迁移到Spark”并给出替换逻辑说明这样既安全又有思考深度。2.3 核心表结构设计数据表设计是整个项目的命脉我一开始踩过坑比如薪资字段只存字符串“8k-15k”后面统计平均薪资直接卡壳。改过的核心表结构大概是这样users_user扩展Django自带User增加phone、avatar、resume一对一关联简历表users_resume姓名、毕业院校、专业、学历、工作年限、技能标签逗号分隔、自我评价、更新时间jobs_company公司名称、行业、规模、所在城市、简介jobs_job职位名称、公司外键、城市、薪资下限、薪资上限、学历要求、经验要求、技能标签、职位描述、发布时间、状态待审核/已发布/已下线jobs_apply_record学生用户外键、职位外键、投递时间、状态jobs_favorite用户外键、职位外键、收藏时间analysis_result分析类型、分析时间、结果JSON字段收藏和投递都只做“用户职位”的联合唯一约束避免重复投递。职位表里的salary_min、salary_max是整数字段单位用K分析薪资时直接聚合不用再做字符串解析。2.4 Django Admin后台的作用Django自带的Admin千万别浪费我在这里面做了大量定制答辩演示时非常加分。我在Admin里注册了职位、公司、用户、投递记录并配置了list_display、list_filter、search_fields还加了一个“导出Excel”的自定义Action。管理员登录后可以直接审核职位不用额外写一堆管理页面省下的时间都用来打磨数据看板了。3. 核心功能实现解析3.1 Django查询、删除对象的ORM基本功写Django项目ORM能力是基础中的基础。比如查询某个学生的所有投递记录我会用select_related把职位和公司一起查出来避免循环里逐条查数据库产生N1问题# 查询当前用户投递记录并带上职位、公司信息 records ApplyRecord.objects.filter( userrequest.user ).select_related( job, job__company ).order_by(-apply_time)删除对象时也要考虑外键关联。职位被删除前先清掉关联的投递记录和收藏记录否则会报外键错误或者留下一堆脏数据job get_object_or_404(Job, pkjob_id) ApplyRecord.objects.filter(jobjob).delete() Favorite.objects.filter(jobjob).delete() job.delete()这里我用的是物理删除生产环境更推荐加一个is_active字段做软删除只有管理员能真正删掉数据避免学生误操作把投递记录全搞丢。3.2 登录认证Cookie、Token与会话的取舍这个项目要有普通网页登录也要支持前端Ajax请求登录态方案我考虑过两种。第一种是用Django的sessioncookie简单且安全页面和Ajax自动携带csrftoken但纯接口调用时不够“现代感”。第二种是自写Token认证用户登录成功后生成一段随机Token存RedisKey为token:{user_id}同时写入HttpOnly的Cookie前端请求时携带后端写一个装饰器统一校验。from django.http import JsonResponse import secrets import redis r redis.Redis(hostlocalhost, port6379, db0) def login_view(request): if request.method POST: user authenticate(request, usernamerequest.POST[username], passwordrequest.POST[password]) if user: token secrets.token_hex(16) r.setex(ftoken:{user.id}, 86400, token) response JsonResponse({code: 0, msg: 登录成功}) response.set_cookie(auth_token, token, httponlyTrue, max_age86400) return response return JsonResponse({code: 1, msg: 用户名或密码错误})注意HttpOnly一定要开这样前端脚本读不到Cookie能防XSS窃取。项目上线时如果走HTTPS记得把secureTrue也加上。这里没有用JWT因为毕设里TokenRedis已经完全够用讲起来也容易。3.3 职位搜索与筛选从Q查询到分页排序职位检索是主功能搜索条件至少有四个维度关键词职位名或技能标签、城市、学历、工作经验。要实现“多个条件组合且条件都可选”我统一构造Q对象避免写一堆if else去拼字符串查询from django.db.models import Q def search_jobs(request): keyword request.GET.get(keyword, ) city request.GET.get(city, ) edu request.GET.get(education, ) min_salary request.GET.get(min_salary, 0) q Q(statuspublished) if keyword: q (Q(title__icontainskeyword) | Q(skill_tags__icontainskeyword) | Q(company__name__icontainskeyword)) if city: q Q(city__exactcity) if edu: q Q(education__exactedu) if min_salary: q Q(salary_max__gtemin_salary) job_list Job.objects.filter(q).select_related(company).order_by(-publish_time) paginator Paginator(job_list, 12) page_obj paginator.get_page(request.GET.get(page)) return render(request, jobs/job_list.html, {page_obj: page_obj})薪资筛选这里千万不要写成salary_min__gtemin_salary否则会把很多“上限满足但下限不够”的职位漏掉。我统一按salary_max__gte来判断意思是这个职位的薪资天花板能到用户期望值逻辑上更好解释。3.4 WebSocket实时推送从后台推数据到前端很多毕设做到WebSocket就发怵其实核心就是借助Django Channels实现“后台有数据变化前端页面自动收到通知”。我的场景是企业把投递状态改成“已通过”学生端页面上不需要手动刷新就能弹出状态更新提示。配置上需要装channels和daphne在settings.py里把ASGI_APPLICATION指到路由文件再配置Redis作为channel layer# settings.py INSTALLED_APPS [ daphne, ... channels, ] ASGI_APPLICATION config.asgi.application CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: {hosts: [(127.0.0.1, 6379)]}, } }Consumer端核心逻辑是让用户加入以自己ID命名的分组这样后端可以精准推给目标用户class NoticeConsumer(AsyncWebsocketConsumer): async def connect(self): self.user_id self.scope[url_route][kwargs][user_id] self.group_name fuser_{self.user_id} await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) async def send_message(self, event): await self.send(text_datajson.dumps({ message: event[message], time: event[time], }))在投递状态变更的视图里用channel_layer.group_send向目标用户推送消息。这里有个容易踩的坑Channels 4路由的写法跟旧版不一样用websocket_urlpatterns加ProtocolTypeRouter时注意URL里面不能写host只要写路径。3.5 Admin后台定制与Excel导出后台导出数据简直是毕设演示的“救命功能”老师问你“数据怎么管理”的时候直接打开管理后台导出一份Excel效果比干讲强很多。我写了一个Action把选中的职位记录用openpyxl导出def export_excel(modeladmin, request, queryset): wb Workbook() ws wb.active ws.append([职位名称, 公司, 城市, 薪资下限, 薪资上限, 发布时间]) for obj in queryset: ws.append([obj.title, obj.company.name, obj.city, obj.salary_min, obj.salary_max, obj.publish_time.strftime(%Y-%m-%d)]) response HttpResponse(content_typeapplication/vnd.ms-excel) response[Content-Disposition] attachment; filenamejobs.xlsx wb.save(response) return response export_excel.short_description 导出选中的职位为ExcelAdmin配置里把list_display、list_filter、search_fields全部设置好每天发布的海量职位就能按条件快速筛查和管理。3.6 数据看板接口设计数据看板是整个项目“大数据感”最强的页。我在后端写了一个聚合接口返回ECharts绘图需要的JSON数据前端通过fetch拉取后绘图。接口输出大致是这样的{ city_distribution: [{name: 北京, value: 6200}, ...], salary_avg: [{name: 北京, value: 18.5}, ...], skill_top20: [{name: Java, value: 2380}, ...], education_ratio: [{name: 本科, value: 62}, ...] }这个接口的数据不是每次都现场跑全量统计而是每天定时把最新的统计结果写入analysis_result表接口直接读表返回。这样页面响应速度稳定答辩演示的时候不会因为数据量大而转圈等待。4. 大数据处理链路详解4.1 数据采集爬虫与合规提醒数据主要分两类一类是公开职位信息另一类是模拟补全的数据。自己写爬虫的话用requests加BeautifulSoup就够了。结构很简单def fetch_jobs(): url https://example.com/jobs?city{} headers {User-Agent: Mozilla/5.0 ...} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.job-item): title item.select_one(.job-title).text.strip() city item.select_one(.job-city).text.strip() salary_str item.select_one(.job-salary).text.strip() # 解析薪资区间 yield parse_salary(salary_str)爬虫代码虽然看起来不多但实际采集时工程问题不少。第一必须控制请求频率加time.sleep随机延时文明采集第二严格遵守目标网站的robots.txt协议只采集公开信息不碰任何个人隐私数据第三采集结果先存CSV作为原始数据不要直接写数据库方便清洗环节重跑。合规性这点也会在论文里专门说明答辩时能体现出工程素养。4.2 数据清洗缺失值、重复值与薪资归一化拿到原始数据后最耗时间的就是清洗。我的固定动作是删除职位名称为空的记录按“公司职位城市”联合去重薪资文本统一从“8k-15k”这类格式拆成整数下限上限统一单位为K过滤掉薪资明显异常的数据比如下限大于100K或两倍中位数这些通常是虚构的高薪钓鱼信息学历、城市字段做规范化映射比如“本科及以上”和“本科”统一成“本科”import pandas as pd df pd.read_csv(data/raw_jobs.csv) df df.dropna(subset[title, company_name, city]) df df.drop_duplicates(subset[company_name, title, city]) def parse_salary(s): try: low, high s.lower().replace(k, ).split(-) return int(low), int(high) except Exception: return None, None df[salary_min], df[salary_max] zip(*df[salary_str].map(parse_salary)) df df.dropna(subset[salary_min, salary_max]) df df[(df[salary_max] 80) (df[salary_min] 0)] df.to_csv(data/clean_jobs.csv, indexFalse)清洗前的原始数据一定要保留一份答辩时展示“原始数据有异常值、清洗后数据分布更合理”这个过程比任何口头描述都有说服力。这个小细节在当时帮我加了不少印象分。4.3 岗位技能词频分析技能标签列存的是逗号分隔的字符串比如“Java,Spring,MySQL”。在做技能词频统计前我先把这些字符串全部拆分再用Counter统计Top20。这里直接贴一段核心逻辑from collections import Counter skill_counter Counter() for tags in df[skill_tags].dropna(): for tag in str(tags).split(,): tag tag.strip() if tag: skill_counter[tag] 1 top20 skill_counter.most_common(20)如果职位描述是长文本而不是标签那就需要jieba分词加停用词过滤技术点同样清晰。统计结果最终存入analysis_result表页面上用横向柱状图渲染。我建议用词云图展示效果更震撼但中文词云要注意指定中文字体文件路径不然页面上全是方块字。4.4 城市薪资与学历需求分析薪资分析要分城市看平均值和中位数避免北京一个高薪岗位把某城市平均薪资拉上天。实现上用groupby聚合city_salary df.groupby(city)[salary_max].agg([mean, median]).reset_index() city_salary.columns [city, avg_salary, median_salary]学历需求比例直接统计education字段的占比然后在前端用饼图展示。这里讲一个答辩技巧不要只报平均薪资要同时说明“中位数和平均值的差异反映了哪个城市的薪资分布更极端”这个细节能体现你真的理解统计口径而不仅仅是调用函数出图。4.5 为什么要轻量级实现而不是硬上Hadoop我记得做项目前也纠结过要不要上一套Hadoop集群后来想明白了在几万条数据规模下Hadoop的分布式优势根本发挥不出来光搭环境就得耗掉一半时间论文还得解释为什么用小数据集跑大集群。正确的姿势是用Pandas把整条链路跑通但在论文里写清楚“系统设计了向Spark/Hive迁移的扩展路径”例如数据量到百万级后将清洗和聚合任务迁移到Spark本地或集群模式存储层可换成Hive表。这样既保证了现实可行性又能体现对更大数据量场景的理解。这个“伸缩性”思路是答辩老师非常爱听的。5. 部署、排错、文档与答辩准备5.1 从本地到Linux服务器部署项目开发完我建议至少部署到一台Linux服务器云服务器学生机一个月几十块或者本地虚拟机也行因为部署过程本身就是毕设工作量的体现。标准流程是装Python、MySQL、Redis、Nginx创建虚拟环境pip install -r requirements.txt然后python manage.py migrate最后collectstatic收集静态文件。uWSGI和Nginx的配合是关键。Nginx只负责处理静态文件并把动态请求转发给uWSGI配置如下server { listen 80; server_name your_domain.com; location /static/ { alias /var/www/job_system/static/; } location /media/ { alias /var/www/job_system/media/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; uwsgi_read_timeout 60; } }settings.py里一定要改两处一是ALLOWED_HOSTS加上服务器IP或域名否则直接返回400 Bad Request二是DEBUG关掉配置好STATIC_ROOT。我当初部署时第一次访问页面静态文件全丢排查半天发现是Nginx的alias路径写错了少了一个斜杠。5.2 进程管理与定时统计任务服务器上的Django进程不能直接挂在终端窗口不然SSH一断服务就没了。我用了supervisor管理uWSGI进程配置简单而且崩溃了能自动拉起。定时统计任务更简单不用为了一个每日统计就上Celery除非你项目里已经有Celery和Redis直接用crontab跑一个管理命令就行0 3 * * * cd /var/www/job_system /usr/bin/python3 manage.py run_daily_analysis logs/analysis.log 21每天早上3点自动更新一次分析结果页面随时打开都是最新数据。这个定时任务机制在文档和答辩里都能体现系统的完整性。5.3 文档与论文写作技巧“程序文档”如果只是把代码贴进文档那就浪费了。我的文档分五大部分需求分析、系统设计、功能实现、测试记录、部署手册。需求分析里画系统用例图和角色权限表系统设计里放ER图、核心表结构、接口设计功能实现部分每个模块配一个核心代码片段加解释测试记录里保留真实测试用例和截图例如“用户登录失败时提示正确”“职位搜索页码越界时正常返回空页”部署手册直接照抄服务器上跑通的命令别人拿着能复现才算数。论文写作最容易被小看的其实是截图数量。我当时把每个功能页面、每个数据统计图表、后台管理列表都截了图并统一命名、标注功能点。最后排版时发现图片根本不够用又回头补拍了一轮。做这类图文并茂的毕设建议从编码第一天就开始随手截图不然最后真的会补到怀疑人生。5.4 答辩现场怎么演示和讲代码演示顺序一定要设计成“业务闭环”我当时的顺序是注册新账号 → 完善简历 → 在职位列表搜“Java”并筛选城市 → 进入详情页投递简历 → 切到企业账号查看收到简历把状态改为“已通过” → 切回学生账号页面不刷新收到WebSocket消息提示 → 打开数据看板展示“Java”在技能榜单第几名、哪个城市平均薪资最高。这套演示流程连起来大约5分钟走完以后答辩老师对大数据库的印象基本就立住了。准备追问问题时要能答上几个高频问题“你的数据从哪来的”公开数据加模拟补全清洗后入库“哪些指标说明你的系统效果好”平台职位覆盖率、用户投递转化率“为什么选Django”生态成熟、自带Admin、ORM好用适合快速搭建业务系统。回答时别背概念用自己的代码逻辑解释已经足够有说服力。5.5 高频问题排查速查表开发到部署这一路我把最常遇到的问题整理成了表复制走直接照着查就行现象直接原因解决办法页面返回400 Bad RequestALLOWED_HOSTS未配置在settings里加上IP或域名静态文件全部404Nginx的static路径配置错误检查location /static的alias路径MySQL写入中文乱码数据库字符集不是utf8mb4建库时指定CHARACTER SET utf8mb4uWSGI启动正常但访问502socket路径/权限不一致统一uWSGI socket路径和Nginx的uwsgi_passWebSocket连接后秒断ASGI路由没生效或Redis没开检查ASGI_APPLICATION配置和Redis进程crontab任务不执行环境变量或Python路径不对使用虚拟环境里的绝对路径PythonPaginator翻页越界报错页码超出总页数对页码做try/except或get_page容错Excel导出后中文乱码单元格未正确设置字符串编码使用openpyxl统一写入并设置UTF-8删除职位时报外键错误关联表数据未清理先删投递记录和收藏再删职位排查这些问题的通用思路是先看日志再看配置最后看权限。Django的DEBUGTrue时错误页面会直接告诉你具体文件和行号部署到服务器后记得打开日志文件/var/log/nginx/error.log和uWSGI的日志输出90%的问题一眼就能定位。最后一个直接的体会做完这个项目再回头看最大的收获不是“会写Django”而是学会了一条完整的技术链路怎么走需求拆解确定表结构爬虫采集原始数据清洗入库业务功能开发统计聚合可视化展示再部署到服务器让整套系统跑起来。每一次数据格式调整都要同步改前端图表每一次状态变更都要考虑消息通知这些细节单独看都不难串在一起才是真实的项目开发经验。如果你现在正要开始类似题目我想说一句实在话不要一开始就想着把功能堆得多花哨先把“学生注册→投递→企业处理→数据看板”这条主链路跑通再往上面加WebSocket、Excel导出这些加分项。源码、文档、代码讲解这类交付物拆开来看就是让接手的人能跑起来、能看懂、能照着改。把这三件事做好这个毕设基本就成了。希望这份从头到尾的记录能帮你少走一段弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →