简介一份基于Python的电影数据分析与可视化系统完整项目面向具备基础Python与Django知识、希望实战数据采集与可视化流程的学习者。系统涵盖用户登录、爬虫采集电影数据、电影简介与TOP10推荐、模糊搜索以及基于关键词的数据分析可视化界面可视化部分包含年代/产地/类型饼图柱状图、评价词云图等可帮助理解从爬虫、存储到展示的完整链路。压缩包共42个文件含8个py源码、11个html模板、8个csv数据、1个sqlite3数据库及环境安装操作说明等整体299.78MB目录结构清晰便于按模块对照学习。目前已有1040人学习下载适合课程设计、毕业设计或数据分析入门练手读者可基于源码快速启动项目并借鉴其中的爬虫、用户系统与可视化设计思路进行二次开发。1. 电影数据分析与可视化系统拿到手能跑出什么做过课程设计或数据分析入门的人应该都有体会电影数据到处都是但真要按年代、产地、类型拆开看还得自己搭一套能点能查能出图的东西。这套基于 Python Django 的电影数据分析与可视化系统就是干这个的——它把用户登录、爬虫采集、TOP10 推荐、搜索、饼图柱状图词云分析串成了一条完整链路下载下来不是看截图是能直接跑起来的 Web 应用。项目里有现成的 sqlite3 数据库和 CSV 数据文件不用你费劲造数据启动后登录进去就能看到图表。整体技术栈是 Django pandas ECharts机器学习用得不多更多是统计排序和文本分词对新手友好对熟手来说也是一个能快速改造成自己项目的底座。适合三类人拿它当课程设计或毕设骨架的在校生、想学 Django 业务闭环的转行者、以及需要一份可复现示例来理解 Web 可视化怎么落地的数据分析从业者。2. Django 项目结构与数据入库先把数据库跑起来2.1 拆解目录哪些文件承担什么职责拿到压缩包后先别急着跑命令花两分钟把目录结构看明白后面排查问题会省很多事。项目核心是一个标准 Django 工程加上一层数据处理脚本和一批数据文件。我按功能把它分成四块Django 工程本体、数据导入脚本、原始数据、模板与静态资源。目录/文件职责说明app/views.py所有业务视图登录、搜索、推荐、图表数据接口app/urls.pyURL 路由映射把每个请求分发给对应视图app/settings.py项目配置数据库、模板路径、静态文件、应用注册create_data.py一键导入脚本把 CSV 数据写入 sqlite3movie_data/*.csv五个原始数据文件电影、用户、评分、评论、演职人员templates/*.html页面模板含图表页面和搜索页面static/img、static/css静态资源目录db.sqlite3已初始化的数据库文件含原始数据和导入后的数据manage.pyDjango 管理入口启动、迁移、建应用都靠它这里要特别提醒一个点db.sqlite3是已经跑过迁移和导入的成品数据库。理论上你拿到项目后直接python manage.py runserver就能起服务。但我见过太多人下载后在另一台电脑上启动报错原因往往是数据库文件没拷贝完整或者拷贝了但 Django 的迁移记录和数据库不匹配。所以后面我会带你走一遍从 CSV 到数据库的完整导入流程这个流程走通了你对项目的数据流就有底了。manage.py是 Django 约定的入口启动服务、执行迁移都是通过它调用app/settings.py里的配置。create_data.py独立于 Django 工程之外它通过DJANGO_SETTINGS_MODULE环境变量加载项目配置然后读取 CSV 写入数据库算是一个独立的数据管道脚本。2.2 数据从哪里来CSV 文件设计与 sqlite3 入库流程movie_data目录下放着五个 CSVmovies.csv保存电影基本信息users.csv保存用户账号ratings.csv保存用户对电影的评分记录comments.csv保存电影评论内容person.csv保存导演和演员信息。这种拆分方式是典型的星型结构电影是主表评分和评论是事实表搜索时通过关联字段把它们串起来。很多新手拿到项目后习惯直接往数据库里塞数据但其实create_data.py里已经写好了导入逻辑核心思路是用 pandas 读取 CSV然后批量写入 Django ORM 对应的模型。它的好处是CSV 是纯文本跨机器迁移、多人协作都不会有数据库版本冲突而且数据文件放在项目里随时可以删掉db.sqlite3重新导入等于有了后悔药。常见的导入代码逻辑类似这样# create_data.py 核心逻辑 import os import django import pandas as pd os.environ.setdefault(DJANGO_SETTINGS_MODULE, app.settings) django.setup() from app.models import Movie # noqa: E402 def import_movies(csv_path): df pd.read_csv(csv_path, encodingutf-8-sig) movies [] for _, row in df.iterrows(): movies.append(Movie( titlerow[title], yearrow[year], regionrow[region], genrerow[genre], ratingfloat(row[rating]) )) Movie.objects.bulk_create(movies, batch_size500) print(f导入 {len(movies)} 条电影数据)这里先设置DJANGO_SETTINGS_MODULE环境变量再调用django.setup()否则 Django 的 ORM 无法在独立脚本中工作。pandas 读取时指定encodingutf-8-sig是为了兼容部分 CSV 自带 BOM 头的情况写入时用bulk_create而不是逐条save()批量插入的性能差距在小数据量时看不出来但数据量上到几万条就很明显了。batch_size500表示每 500 条提交一次事务避免一次提交过多占用内存。如果你要导入自己的电影数据只要保证 CSV 列名和上面代码里的字段名一致即可。注意年份字段在 CSV 里可能是整数也可能是带括号的字符串比如 2023(中国大陆)导入前先清洗一下再用int()转换否则 pandas 推断类型时会踩坑。2.3 启动前的环境准备三行命令跑通整套服务数据文件就位后启动流程其实就三步创建数据库表结构、导入数据、启动服务。Django 的迁移命令负责把模型转换成数据库表数据导入脚本负责填充内容最后用runserver起一个本地开发服务器。cd 项目根目录 pip install django pandas jieba wordcloud python manage.py migrate python create_data.py python manage.py runserver 8000第一条命令安装依赖migrate会读取app/models.py里定义的模型在 sqlite3 中创建对应的表create_data.py把 CSV 数据写入这些表runserver 8000启动开发服务器浏览器访问http://127.0.0.1:8000即可看到登录页。如果db.sqlite3已经在项目里且完整migrate之后也可以直接跑不会报错。开发者模式下的runserver会自动监听代码变更并重启改完视图函数刷新页面就看到效果。如果你改了settings.py里的数据库配置或者新增了模型字段需要重新跑makemigrations和migrate才会生效。这时候访问首页如果出现样式丢失多半是静态文件路径问题我会在避坑章节专门讲。3. 登录、推荐与搜索三条业务链路怎么串起来3.1 用户登录Django Session 与会话保持登录模块是整套系统的入口逻辑上并不复杂用户在login.html输入用户名和密码前端提交到登录视图视图用 Django 自带的认证系统校验用户表通过后写入 session后续请求携带 session 信息即可识别身份不需要每次请求都重新登录。# app/views.py 登录视图 from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(home) else: return render(request, login.html, {error: 用户名或密码错误}) return render(request, login.html)这里authenticate是 Django 提供的认证函数会自动根据用户名和密码去数据库里查返回用户对象或Nonelogin函数将用户 ID 写入 session后续视图里用request.user就能拿到当前用户。注意项目里的用户数据是放在users.csv里的导入时如果没有调用create_user而是直接create密码字段可能不是哈希值会导致登录失败这个坑后面细说。对于课程设计级别的项目Django 自带 auth 系统完全够用。不需要自己设计用户表、不需要写 token 生成逻辑Session 机制帮你把状态管理的活干完了。如果你有加验证码的习惯可以在前端加个滑块或图形验证码后端只需在登录视图验证前多写一个判断。3.2 TOP10 推荐按评分与偏好排序的简单可行做法推荐模块的要求是根据用户偏好展示 TOP10 的电影。真实工业界做推荐系统要用协同过滤、矩阵分解甚至深度模型但在这套项目的场景下一个可解释、可落地的方案是基于用户历史高分记录的偏好加权排序先看用户在评分表里给过高分的电影从这些电影里提取高频导演和类型构成偏好模板再用模板去给全量电影打加权分。# app/views.py 推荐逻辑 from django.db.models import Avg def recommend_movies(request, user_id): high_ratings Rating.objects.filter( user_iduser_id, score__gte4 ).select_related(movie) genre_weight {} director_set set() for r in high_ratings: for g in r.movie.genre.split(,): genre_weight[g] genre_weight.get(g, 0) 1 director_set.add(r.movie.director) candidates Movie.objects.annotate(avg_scoreAvg(rating)) for movie in candidates: base movie.avg_score or 0 prefer sum(1 for g in movie.genre.split(,) if g in genre_weight) * 0.5 if movie.director in director_set: prefer 1 movie.score base prefer return sorted(candidates, keylambda m: m.score, reverseTrue)[:10]这段代码的核心是两步第一步从用户高分记录里提取偏好信息类型权重和导演集合第二步遍历候选电影把平均评分和偏好加分相加作为综合得分取前 10 名返回。prefer的加分数值没有标准答案0.5 分表示一个类型命中约等于评分多 0.5 分你可以根据数据分布调整。如果你的项目数据量大全部电影遍历会慢这时候可以用order_by(-avg_score)[:100]先取平均分前 100 的候选池再做偏好加权效率更高且推荐质量损失很小。这种简单方案和协同过滤的最大区别在于它不需要用户之间的行为相似度计算也不依赖共现矩阵单用户冷启动时依然能按评分给出结果。作为课程设计或原型验证它的解释性和效果已经足够而且方便在答辩时讲清楚逻辑。3.3 搜索功能电影名、导演、演员的模糊匹配搜索功能的要求是按照电影名、导演、演员模糊匹配。Django ORM 里做模糊匹配用icontains如果需要同时匹配多个字段就用Q对象把条件组合起来彼此之间是 OR 关系。这里的重点在于导演和演员信息不一定直接挂在电影表上可能需要通过中间表关联。# app/views.py 搜索视图 from django.db.models import Q def search_movies(request): keyword request.GET.get(q, ).strip() if not keyword: return render(request, search.html, {movies: []}) movies Movie.objects.filter( Q(title__icontainskeyword) | Q(director__icontainskeyword) | Q(actor__icontainskeyword) ).distinct().order_by(-rating) return render(request, search.html, {movies: movies})title__icontains会转化为 SQL 里的LIKE %keyword%大小写不敏感distinct()是必须的因为电影和演员是多对多关系时联表查询会产生重复行。如果你在person.csv里保存了每个电影的演员列表并且导入时已经关联好那么actor字段可以直接用icontains如果数据还没关联需要先通过中间表 join这时候把Movie换成Q(Movie.actors__name__icontainskeyword)这样的写法。模糊搜索的性能隐患在于LIKE %..%走不了索引数据量上到百万级会明显变慢。要是以后数据量大了可以考虑给标题字段加trigram索引或者换成search_vector全文检索但这套项目当前的数据量完全不用操心。搜索页面前端是把返回的movies列表渲染成卡片展示封面、评分、年份等信息模板里用{% for movie in movies %}循环即可。4. 可视化页面ECharts 饼图柱状图与评论词云4.1 图表数据的聚合查询把数据库变成 JSON可视化的数据源不能在页面里现算必须在后端用分组聚合查询算好再序列化成 JSON 交给前端渲染。饼图和柱状图分别对应年代分布、产地分布、类型分布这三类统计都用 Django 的annotateCount完成。评论词云则需要把comments.csv里的评论文本取出来分词后按词频统计。# app/views.py 图表数据接口 from django.db.models import Count from django.http import JsonResponse def chart_data(request): year_data Movie.objects.values(year).annotate( countCount(id) ).order_by(year) genre_data {} for movie in Movie.objects.all(): for g in movie.genre.split(,): genre_data[g] genre_data.get(g, 0) 1 return JsonResponse({ years: list(year_data), genres: [{name: k, value: v} for k, v in genre_data.items()] })values(year).annotate(countCount(id))是 SQL 里的GROUP BY year返回每个年份对应的电影数量。这里有一个非常容易踩的点values()之后annotate的字段会自动带上分组条件如果你还想查region必须在values(year, region)里同时写出来否则结果会多一层分组粒度。类型分布之所以用 Python 循环而不是 ORM 聚合是因为一个电影可能属于多个类型类型字段是用逗号拼接的字符串SQL 层面不好直接按分隔符分组循环统计更稳妥。接口的 URL 路由在app/urls.py里配置# app/urls.py from django.urls import path from . import views urlpatterns [ path(chart-data/, views.chart_data, namechart_data), path(cloud-data/, views.cloud_data, namecloud_data), ]配置好路由后前端直接访问/chart-data/就能看到 JSON 格式的数据。调试时建议先用浏览器访问这个地址确认数据正常后再去调前端图表这能帮你把后端数据问题和前端渲染问题隔离开。4.2 前端图表渲染ECharts 饼图柱状图与词云配置前端图表页面用的是 ECharts饼图和柱状图通过echarts.init初始化容器然后setOption加载数据。词云部分依赖echarts-wordcloud插件它的数据结构是[{name: 电影名, value: 词频}]和后端统计结果天然对应。图表页面加载完成后通过fetch请求数据接口拿到 JSON 后更新图表配置。// templates/fc_charts.html 核心脚本 fetch(/chart-data/) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(genreChart)); chart.setOption({ series: [{ type: pie, data: data.genres, radius: [30%, 70%] }] }); }); fetch(/cloud-data/) .then(res res.json()) .then(data { const cloud echarts.init(document.getElementById(cloudChart)); cloud.setOption({ series: [{ type: wordCloud, shape: circle, sizeRange: [14, 60], data: data.words }] }); });饼图的radius: [30%, 70%]表示内径和外径比例这就是环形饼图的配置方式。词云里的sizeRange控制字号范围值越小越追求展示密度值越大越突出高频词shape可选circle或diamond等形状影响词云整体轮廓。如果你的词云数据量很大建议先按词频排序后取前 200 个词再传给前端否则渲染性能会明显下降。词云的数据准备在后端完成把comments.csv里所有评论拼接成一个长文本用 jieba 分词后过滤掉停用词stopwords.txt再统计每个词的频率最后按频率降序返回。停用词表是这个项目的关键资源没它你会在词云里看到一堆的了就这类无意义词图表瞬间变得没有信息量。4.3 图表页面的模板组织与数据联动templates目录下的fc_charts.html是图表总页面fc_show.html负责详细展示fc_search.html负责搜索结果。图表页面之间可以传参联动比如点击饼图某个类型后跳转到该类型下的电影列表。实现方式是用 ECharts 的on(click)事件捕获点击项然后通过 URL 参数跳转。!-- 模板中联动跳转示例 -- script chart.on(click, function(params) { window.location.href /search/?q encodeURIComponent(params.name); }); /script这个做法把图表和搜索模块打通了用户点击类型图中的喜剧就能直接看到喜剧电影列表交互上比孤立的图表更有说服力。如果你是拿这套系统做答辩演示这个联动行为一定要展示出来很容易成为加分点。搜索接口那边已经支持模糊匹配所以这里只需要把params.name作为q参数传过去即可不需要额外开发接口。5. 避坑与排查跑这套系统的五个高频问题5.1 登录一直提示用户名或密码错误现象用users.csv里导入的账号登录数据库里明明有记录但页面一直报错。原因CSV 里的密码字段是明文文本导入时如果用Movie.objects.create()或User.objects.create()直接写入Django 的authenticate()函数会默认拿输入密码去和数据库里的哈希值比对明文永远对不上。这是 Django 的设计安全策略不是 bug。解决导入用户数据时使用create_user()方法或者手动调用set_password()对密码做哈希后再保存。代码上这样改from django.contrib.auth.models import User def import_users(csv_path): df pd.read_csv(csv_path) for _, row in df.iterrows(): user, created User.objects.get_or_create(usernamerow[username]) user.set_password(row[password]) user.save()我一般会在create_data.py里单独加一个import_users函数和电影数据导入分开执行这样只改密码逻辑时不用重新导全量数据。5.2 图表页面打开是空白的控制台有报错现象页面能渲染出来但饼图和柱状图区域是空白打开浏览器开发者工具看到TypeError或Cannot read properties of undefined。原因大概率是后端接口返回的数据结构前端没有匹配上。比如后端返回的字段名是genres前端代码读的是data.genreList两边对不上或者某个分组结果里有None值前端直接访问属性时报错。解决先直接访问/chart-data/看 JSON 结构确认字段名和层级再回到前端页面核对脚本里的引用。我在调试这种问题时会先输出console.log(data)看一眼实际字段再决定是改后端序列化还是改前端取值。另外ECharts在容器没有高度时也会渲染失败检查 CSS 里是否给图表容器设置了height: 400px之类的固定高度没有的话图表会静默失败。5.3 词云显示一堆方块中文全是乱码现象词云图能渲染但上面的字全是方框一个中文都认不出来。原因ECharts 词云插件绘制文字时用的是 canvas 绘制如果浏览器或操作系统的默认字体不包含中文字符就会以方框占位。这和 jieba 分词无关分词结果是没问题的问题出在字体上。解决在 ECharts 词云的textStyle里显式指定中文字体textStyle: { fontFamily: Microsoft YaHei, PingFang SC, sans-serif }Windows 上Microsoft YaHei是微软雅黑macOS 上用PingFang SC。如果部署在 Linux 服务器上需要先检查系统有没有安装中文字体没有的话用fc-list :langzh查一下再apt-get install fonts-wqy-microhei安装文泉驿字体否则线上环境大概率还是方块。5.4 重新换电脑部署后页面打不开提示 Table not found现象把项目拷贝到另一台电脑运行runserver后访问页面报错no such table: app_movie。原因db.sqlite3文件没有拷贝完整或者拷贝了但它是旧版本的库缺少后续新增的表。sqlite3 是单文件数据库拷贝时只拷了数据库文件但没拷movie_data目录或者migrate没执行都会出现表结构缺失。解决最简单的方式是删掉db.sqlite3重新执行migrate create_data.py让数据库从代码和 CSV 里完全重建。这一步能跑通说明你的数据链路是完整的后面想迁移到 MySQL 也更容易。记住拿到任何 Django 项目第一件事不是启动而是确认数据库能不能从零重建。5.5 页面样式全乱了只有文字没有图片现象功能都能用但页面没有任何样式图片图标全部加载失败。原因Django 的开发服务器在DEBUGTrue时会自动服务静态文件但它要求settings.py里正确配置STATICFILES_DIRS指向实际静态文件目录。如果模板里用的是{% load static %}和{% static css/base.css %}而static目录路径配置不对样式文件就找不到。解决检查settings.py里这几行配置STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static]确认项目根目录下有static文件夹且模板文件里通过{% static %}引用。如果项目部署在 Nginx 上且DEBUGFalse还需要先跑python manage.py collectstatic把静态文件收集到一个统一目录再让 Nginx 托管该目录这是一个典型的开发环境正常、生产环境失效的场景。6. 进阶玩法给推荐和图表加点工程感6.1 把数据源从 sqlite3 切成 MySQL如果你打算把这个项目写进简历建议把数据库从 sqlite3 换成 MySQL这一步能体现你对生产环境差异的认知。操作不复杂先在 MySQL 里建好数据库然后在settings.py里替换数据库配置最后重新跑迁移和导入脚本就行。DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: movie_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }换库时要注意MySQL 默认字符集要设成utf8mb4否则中文评论词云的数据进去后再查出来可能乱码。另外create_data.py里 pandas 读取 CSV 后写入的逻辑不用改ORM 帮你屏蔽了底层数据库差异。我第一次迁移时翻过车——数据库里表建好了但数据导入中途崩了原因是 CSV 里的某个电影标题包含了 MySQL 不认的特殊字符。后来我在导入时加了异常捕获单条失败打印日志继续下一条数据管道才稳定下来。6.2 把推荐逻辑从示例换成参数可调现在推荐逻辑里的偏好加权系数是写死在代码里的你可以做成可配置的方便演示时调参。最简单的做法是把它放到 Django 的settings.py里或者做一个管理页面来调整。我建议至少把类型命中加分的权重系数暴露出来答辩时现场调整这个参数观众能直观看到推荐列表的变化比嘴上讲原理更有说服力。我把prefer加权封装成了一个函数运行时输出当前权重配置到日志方便对比不同参数下的排序结果。同时加了缓存同一个用户在一小时内访问推荐页直接返回缓存列表避免反复遍历全量电影。现在项目里已经有recommend_movies这个函数的雏形你只需要在函数入口加一个缓存判断用 Django 的cache框架就能实现。6.3 验证项目健康的自查步骤项目跑通后我习惯按一套固定顺序做自查先确认数据库能重建删掉db.sqlite3跑通migrate和create_data.py再验证接口数据访问/chart-data/看 JSON 是否完整最后走一遍核心业务流程——登录、搜索、查看推荐、切换图表。这套流程跑完没报错项目才算真正可用。以前我拿到别人的 Django 项目第一反应是直接复制数据库文件结果换环境后经常翻车后来强制自己每次都从 CSV 开始重建数据链路通不通一眼就看出来了。从那以后我接手的每个 Django 项目都会先写一个一键重建脚本reinit.sh里把rm db.sqlite3 migrate create_data.py串起来省得每次手动敲三条命令。这个习惯帮我避免了不少部署现场的黑匣子问题希望也能帮到你。本文还有配套的精品资源点击获取
