从爬虫到Spark再到ECharts:豆瓣电影数据分析全流程实践

发布时间:2026/9/16 18:12:22

从爬虫到Spark再到ECharts:豆瓣电影数据分析全流程实践 简介一份基于豆瓣电影爬虫与Spark数据分析可视化的毕业设计源码包面向计算机相关专业的在校学生、教师或大数据入门者尤其适合作为毕设课题、课程设计或项目初期演示的参考模板内容围绕“爬虫采集—数据清洗—Spark分析—可视化展示”完整链路展开帮助读者快速理解并复现真实的数据分析项目。包内共242个文件压缩包仅5.64MB主要包含22个Java源码、165个XML配置以及Python爬虫脚本、Spark分析编译类、前端页面CSS/HTML/JS文件、CSV结果数据、SQL数据库脚本和Markdown说明文档既有采集端代码也有分析计算与可视化展示模块目录结构清晰便于按需检索和二次开发。目前已有519人学习下载资源附带可运行源码、文档说明及辅助信息学习价值在于既能掌握豆瓣电影数据的抓取与预处理又能了解Spark分布式统计和图表可视化的落地方法。对于准备毕业设计或想向大数据分析方向进阶的学习者这是一份不错的实践样例。1. 爬虫加Spark分析这套豆瓣电影源码到底能学到什么豆瓣电影的数据规模不大不小正好卡在「单机爬虫能搞定、Excel已经撑不住」的中间地带。用requests把电影信息抓下来存成结构化文件再用Spark做词频、类型、评分等统计最后用ECharts渲染成图表——这一整套链路做完等于把数据采集、离线计算、可视化展示三个最常被问到的环节完整走了一遍。项目源码里出现的WordNum.class、TypeNum.class、LvNum.class这些编译产物说明分析任务已经跑通过直接看代码就能知道每个统计是怎么写的。适合正在做毕设、打算入门Spark或者想补全数据项目经验的人也适合拿来改造成其他垂直领域的数据分析demo。2. 豆瓣电影爬虫字段设计、反爬策略与落盘格式2.1 数据字段设计先想清楚Spark要算什么爬虫不是把页面HTML存下来就算完而是要为后续分析准备好结构化字段。我习惯先列一个字段清单把要分析的口径和字段一一对应。这个项目里需要支持分词词频、类型统计、评分分布、评论数量和年份分析那么至少需要下面这些字段字段名说明来源示例movie_id豆瓣电影唯一IDURL1292052title电影名称页面标题肖申克的救赎year上映年份年份标签1994rating豆瓣评分评分文本9.7comment_num评论人数评论数标签2345678genres类型列表类型标签剧情/犯罪comments短评拼接文本短评区域希望让人自由...注意genres这个字段要存成列表或者用逗号分隔的字符串因为一部电影有多个类型后续用Spark做flatMap拆分才能正确统计各类型占比。comments字段用于词频统计抓取时可以拼接前几条短评不用全量抓取否则数据量会迅速膨胀。2.2 抓取实现requests加队列比Scrapy更轻量对于豆瓣这种中等规模站点使用requests配合线程池就够用了没必要一上来就上Scrapy。下面是简化版的爬虫核心逻辑重点展示如何构造请求、解析字段和断点续抓import requests import pandas as pd from bs4 import BeautifulSoup from concurrent.futures import ThreadPoolExecutor HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } def fetch_movie(movie_id): url fhttps://movie.douban.com/subject/{movie_id}/ try: resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) # 以豆瓣电影页面结构为例实际解析需按页面调整 title soup.find(h1).get_text(stripTrue) year soup.select_one(.year).get_text(stripTrue).strip(()) rating soup.select_one(.rating_num).get_text(stripTrue) comment_num soup.select_one(.rating_people).get_text(stripTrue) genres ,.join([a.get_text() for a in soup.select(.tags .type)]) return { movie_id: movie_id, title: title, year: year, rating: rating, comment_num: comment_num, genres: genres, } except Exception as e: print(f抓取失败 {movie_id}: {e}) return None if __name__ __main__: ids list(range(1292052, 1292070)) # 示例ID段 results [] with ThreadPoolExecutor(max_workers4) as pool: for result in pool.map(fetch_movie, ids): if result: results.append(result) df pd.DataFrame(results) df.to_csv(douban_movies.csv, indexFalse, encodingutf-8-sig)代码里用ThreadPoolExecutor开4个线程原因是不想给目标站点造成压力同时又能比单线程快好几倍。headers里的User-Agent必须设成常见浏览器的样子否则豆瓣会直接拒绝。timeout参数设置为10秒避免某个电影页面卡住导致整个线程池阻塞。爬取结果统一放进DataFrame再落盘成CSVSpark可以直接读取不需要额外转格式。2.3 反爬遇到的几个坑以及我的处理方式豆瓣对单IP的请求频率比较敏感最常见的是返回418或者302跳转。我踩过的坑集中在三个方面请求头不全只带了User-Agent缺少Accept-Language和Referer后来补上就稳定很多。没有加延时即使开了线程池每个线程连续请求也会触发封禁。我一般在线程任务里加一个time.sleep(random.uniform(0.5, 1.5))。Cookie失效登录后的Cookie用于访问更多数据但Cookie过期后请求会静默失败。我的做法是写一个健康检查函数定期用一个已知ID请求连续失败就停止爬虫并输出日志方便人工介入。关于断点续爬更稳妥的做法是每次抓完一个电影就立刻把记录追加到CSV而不是全部抓完再写入。这样即使中途被封已经抓到的数据还在重跑时只需要跳过CSV里已有的movie_id即可。3. Spark数据分析从CSV到多个统计结果文件3.1 为什么选择Spark而不是Pandas豆瓣电影数据量可能在几万条到几十万条之间Pandas完全能处理。但作为毕设或数据项目用到Spark是一个加分项因为它体现了分布式计算的思路。而且当评论数据被展开成词条之后规模会膨胀几十倍此时Spark的延迟计算和分区优势就体现出来了。项目文件里的_part-r-00000.crc这些临时文件正是Spark写Hadoop输出格式时生成的校验文件说明任务跑的是标准Spark流程。环境方面我用的是Spark 3.x搭配Scala 2.12Python版PySpark也可以。代码逻辑一致这里以Scala为例因为编译产物是.class文件说明原始工程是Java或Scala写的。3.2 读取CSV并构建DataFrameimport org.apache.spark.sql.SparkSession import org.apache.spark.sql.functions._ val spark SparkSession.builder() .appName(DoubanMovieAnalysis) .master(local[*]) .getOrCreate() val df spark.read .option(header, true) .option(encoding, UTF-8) .csv(data/douban_movies.csv)这里.master(local[*])表示使用本地多线程模式星号代表利用所有可用核心。如果部署到集群只需要把master换成yarn或spark://地址。读取时显式指定UTF-8否则Windows环境下生成的csv可能出现乱码。3.3 四个核心统计任务的实现项目里的WordNum、TypeNum、LvNum分别对应词频、类型统计、评分等级统计YearNum是年份统计CommontNum是评论数量分析。下面把逻辑拆开写便于理解每个类在做什么。// 1. 电影类型统计拆开逗号分隔的genres val typeDF df .select(explode(split(col(genres), ,)).as(type)) .groupBy(type) .count() .orderBy(col(count).desc) // 2. 年份分布统计过滤异常年份后分组 val yearDF df .filter(col(year).rlike(^\\d{4}$)) .groupBy(year) .count() .orderBy(year) // 3. 评分等级统计将评分映射到区间 val lvDF df .withColumn(level, when(col(rating).cast(double) 9.0, 9分以上) .when(col(rating).cast(double) 8.0, 8-9分) .when(col(rating).cast(double) 7.0, 7-8分) .otherwise(7分以下)) .groupBy(level) .count() // 4. 评论数量Top10电影 val hotDF df .select(title, comment_num) .withColumn(comment_num, col(comment_num).cast(int)) .orderBy(col(comment_num).desc) .limit(10)explode和split是Spark处理数组字段最常用的组合split把字符串切成数组explode把数组展开成多行。评分等级用了多个when判断cast(double)是为了防止CSV里的评分字段被读成字符串。评论数量必须转成int再排序否则会按字典序排列导致999排在10000前面。3.4 结果输出格式与目录组织每个统计结果需要单独保存项目里可以看到多个part-r-00000文件这就是Spark的默认输出格式。为了让后续可视化方便读取我建议统一输出为parquet或者带header的CSV。下面是推荐写法typeDF.coalesce(1).write .option(header, true) .mode(overwrite) .csv(output/type_stat) yearDF.coalesce(1).write .option(header, true) .mode(overwrite) .csv(output/year_stat)这里使用了coalesce(1)强制合并成一个分区文件好处是生成的文件直接就是需要的目录且内容完整不会出现一堆part文件。缺点是数据量很大时单文件写入会有性能问题不过对于豆瓣电影这种万级数据量完全够用。mode(overwrite)表示每次跑任务都覆盖旧结果避免手动清理历史文件。4. 可视化展示把Spark结果变成前端图表4.1 结果数据如何给前端使用Spark输出的CSV文件可以直接被前端异步加载但实际项目中更推荐把多个统计结果合并成一个JSON文件让前端一次请求拿到全部数据。我的做法是写一个Python脚本读Spark输出目录里的CSV再统一打包成douban_stats.json。结构如下{ typeStat: [{type: 剧情, count: 1200}], yearStat: [{year: 1994, count: 45}], levelStat: [{level: 9分以上, count: 30}], hotMovies: [{title: 肖申克的救赎, comment_num: 2345678}] }前端只需要一个fetch请求就能拿到全部图表数据避免多次请求造成的加载闪烁。如果要做成实时展示可以换成后端接口但作为毕业设计静态JSON文件配上简单的HTTP服务就够了。4.2 ECharts图表配置的几个关键点使用ECharts做可视化时我通常聚焦四个图表饼图展示电影类型占比柱状图展示年份分布条形图展示评分等级横向柱状图展示热门电影评论数。下面以类型统计为例fetch(douban_stats.json) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(typeChart)); chart.setOption({ tooltip: { trigger: item }, series: [{ type: pie, radius: [40%, 70%], data: data.typeStat.map(item ({ name: item.type, value: item.count })) }] }); });饼图这里使用了环形效果通过radius数组控制内半径和外半径视觉上比实心饼图更现代。tooltip设置为item触发鼠标悬浮时能展示该类型的名称和数量。需要注意ECharts实例要在DOM渲染完成后再初始化否则获取不到容器节点。4.3 可视化大屏的排版建议如果是做可视化大屏建议把页面分成上中下三块区域顶部放总览统计数字中部放类型占比和年份趋势底部放热门电影排行和评分等级。左侧放饼图、右侧放柱状图这种布局符合人眼从左到右的阅读习惯。另外为了适配大屏分辨率ECharts容器要使用百分比宽度并且在窗口变化时调用chart.resize()。还可以用CSS Grid或Flexbox来划分区域而不是用绝对定位这样不同尺寸的屏幕都能自适应。5. 编码细节与常见报错的应对方法5.1 从源码中能学到的三个编程习惯这个项目的编译产物里有WordUtil.class说明作者把文本处理逻辑单独抽了一个工具类这种写法很值得借鉴。第一个习惯是把清洗逻辑独立成函数比如去除标点、过滤停用词、统一大小写方便复用和单元测试。第二个习惯是写常量管理比如CSV文件路径、输出目录、豆瓣请求头都定义成常量避免散落在代码各处。第三个习惯是给每个统计任务写单独的类或对象如TypeNum、YearNum职责单一之后想更换某个统计逻辑直接定位对应类就行不会影响其他模块。5.2 关于Spark任务长时间不结束遇到本地模式Spark跑很久不结束先检查是不是数据倾斜。比如某一年份的电影数量特别多groupBy后落到单个分区上导致其他分区空闲等待。我一般通过df.groupBy(year).count().show(false)先把数据分布打出来观察有没有极端情况。如果存在倾斜可以加一个随机前缀进行两阶段聚合先把key打散再对结果去掉前缀聚合一次。对于这个电影数据的规模极少出现真正的倾斜更多时候是local[*]线程数太多小任务反而被线程切换拖慢改成local[2]反而更快。5.3 爬虫与Spark衔接时的编码问题在Windows上爬虫生成的CSV默认是GBK编码而Spark读取时默认UTF-8会出现乱码。我的习惯是爬虫落盘时统一用encodingutf-8-sig这个参数会在文件头部写入BOMExcel打开也不会乱码Spark读取时指定option(encoding, UTF-8)即可。另外CSV里的年份字段可能混入空格或“()”在Spark里用regexp_replace(col(year), [^0-9], )清洗后再转int能避免很多解析异常。5.4 让项目能演示得更出彩的改进方向如果想让这个毕设项目更有竞争力可以在现有基础上加入评论情感分析。爬虫已经拿到了comments字段用Spark调用一个简单的分词库按词频表打正负情感分生成情感趋势折线图作为第五张图表。再或者将Spark任务包装成定时执行每天抓取新增电影数据用增量统计替换全量统计这样整个项目就从一次性分析变成了可持续运行的框架。改动成本不高但面试时能讲述的深度会完全不同。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/16 18:12:22

PyTorch GPU/CPU设备调度:单卡、多卡训练与模型保存加载全指南

1. 为什么这个总结值得你花15分钟认真读完PyTorch里GPU和CPU的切换,看起来只是几行代码的事——model.to(device)、torch.device(cuda:0)、DataParallel……但我在带三个团队做模型训练的四年里,亲眼见过太多人栽在这几个看似简单的环节上:刚…

2026/9/16 18:12:22

去噪扩散概率模型DDPM的PyTorch实现与源码解析

简介:这是一套基于Pytorch实现的去噪扩散概率模型(DDPM)完整项目源码,面向希望深入理解扩散模型原理、并动手实践图像去噪与增强的开发者、研究者和学生。项目代码涵盖数据加载、网络构建、损失函数与优化器配置等完整流程&#x…

2026/9/16 19:07:27

MES蓝图设计实战:需求调研、流程重构与落地避坑指南

1. MES蓝图设计到底在解什么题我见过太多MES项目开局的场面:业务顾问背着电脑进厂,调研一周写了份现状报告,再花两周画几十张流程图,PPT一做就开始评审,评审会上大家点头说"没问题",结果一进开发…

2026/9/16 19:07:27

基于PyTorch和单通道EEG的睡眠分期:从信号预处理到深度学习实战

简介:基于PyTorch框架的单通道EEG睡眠分期Python源代码,面向计算机相关专业正在做毕业设计、课程设计或需要项目实战练习的学习者,主要解决睡眠脑电信号的自动分期识别问题,涵盖浅睡、深睡、REM等阶段分类。资源包共21个文件&…

2026/9/16 19:02:26

DOE光场整形实战:基于GS算法的相位设计与MATLAB仿真

简介:一份围绕衍射光学元件(DOE)光场整形的轻量资源包,面向光学工程、激光加工、成像系统等领域的科研与工程人员,也适合相关专业学生作为课程设计与仿真参考。内容聚焦基于傅里叶光学的相位函数设计,借助M…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/16 0:04:09

PHP源码部署实战:从环境配置到运行情侣游戏全攻略

简介:这是一套面向情侣互动场景的PHP完整源码,集成情侣飞行棋、真心话大冒险、情趣骰子等玩法,并内置完整分销制度,可自定义多种返佣比例,源码完全开源无加密,支持微信无感自动授权登录与第三方授权&#x…

2026/9/15 14:22:53

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

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

2026/9/15 21:31:11

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

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

2026/9/15 11:42:23

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

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

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

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

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