资讯详情 从爬虫到可视化:豆瓣电影数据看板项目源码解析
📅 2026/10/25 19:08:24
简介这套基于Python的电影数据可视化分析系统源码面向正在完成Python课程设计或期末大作业的高校学生也适合对网络爬虫与数据可视化感兴趣的入门开发者。资源包共8个文件大小仅3.5MB结构清晰4个Python脚本包括Flask后端入口app.py、爬虫脚本crawler.py、词云生成WordCloud.py、2个压缩包templates模板与static静态资源、1个Markdown说明文档和1个SQLite数据库douban.db轻量易用便于按模块对照学习。目前已有2565人学习下载可帮助读者快速理解电影数据从采集、存储到可视化展示的完整实现流程。借助这套源码可以学习豆瓣电影数据的爬取方法、基于Flask的Web应用搭建思路以及词云等可视化图表的生成逻辑对完成课程设计报告和答辩演示具有直接参考价值。1. 把豆瓣电影数据变成可视化看板这份源码到底做了什么期末周剩三天别人交的是 PPT你交一个能点开看图的网站这印象分完全不一样。这份源码就是一个典型的「爬虫 Flask SQLite ECharts」全家桶crawler.py去豆瓣抓电影信息存进douban.dbapp.py起一个 Flask 服务把数据库里的评分、类型、年份按统计口径聚合出来最后用 ECharts 柱状图和词云展示到页面上。它不是只跑一次的脚本也不是画好的静态图而是从数据采集到前端渲染的完整链路。适合正在做期末大作业、课程设计或者想往简历里放一个「能演示也能答辩」项目的从业者。你能把数据换掉、图表改掉、页面逻辑理顺这比背十个接口都有说服力。2. 源码拆包目录结构、数据流与依赖安装2.1 目录结构与数据流crawler 到 db 到 Flask 到前端解压之后你会看到一层仓库名目录比如Movie-Analysis-Platform主-main真正的工程在里面的doubanMovie_flask子目录下。这个嵌套结构是第一处容易踩坑的地方很多人把路径指错启动直接报错。整理一下核心文件文件/目录角色app.pyFlask 主程序路由与聚合查询都在这crawler.py豆瓣爬虫抓取电影信息写入 SQLiteWordCloud.py独立脚本读取评论生成词云图douban.dbSQLite 数据库存电影表和评论文本templates.zip前端模板解压后得到templates/index.htmlstatic.zip静态资源解压后放 ECharts、jQuery 和词云图片doubanMovie/早期版本的备用脚本可以忽略杂例/零碎的测试代码不影响主流程README.md项目说明与启动命令数据流是一条直线crawler.py抓取豆瓣 Top250 电影信息后写入douban.dbapp.py通过 SQL 查询聚合出评分分布、类型分布等结果渲染templates/index.html时把数据以 JSON 接口形式交给前端前端用 ECharts 画图WordCloud.py负责把评论变成词云图片放进static目录。先跑通这条主链路比研究每一行代码更重要。建议按「数据库 → Flask → 前端图表」的顺序验证哪里断了就定位哪一段。2.2 依赖环境Python 版本与 pip 包清单项目基于 Python 3建议直接用 3.8 到 3.10 版本。WordCloud 这个包在 Python 3.11 以上的 Windows 环境偶尔会出现没有预编译 wheel 的问题3.10 以下基本是pip install一把过。先切到工作目录再装依赖cd doubanMovie_flask pip install flask requests beautifulsoup4 lxml wordcloud jieba逻辑说明flask负责 Web 服务requests加beautifulsoup4是爬虫组合lxml是 BeautifulSoup 的解析加速器wordcloud生成词云jieba做中文分词。如果后续看到脚本里用了pandas再补一条pip install pandas即可Docker 或虚拟环境里同样适用。参数说明Windows 用户如果pip install wordcloud报错大概率是本机缺少 VC 编译工具链。常见做法是去 PyPI 下载对应 Python 版本的wordcloudwheel 文件手动安装或者把 Python 降到 3.9 再试后者更省时间。2.3 启动项目flask run 之前的三件事拿到源码后不要急着python app.py先做三件事每一件都是真实耗时的坑。第一templates.zip和static.zip必须解压到app.py同级目录否则 Flask 找不到模板文件页面会白屏。第二确认douban.db存在且不是空文件如果解压包里没有先用python crawler.py生成数据。第三检查5000端口是否被占用被占就把 Flask 的端口参数改掉。python crawler.py # 生成或更新 douban.db数据已存在时可跳过 python app.py # 启动 Flask 开发服务器提示启动前先用dir或ls看一眼目录确认app.py旁边就是templates和static文件夹。这一步能帮你省下二十分钟排查白屏的时间。逻辑说明crawler.py是独立于 Web 服务的脚本跑一次会把数据写进数据库app.py是常驻进程启动后保持运行。如果只是想看可视化效果数据库文件齐全时可以跳过爬虫。3. 爬虫与数据层douban.db 的字段口径与数据兜底3.1 crawler.py 抓取逻辑与字段映射crawler.py的核心逻辑是循环请求豆瓣 Top250 的分页 URL用 BeautifulSoup 解析页面把每部电影的标题、评分、评语等字段写入数据库。网上流传的版本大同小异典型的写法是import time import random import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://movie.douban.com/, } def parse_page(html): soup BeautifulSoup(html, html.parser) movie_list [] for li in soup.select(ol.grid_view li): title li.select_one(.title).get_text(stripTrue) rating li.select_one(.rating_num).get_text(stripTrue) quote li.select_one(.quote .inq) quote_text quote.get_text(stripTrue) if quote else movie_list.append({ title: title, rating: float(rating), quote: quote_text, }) return movie_list def fetch_top250(): for start in range(0, 250, 25): url fhttps://movie.douban.com/top250?start{start} for attempt in range(3): try: r requests.get(url, headersHEADERS, timeout10) r.raise_for_status() movies parse_page(r.text) save_to_db(movies) # 项目里封装好的写库函数 break except Exception as e: print(f第 {start} 条失败{e}重试 {attempt 1}/3) time.sleep(3) time.sleep(random.uniform(2, 5))逻辑说明URL 里的start参数控制分页偏移每页 25 条循环 10 次就是 Top250。User-Agent和Referer缺一不可豆瓣会在缺少 Referer 时直接返回 418 拒绝访问。每页抓完随机睡 2 到 5 秒目的是降低请求频率减少触发反爬校验的概率。参数说明timeout10是单次请求的上限网络不稳定时可以放大到 20range(3)是单页最大重试次数超过就跳过当前页。这两个参数是爬虫稳定性最关键的调节点期末答辩时被问「为什么爬一半断了」答案就在这里。3.2 表结构与统计口径评分、类型、年份怎么聚合douban.db里最常见的就是一张movie表字段设计直接决定后面 Flask 接口好不好写。项目里实际用到的建表语句类似这样CREATE TABLE IF NOT EXISTS movie ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, rating REAL DEFAULT 0, votes INTEGER DEFAULT 0, genre TEXT, director TEXT, year INTEGER, quote TEXT, url TEXT UNIQUE );逻辑说明rating用 REAL 存浮点数因为豆瓣评分有 9.7、8.9 这种带一位小数的值genre是一个字符串多个类型用斜杠分隔url加 UNIQUE 约束防止爬虫重复插入同一部电影。这个约束是去重的关键很多课程设计里数据越跑越多就是因为没做唯一性校验。统计口径方面Flask 接口常用的聚合语句是把评分转成整数后分组再把类型字段按斜杠拆开计数SELECT CAST(rating AS INT) AS bucket, COUNT(*) AS cnt FROM movie GROUP BY bucket ORDER BY bucket;代码逻辑CAST(rating AS INT)会把 7.9 和 7.2 都归到 7 分组这样柱状图的横坐标是 0 到 10 共 11 个档位视觉上更整齐。类型分布则是在 Python 里对genre字段做split(/)之后逐个累加绕开了 SQL 对多值字段的尴尬操作。3.3 反爬触发或数据量不足时的兜底策略豆瓣的反爬通常不是直接封 IP而是返回 418 或要求验证。如果你的crawler.py跑到第三页就断先检查请求头再检查延时。但如果是期末提交前最后一晚突然被限流最稳妥的兜底是从已有数据库或本地 CSV 里导入一份样例数据保证可视化页面有东西可看。import sqlite3 conn sqlite3.connect(douban.db) c conn.cursor() # 样例数据格式(title, rating, year, genre) sample_data [ (肖申克的救赎, 9.7, 1994, 剧情/犯罪), (霸王别姬, 9.6, 1993, 剧情/爱情), (阿甘正传, 9.5, 1994, 剧情/爱情), ] c.executemany( INSERT OR IGNORE INTO movie (title, rating, year, genre) VALUES (?, ?, ?, ?), sample_data, ) conn.commit() conn.close()逻辑说明executemany一次插入多条INSERT OR IGNORE配合 url 唯一约束或主键去重。这段脚本的价值不在于插入数据本身而在于给你留了一条应急路径——爬虫挂了页面依然能通过答辩演示。4. Flask 路由与 ECharts 渲染数据怎么从数据库走到图表4.1 路由与聚合查询一个页面一个 JSON 接口app.py的设计思路很直接首页路由渲染 HTML其余路由以/api/开头返回 JSON前端异步请求后画图。数据库连接写成一个通用函数避免每个接口重复打开关闭连接。from flask import Flask, render_template, jsonify import sqlite3 app Flask(__name__) DB_PATH douban.db def query(sql, args()): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row rows conn.execute(sql, args).fetchall() conn.close() return rows app.route(/) def index(): return render_template(index.html) app.route(/api/rating_distribution) def rating_distribution(): rows query( SELECT CAST(rating AS INT) AS bucket, COUNT(*) AS cnt FROM movie WHERE rating 0 GROUP BY bucket ORDER BY bucket ) return jsonify([{bucket: r[bucket], count: r[cnt]} for r in rows]) app.route(/api/genre_distribution) def genre_distribution(): rows query(SELECT genre FROM movie WHERE genre ! ) counter {} for r in rows: for genre in r[genre].split(/): genre genre.strip() counter[genre] counter.get(genre, 0) 1 return jsonify( sorted(counter.items(), keylambda x: x[1], reverseTrue)[:10] )逻辑说明row_factory sqlite3.Row让查询结果支持按列名访问r[bucket]比r[0]可读性好很多。genre_distribution里先用split(/)拆开多值字段再累加计数最后用sorted按数量倒序取前 10保证饼图不会被几十个小类目挤爆。参数说明WHERE rating 0过滤掉没有评分的数据避免CAST(NULL AS INT)产生的 0 分组干扰图表。实际使用中如果发现图表数据少得可疑优先检查这一条件是否误伤了有效数据。4.2 ECharts 核心配置柱状图、饼图的动态数据绑定前端模板里最核心的一段逻辑是fetch请求/api/接口把返回的 JSON 映射到 ECharts 的xAxis和series上。以评分分布柱状图为例fetch(/api/rating_distribution) .then((res) res.json()) .then((data) { const chart echarts.init(document.getElementById(ratingChart)); chart.setOption({ title: { text: 豆瓣电影评分分布 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.map((d) d.bucket 分), }, yAxis: { type: value, name: 电影数量 }, series: [ { type: bar, data: data.map((d) d.count), barWidth: 30, }, ], }); });逻辑说明data.map((d) d.bucket 分)把整数分数转成字符串类目data.map((d) d.count)提取对应的计数值。ECharts 的类目轴和数值轴必须长度一致错位会导致图形错乱或空白。参数说明trigger: axis让鼠标悬停时显示整条轴线上的提示barWidth: 30固定柱子宽度避免柱子过粗或过细影响观感。若默认主题太素可以把color数组放在setOption顶层效果是全局配色统一。4.3 WordCloud 词云从豆瓣短评到词频图WordCloud.py一般是独立运行的脚本目标是从评论文本里生成一张中文词云图存到static/wordcloud.png前端通过img src/static/wordcloud.png直接显示。因为豆瓣短评本身是中文必须经过分词处理否则词云会变成一堆单字。from wordcloud import WordCloud import jieba def generate_wordcloud(comment_file, output_filestatic/wordcloud.png): with open(comment_file, r, encodingutf-8) as f: text f.read() words .join(jieba.cut(text)) wc WordCloud( font_pathC:/Windows/Fonts/msyh.ttc, width1200, height800, background_colorwhite, max_words200, collocationsFalse, ) wc.generate(words) wc.to_file(output_file)逻辑说明jieba.cut(text)生成分词迭代器用空格拼接成 WordCloud 期望的输入格式。font_path必须指向一个支持中文的字体文件Windows 选择msyh.ttc或simhei.ttfLinux 服务器一般用/usr/share/fonts/下的文泉驿字体。参数说明max_words200限制词云里最多出现 200 个词collocationsFalse禁止 WordCloud 自动把相邻词拼成新词组这对中文很重要——不关掉的话会出现「电影电影」这种无意义的词组。5. 课程设计跑路避坑指南五个典型的翻车现场5.1 页面白屏templates 和 static 没解压现象python app.py启动后浏览器打开http://127.0.0.1:5000页面全是白的控制台报TemplateNotFound或一堆 404 静态资源错误。原因Flask 默认在app.py同级目录找templates和static文件夹而压缩包里这两个目录是 zip 格式没解压等于不存在。解决把templates.zip和static.zip解压到app.py所在目录确认路径是templates/index.html和static/然后重启app.py。经验是每次拿到源码先看有没有压缩态的目录有就先解压。5.2 爬虫返回 418 或 403请求头不全与请求频率过高现象crawler.py第一次跑能抓 25 条第二次循环开始报HTTP 418或403代码本身没有语法错误。原因豆瓣对缺少Referer或请求频率过高的客户端返回反爬状态码。418 是「我是茶壶」的玩笑代码本质是拒绝服务。解决在HEADERS里补上完整的User-Agent和Referer并把每页之间的延时从固定 1 秒改成random.uniform(2, 5)秒。如果还是被限就暂停十分钟再跑不要无脑重试。5.3 词云图片全是方框中文字体缺失现象WordCloud.py正常执行完生成图片在浏览器里打开一看中文全部显示为方块或豆腐块。原因wordcloud库默认字体是 DroidSansMono不支持中文。重新生成时没有指定font_path中文字符全部无法渲染。解决在WordCloud构造函数里指定中文字体路径Windows 用C:/Windows/Fonts/msyh.ttcmacOS 用/System/Library/Fonts/PingFang.ttc。如果不确定系统字体路径可以先在 Python 里列出matplotlib.font_manager的可用字体逐个试。5.4 图表空白不渲染返回的 JSON 字段名对不上现象页面能打开但某个图表的区域是空白的浏览器控制台报Cannot read properties of undefined或数据长度为 0。原因前端代码写的是data.map((d) d.name)但 Flask 接口返回的是{bucket: 7, count: 12}字段名不存在map 出来的全是 undefined。解决打开浏览器开发者工具的 Network 面板查看接口实际返回的 JSON 结构再回去对齐前端字段名。切身体会是先console.log(data)再写渲染逻辑能省大量 debug 时间。5.5 sqlite3.OperationalError: no such table现象python app.py启动时报sqlite3.OperationalError: no such table: movie数据库文件明明存在。原因douban.db是个空库或损坏文件里面没有建表。可能解压包里本来就没有表结构或者上次爬虫被中断后数据未落盘。解决先手动执行建表语句创建movie表再跑crawler.py。更保险的做法是把建表逻辑封装成init_db()函数在app.py启动时自动调用这样空库也能自动恢复。6. 把项目换成自己的数据数据源替换与图表扩展这份源码真正的扩展点不在 Flask 层而在爬虫层。只要crawler.py能输出和现在movie表一致的字段app.py和前端 ECharts 代码一个字都不用改。常见的替换方向是爬猫眼 Top100 或其他电影榜单核心改动集中在解析逻辑把parse_page里的 CSS 选择器改成目标站点的列表项结构字段映射保持title、rating、genre、year不变入库后所有可视化立刻生效。我一般会在crawler.py里留一个PAGE_SELECTOR配置项换数据源时只改选择器和 URL 规则。新增图表类型时套路是「加一条 Flask 路由加一个前端容器加一个 setOption」。比如要做年份与评分的关系趋势后端加一个/api/year_trend接口返回按年份聚合的平均分前端在index.html里加一个div idyearChart然后复制已有柱状图的 fetch 逻辑改成折线图。需要注意依赖的static/echarts.min.js是否包含折线图模块常用版本都支持无需额外引入。最后用自己的真实数据验证一遍全链路删掉douban.db跑爬虫再启动 Flask检查三个图表和一个词云是否正常刷新。我第一次拿到这类课程设计源码时就是没解压 templates 直接启动白屏折腾了二十多分钟最后发现模板根本不在那感觉就像考试写完了才发现答题卡没涂。从那以后我拿到任何源码第一件事永远是先跑通主路径再谈改功能。希望帮到你。本文还有配套的精品资源点击获取