资讯详情

Django电影数据分析可视化系统:从爬虫采集到词云展示的实现

📅 2026/10/31 21:41:32 | 华诺云谱 👁 阅读
Django电影数据分析可视化系统:从爬虫采集到词云展示的实现
简介一份基于Python Django框架实现的电影数据分析与可视化系统代码包面向数据分析、爬虫及Web可视化初学者可用来学习从数据采集到前端展示的完整流程。压缩包共42个文件、约299.78MB主要包含HTML模板、Python源码、CSV数据文件、SQLite数据库以及环境安装说明等目录采用templates、static、data等分类组织模板、样式与数据分离便于按模块定位代码。系统涵盖用户登录、电影爬虫、TOP10推荐、模糊搜索和数据可视化分析界面能够按电影年代、产地、类型等维度生成饼图、柱状图并通过词云图分析电影评价从数据入库到图表渲染一应俱全。已有1040人学习下载代码内置爬虫与预处理脚本、停用词表和示例数据操作说明与初始数据库一并附赠适合在真实数据集上动手复现也可作为课程设计或毕业设计的参考工程。1. 先放结论电影数据分析系统的难点不在画图在数据进库先放个结论这套基于 Python Django 的电影数据分析与可视化系统难点不在画图在数据怎么干净地进库。很多人以为写个脚本读一遍 CSV 就能出图表真上手才发现登录要接 session、搜索要拼模糊匹配、词云要切中文还要过滤停用词前端还得有一个能交互的展示看板。我拆的这份工程恰好把这些环节完整串了一遍爬虫采集、CSV 中转、SQLite 存储、用户登录、TOP10 推荐、按片名导演演员的模糊搜索以及年代、产地、类型的饼图柱状图和评论词云图。适合正在做课程设计、想参考一套完整工程链路、或者打算把 Django 项目改造成自己分析系统的从业者。下面按我复现时的顺序来拆。2. 搭骨架从 manage.py 到 runserver 的启动底子与建表顺序拿到工程后第一件事不是看代码而是先把这个工程的布局摸清楚。Django 项目上手最大的障碍是「文件这么多谁管谁」只要把入口、配置、数据、模板这四层分清楚后面所有的操作都会顺。2.1 先读懂这份工程的文件布局打开目录后我按「从哪里启动、哪里配、哪里存数据、哪里放页面」四个维度把文件归了一下类整理成下面这张表路径/文件作用复现时要留意的点manage.pyDjango 命令行入口启动服务、建表、检查配置全靠它所有 manage 命令都要在项目根目录执行app/项目配置和视图逻辑所在地含 settings.py、urls.py、views.py、wsgi.py、asgi.pyDjango 项目包不是普通业务 appcreate_data.py读取 movie_data 下的 CSV把数据清洗后写入 SQLite删掉 db.sqlite3 后靠它重建数据db.sqlite3系统的数据库文件用户数据、电影数据都存在这里可以删除重建也可以直接用现成的movie_data/爬虫原始数据含 movies.csv、users.csv、person.csv、ratings.csv、comments.csv新爬的数据先进这里再跑脚本入库data/运行期依赖文件含 tasks.csv、stopwords.txt 等stopwords.txt 是词云过滤停用词的关键templates/页面模板base.html 是公共框架fc_*.html 是各功能页fc_charts.html 是可视化主页面static/项目的 css、图片等静态资源页面样式丢失时先查这里和 settings 配置这十个文件/目录里最核心的关系在 app、templates、movie_data、create_data.py 四者之间。Django 的常规逻辑是请求先进入 urls.py 路由由 views.py 里的视图函数处理视图从数据库里取数据再渲染 templates 下的模板页面引用的静态样式从 static 读。而 create_data.py 是一个不经过 HTTP 的独立脚本它直接读取 movie_data 里的 CSV 文件把数据灌进 db.sqlite3。好处是「可重建」哪天数据库搞坏了删掉 db.sqlite3重新跑一次 create_data.py整个系统回到初始状态。这里要注意一个细节工程里有两个看起来很接近的目录movie_data 和 data。我复现时的理解是movie_data 是爬虫抓回来的原始数据文件data 里放的是运行时依赖的字典和任务配置比如停用词表。实际改数据时优先改 movie_data 下的 CSV不要动 data 下的配置。2.2 环境安装与启动从 venv 到 runserver 的完整命令项目里的「环境安装.docx」和「环境启动操作.txt」已经把依赖和启动顺序写成文档了我按自己的习惯重新走了一遍推荐的执行顺序如下。先建虚拟环境避免把依赖装到系统 Python 里后面部署到服务器时会清爽很多。# 1. 创建虚拟环境Windows 和 Linux 都适用 python -m venv venv # 2. 激活虚拟环境 # Windows 下用这条 venv\Scripts\activate # macOS / Linux 下用这条 source venv/bin/activate # 3. 安装核心依赖Django 是骨架pandas 处理 CSVjieba 分词wordcloud 生成词云 pip install django pandas jieba wordcloud # 4. 创建 Django 内置表auth、session 等登录相关表在这一步生成 python manage.py migrate # 5. 执行数据创建脚本把 movie_data 下的 CSV 灌进 SQLite python create_data.py # 6. 启动开发服务器默认监听 8000 端口 python manage.py runserver每一条命令都不要跳过。尤其是第 4 步 migrate很多人以为数据全在 create_data.py 里其实 Django 自带的用户认证表是在迁移时才建出来的。登录功能没有 auth 相关表启动后访问登录页面会直接报 no such table 的错误。第 5 步 create_data.py 是数据入库的总入口执行前确认 db.sqlite3 和 create_data.py 在同一个目录下脚本里读 CSV 的路径用的是相对路径位置不对会找不到文件。第 6 步跑起来后浏览器访问 http://127.0.0.1:8000 就能看到登录页。注意migrate 必须先于 create_data.py 执行。create_data.py 写入数据时依赖表结构已存在而内置的 user 表是登录功能的基础顺序反了会出现表找不到或外键报错。如果启动后页面能打开但登录报错先回来看第 4 步有没有真正执行成功。这一步做完系统骨架已经立住了但页面上还没有什么可视化内容——因为数据来源这关还没过这就是下一章要拆的部分。3. 数据管道爬虫 CSV 中转 create_data.py 一键入库这个系统的数据链路分成两段爬虫把网页内容抓到 CSVcreate_data.py 把 CSV 洗进 SQLite。这条管道设计得很适合拿来学习因为它把「采集」和「入库」彻底解耦了。3.1 为什么爬虫结果先落 CSV 再进库很多人在这一步会纠结为什么不直接在爬虫里连数据库抓一条写一条我最初也这么干过但跑了一天后换了方案。先落 CSV 再入库有三个实打实的好处。第一是可重跑。反爬严格的站点重复请求很容易被封CSV 是纯文本解析挂了重跑一遍成本极低。第二是可检查。CSV 可以用文本编辑器直接打开入库之前先肉眼扫一遍字段和空值比入库之后再排查快得多。第三是可接多数据源。列表页、详情页、评论页通常是不同页面分开抓、分开存最后统一在 create_data.py 里合并逻辑更干净。爬虫部分常见做法是用 requests 拿页面BeautifulSoup 解析节点。下面这段代码是我按这份工程的数据字段改的示例目标是抓取每个电影条目并追加到 movie_data 下import csv import time import requests from bs4 import BeautifulSoup def fetch_detail(item_id): # 列表页拿到详情页 URL 后逐个请求并解析 url fhttps://example-movie-site.com/detail/{item_id} resp requests.get(url, headers{User-Agent: Mozilla/5.0}, timeout10) soup BeautifulSoup(resp.text, html.parser) return { title: soup.select_one(.title).text.strip(), year: soup.select_one(.year).text.strip(), country: soup.select_one(.country).text.strip(), genre: soup.select_one(.genre).text.strip(), rating: soup.select_one(.rating).text.strip(), director: soup.select_one(.director).text.strip(), actors: soup.select_one(.actors).text.strip(), } def save_to_csv(items, pathmovie_data/movies.csv): with open(path, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, year, country, genre, rating, director, actors]) writer.writeheader() writer.writerows(items) # 常见做法按页抓取每次抓完 sleep 一下避免请求过快 for page in range(1, 11): items [fetch_detail(i) for i in range((page - 1) * 20, page * 20)] save_to_csv(items, movie_data/movies.csv) time.sleep(1)这段代码的字段和 movie_data/movies.csv 是能对上的title、year、country、genre、rating、director、actors。这里有两个关键的参数设置。第一个是 headers 里的 User-Agent至少要伪装成一个浏览器否则很多站点直接返回 403。第二个是 encodingutf-8-sig这个编码能避免 Excel 打开 CSV 时中文乱码也是后面 create_data.py 读取时的前提。time.sleep(1) 是给目标站点留缓冲真实爬虫里还要在解析节点外套一层 try/except否则一个页面超时整批数据就中断了。3.2 create_data.py 的三件事建表、去重、批量写入CSV 只是中间产物真正决定系统里有什么数据的是 create_data.py。这个脚本要做三件事加载 Django 配置、读取 CSV、批量写入数据库。核心逻辑我拆成了下面这段# create_data.py 主流程 import os import django # 1. 指定 Django 配置模块让脚本可以脱离 runserver 独立运行 os.environ.setdefault(DJANGO_SETTINGS_MODULE, app.settings) django.setup() import pandas as pd from app.models import Movie # 假设模型类叫 Movie字段与 CSV 对应 def load_csv_to_df(path): # 2. 读取 CSVutf-8-sig 防止中文乱码 df pd.read_csv(path, encodingutf-8-sig) # 丢弃完全空的行避免 None 入库导致前端报错 df df.dropna(howall) return df def sync_movies(): df load_csv_to_df(movie_data/movies.csv) # 3. 字段级空值补为空字符串不要写入 None df df.fillna() objs [ Movie( titlerow[title], yearrow[year], countryrow[country], genrerow[genre], ratingrow[rating], directorrow[director], actorsrow[actors], ) for _, row in df.iterrows() ] # 4. 批量写入主键冲突时直接跳过不影响后续数据 Movie.objects.bulk_create(objs, ignore_conflictsTrue) if __name__ __main__: sync_movies()这段脚本有三个容易被忽略的点。第一个是os.environ.setdefault和django.setup()这是脚本脱离 runserver 独立运行的前提很多新手脚本里缺了这两行一执行就报 django.core.exceptions.ImproperlyConfigured。第二个是bulk_create(ignore_conflictsTrue)它表示遇到主键冲突就跳过这一条不会中断整批写入所以脚本可以反复执行。第三个是空值处理的两道防线dropna(howall)去掉整行空记录fillna()把字段级空值补齐否则数据库里存了 None前端模板渲染时会显示一个刺眼的 None 字样。这套管道设计得比较基础但很实用。如果以后数据量大到百万级可以把bulk_create换成批量分片写入一次写 500 条再加事务包裹速度能再上一个台阶。对于课程设计和日常分析场景当前方案足够稳。4. 核心链路登录、模糊搜索、TOP10 推荐与可视化接口的串联数据库里有数据之后系统的核心链路就清晰了。这一章我会按「用户登录 → 搜索 → TOP10 推荐 → 图表接口」的顺序来拆每一段都是可以直接拿去改的代码。4.1 登录与搜索session 与 Q 模糊查询登录功能在 Django 里有现成的认证体系。工程里的 users.csv 提供了用户数据通过 create_data.py 灌入库后用 auth.authenticate 校验即可。登录成功后调用 auth.login把登录态写进 session后续页面就能通过 request.user 识别当前用户。参考实现如下# views.py 里的登录视图 from django.shortcuts import render, redirect from django.db.models import Q, Count from django.http import JsonResponse from django.contrib import auth from .models import Movie # 模型类按工程实际导入路径为准 def login(request): if request.method POST: username request.POST.get(username, ).strip() password request.POST.get(password, ).strip() user auth.authenticate(usernameusername, passwordpassword) if user is not None: auth.login(request, user) return redirect(index) return render(request, start.html, {error: 用户名或密码错误}) return render(request, start.html)登录之后是搜索。工程里要求的搜索方式是按电影名、导演、演员模糊匹配Django 的 ORM 里面对模糊匹配的常规方案是icontains配合 Q 对象把三个字段用 OR 连接起来def search(request): kw request.GET.get(q, ).strip() results Movie.objects.none() if kw: results Movie.objects.filter( Q(title__icontainskw) | Q(director__icontainskw) | Q(actors__icontainskw) )[:50] return render(request, fc_search.html, {results: results, kw: kw})这里几个参数要说明一下。icontains在 SQLite 里会生成 LIKE %关键词% 的查询大小写不敏感这正是模糊匹配的语义。三个 Q 对象用|连接生成的 SQL 是 OR 条件只要片名、导演、演员任一字段命中就返回。[:50]是切片限制防止搜索词太泛时把几千条结果一次性渲染到页面上。调用前先strip()去掉空白空关键词直接返回空结果避免无意义的全表匹配。TOP10 推荐在这个工程里用的是「评分优先」的代理策略也就是把评分最高的十部电影展示出来。这个指标最直接也最容易解释def top10(request): movies Movie.objects.filter(rating__gt0).order_by(-rating)[:10] return render(request, fc_movies.html, {movies: movies})rating__gt0过滤掉没有评分的电影order_by(-rating)降序排列切片拿前十条。如果你的系统需要按用户偏好加权可以在查询条件里加入用户的历史评分或收藏维度但基础版本的「偏好」用评分排序已经能自圆其说。4.2 可视化接口把聚合结果转成 ECharts 需要的 JSON可视化是这个系统的重头戏。工程里有饼图、柱状图、词云图三类对应模板文件是 fc_charts.html 和 fc_cloud.html。后端要做的不是直接画图而是把数据库里的记录聚合成前端图表库能直接吃的 JSON 结构。以年代分布和类型分布为例聚合逻辑用 ORM 的 values annotate 就能实现def chart_data(request): # 按年代聚合得到每年的电影数量 years list( Movie.objects.values(year) .annotate(cntCount(id)) .order_by(year) ) # 按类型聚合注意 genre 字段可能存了多个类型用分隔符拆分 genres {} for g in Movie.objects.values_list(genre, flatTrue): for one in str(g).split(/): genres[one] genres.get(one, 0) 1 return JsonResponse({ years: [{name: y[year], value: y[cnt]} for y in years], genres: [{name: k, value: v} for k, v in genres.items()], })values(year)在 SQL 层面等价于 GROUP BY yearannotate(cntCount(id))是每个分组的数量order_by(year)保证 X 轴按时间顺序排列。genre 字段的处理要特别留意电影类型在 CSV 里常见的存法是「剧情/爱情」这种复合格式如果直接按整串分组会得到「剧情/爱情」这个整体而不是「剧情」和「爱情」两个独立分类所以拆开统计更合理。词云接口走的是另一条路它需要读评论数据、分词、过滤停用词最后返回词频 Top 100# 词云接口jieba 分词 停用词过滤 词频统计 import jieba def wordcloud_data(request): with open(data/stopwords.txt, encodingutf-8) as f: stopwords {line.strip() for line in f} df pd.read_csv(movie_data/comments.csv, encodingutf-8-sig) words {} for text in df[comment].dropna(): for w in jieba.lcut(str(text)): if len(w) 1 and w not in stopwords: words[w] words.get(w, 0) 1 top sorted(words.items(), keylambda x: x[1], reverseTrue)[:100] return JsonResponse({ words: [{name: w, value: c} for w, c in top] })词云这一步最能体现数据清洗的价值。jieba 分词后如果不做过滤高频词永远是「的」「了」「很」这类虚词词云图没有任何信息量。工程里的 stopwords.txt 就是干这个的读文件时每行strip()去掉换行符和空格否则停用词比不上的情况经常发生。len(w) 1是基础过滤把单字词排除掉。词频超过 100 就截断也是为了让前端渲染时词云不至于太密。4.3 模板侧的数据对接base.html 继承与前端图表渲染后端把 JSON 给出来了前端怎么接这就要看 fc_charts.html 和 base.html 的关系。base.html 是公共框架顶部导航、css 引用都在这里fc_charts.html 通过 Django 模板继承来复用这套框架自己的内容放在 block 区域里。前端图表渲染用 ECharts这是这套工程里很合适的选型饼图、柱状图、词云都能覆盖而且 JSON 结构刚好能直接对接后端返回值。核心渲染代码// 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, // 后端返回的 {name, value} 数组直接可用 radius: [35%, 65%] }], tooltip: { trigger: item } }); });后端返回的 JSON 结构和 ECharts 的 series.data 高度一致都是{name, value}数组可以省去前端的二次加工。radius: [35%, 65%]表示的是环形饼图的内径和外径比例如果你想要实心饼图去掉这个字段即可。词云图也是一样的套路fetch /wordcloud_data/ 之后把 data.words 传给词云系列。这里强烈建议把 echarts.min.js 下载到 static 目录下本地引用开发服务器无所谓但部署到生产环境时依赖 CDN 会有额外的网络风险。5. 避坑清单五个我在复现时反复翻车的点这套工程整体不算复杂但真按步骤跑起来有几个位置几乎每跑一遍都会遇到问题。我按数据相关和环境相关分成两节把我踩过的坑记下来每条都按现象、原因、解决来写。5.1 数据相关的三个坑坑一CSV 导入时中文变乱码现象create_data.py 跑完页面上电影名显示成一串问号或乱码。原因爬虫保存 CSV 时用了 GBK 或系统默认编码而 pandas 默认按 utf-8 读取两边对不上导入后自然无法正常显示。解决统一用 utf-8-sig 编码。读的时候写pd.read_csv(path, encodingutf-8-sig)爬虫保存时用open(path, w, encodingutf-8-sig)。实际排查中很多「乱码」其实就是漏了这个 BOM 标记Excel 打开过一次再另存为编码就会被改掉所以爬虫和入库两端的编码声明都必须写死。坑二词云图里中文全变方框现象词云能出图但字符全是方框一个汉字都没有。原因wordcloud 库默认字体不支持中文它按字符编码找字形找不到就画方框。这是字体问题不是数据问题。解决给 WordCloud 指定中文字体路径比如 Windows 下用font_pathC:/Windows/Fonts/simhei.ttfLinux 服务器上可以把开源中文字体放到项目的 static/fonts 目录下传绝对路径。如果字能出来但高频词不对去检查 stopwords.txt 里是不是每行头尾带了空格读文件时逐行strip()是必须的。坑三评分缺失导致 TOP10 混入无评分电影现象首页「TOP10」里出现评分 0 或空值的电影排序乱套。原因爬虫没抓到评分CSV 里该单元格为空入库时没有过滤。解决在查询时加条件Movie.objects.filter(rating__gt0)只保留评分大于 0 的记录更彻底的做法是在 create_data.py 里用df.dropna(subset[rating])把无评分的行直接扔掉入库之前就把脏数据挡在门外。5.2 环境与部署的两个坑坑四页面 HTML 能打开但样式全丢现象浏览器里内容正常渲染但文字全部挤在左上角没有任何排版控制台里一堆 css 404。原因settings.py 里 STATIC_URL 虽然配了但 STATICFILES_DIRS 没有指向 static 目录或者模板里用了写死的相对路径。解决在 settings.py 里加上STATICFILES_DIRS [os.path.join(BASE_DIR, static)]模板里统一用{% load static %}加link relstylesheet href{% static css/xxx.css %}。改完重启 runserver浏览器强制刷新清缓存别问我为什么知道要清缓存。坑五Django 依赖装不上或者 runserver 启动就报 ModuleNotFoundError现象pip install 时报版本冲突或者服务启动时某个第三方库导入失败。原因这类老工程通常有一批依赖间的版本约束直接pip install django装到最新版旧代码没跟上新版 API 的变化。解决先看工程里有没有 requirements.txt有就pip install -r requirements.txt按锁定的版本装没有就按「环境安装.docx」里标注的版本来。导入失败时第一时间看 traceback 里是哪个库名再用pip show 库名查实际版本。核心原则是别用最新版跑老工程。6. 验证数据可信度给分析结果加一道质量巡检系统能跑、图表能动这不算完。数据分析的底线是数据可信尤其是爬虫数据源头不可控入库前如果不做体检后面的可视化再漂亮都是自欺欺人。我的习惯是在项目里加一个独立的质量巡检脚本每次数据入库后跑一遍把关键指标打印出来。这个脚本可以放在项目根目录下比如命名 check_data.py内容参考如下# check_data.py 数据质量巡检跑一遍再下结论 import sqlite3 con sqlite3.connect(db.sqlite3) cur con.cursor() def check_movie_table(table_name): total cur.execute(fSELECT COUNT(*) FROM {table_name}).fetchone()[0] print(f总记录数: {total}) # 检查评分空值率超过 5% 就要回头查爬虫 null_sql fSELECT COUNT(*) FROM {table_name} WHERE rating IS NULL OR rating nulls cur.execute(null_sql).fetchone()[0] print(frating 字段空值: {nulls} ({nulls/total:.1%})) # 检查标题重复重复率过高说明爬虫存在重复抓取 dup_sql fSELECT COUNT(*) - COUNT(DISTINCT title) FROM {table_name} dup cur.execute(dup_sql).fetchone()[0] print(f标题重复数: {dup}) # 检查评分越界正常电影评分范围是 0~10 bad_sql fSELECT COUNT(*) FROM {table_name} WHERE CAST(rating AS REAL) 0 OR CAST(rating AS REAL) 10 bad cur.execute(bad_sql).fetchone()[0] print(f评分越界: {bad}) # 把表名替换成你工程里的实际表名 check_movie_table(movies_table) con.close()这个脚本查四件事总记录数、评分空值率、标题重复数、评分越界。这几个指标能快速暴露大部分数据层问题。空值率超过 5%说明爬虫阶段有字段大面积漏抓这时候再好看的词云图也没意义重复数高说明爬虫翻页逻辑里有重复抓取去重逻辑要在入库前补上。可视化界面是给外人看的数据质量是给自己看的两张图的差距往往就在这一步。这真的不是我一开始就有的习惯。有一回词云图看起来特别漂亮后来才发现停用词文件里多了个空格「电影」这个词根本没被滤掉整张图都在重复同一个词我当时还拿这张图去演示过。从那以后我每次改完 create_data.py 或者换了爬虫源第一件事不是开浏览器看图表而是先跑一遍这个巡检函数。图表是最会骗人的先让数据说话希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑