You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
35 KiB
35 KiB
电影数据抓取与分析项目 - 流程文档
本文档面向 Java 初学者,详细说明项目的整体架构、数据流向和各模块职责。
1. 项目概述
1.1 项目目标
从豆瓣电影 Top 250 抓取影片数据,进行清洗、存储和多维度统计分析,最终以 Web 界面和图表形式展示结果。
1.2 技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Java | 11 | 主要开发语言 |
| Spring Boot | 2.7.12 | Web 框架、依赖注入 |
| Spring Data JPA | - | 数据持久化 |
| H2 Database | - | 内存数据库 |
| Jsoup | 1.15.3 | HTML 解析与网页爬取 |
| JFreeChart | 1.5.3 | 图表生成 |
| Apache Commons Math | 3.6.1 | 统计计算(相关性分析) |
| Jackson | - | JSON 序列化 |
| Thymeleaf | - | Web 模板引擎 |
| Caffeine | - | 缓存 |
1.3 两种运行模式
本项目支持两种独立的运行模式:
┌─────────────────────────────────────────────────────────────┐
│ 运行模式选择 │
├─────────────────────────────────────────────────────────────┤
│ 模式一:独立控制台模式 (Main.java) │
│ - 命令:mvn exec:java -Dexec.mainClass="com.movieratings.Main"│
│ - 输出:控制台表格 + JSON 文件 + PNG 图表 │
│ - 适用:数据分析演示、离线运行 │
├─────────────────────────────────────────────────────────────┤
│ 模式二:Spring Boot Web 模式 (MovieRatingsApplication.java) │
│ - 命令:mvn spring-boot:run │
│ - 输出:Web 界面 (http://localhost:8080) │
│ - 适用:交互式查询、在线展示 │
└─────────────────────────────────────────────────────────────┘
2. 系统架构
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────────────┐
│ 项目整体架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 豆瓣电影 │────▶│ MovieCrawler │────▶│ Movie │ │
│ │ Top 250 │ │ (爬虫层) │ │ (模型层) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐│
│ ▼ ▼ ▼│
│ ┌──────────────────┐ ┌───────────────┐ ┌──────────┐│
│ │ DataAnalyzer │ │ MovieRepository│ │JSON/CSV ││
│ │ (分析层) │ │ (持久化层) │ │ (导出) ││
│ └──────────────────┘ └───────────────┘ └──────────┘│
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌───────────────┐ │
│ │ ResultDisplay │ │ MovieService │ │
│ │ (展示层) │ │ (业务层) │ │
│ └──────────────────┘ └───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌───────────────┐ │
│ │ PNG 图表 + 控制台 │ │DirectorController│ │
│ │ (独立模式) │ │ (Web 控制器) │ │
│ └──────────────────┘ └───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Thymeleaf │ │
│ │ (Web 页面) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 包结构
com.movieratings
├── Main.java # 独立模式入口
├── MovieRatingsApplication.java # Spring Boot 入口
├── DataInitializer.java # 启动时数据初始化
│
├── model/ # 数据模型层
│ ├── Movie.java # 电影实体类
│ └── DirectorStats.java # 导演统计 DTO
│
├── crawler/ # 爬虫层
│ └── MovieCrawler.java # 网页爬取与解析
│
├── analysis/ # 分析层
│ └── DataAnalyzer.java # 数据统计分析
│
├── display/ # 展示层
│ └── ResultDisplay.java # 控制台输出与图表生成
│
├── repository/ # 持久化层
│ └── MovieRepository.java # JPA 数据访问接口
│
├── service/ # 业务逻辑层
│ └── MovieService.java # 业务服务
│
└── controller/ # Web 控制层
└── DirectorController.java # HTTP 请求处理
3. 详细数据流程
3.1 模式一:独立控制台流程
┌─────────────────────────────────────────────────────────────────────┐
│ 独立模式执行流程 (Main.java) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 启动 │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ MovieCrawler.crawl(50) │ │
│ │ ──────────────────────── │ │
│ │ • 连接豆瓣 Top 250 页面 │ │
│ │ • 解析 HTML (Jsoup) │ │
│ │ • 提取:排名、标题、评分、年份、导演等 │ │
│ │ • 返回 List<Movie> │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ DataAnalyzer (多维度分析) │ │
│ │ ──────────────────── │ │
│ │ • analyzeRatings() → 评分统计 │ │
│ │ • countMoviesByRatingRange() → 评分分布 │ │
│ │ • findMostReviewed() → 热门电影 │ │
│ │ • analyzeYearRatingCorrelation() → 相关性│ │
│ │ • getTopDirectors() → 导演排行 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ ResultDisplay (结果展示) │ │
│ │ ────────────────── │ │
│ │ • printMoviesTable() → 控制台表格 │ │
│ │ • printDirectorRanking() → 导演排行榜 │ │
│ │ • generateRatingChart() → 评分分布图 │ │
│ │ • generateScatterPlot() → 年份评分散点图 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ 数据导出 │ │
│ │ ──────── │ │
│ │ • saveAsJson() → movies_data.json │ │
│ │ • exportToCSV() → movies_analysis.csv │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 程序结束 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 模式二:Spring Boot Web 流程
┌─────────────────────────────────────────────────────────────────────┐
│ Spring Boot Web 模式执行流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 应用启动 │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ MovieRatingsApplication.main() │ │
│ │ ──────────────────────────── │ │
│ │ • 启动 Spring 容器 │ │
│ │ • 初始化 H2 内存数据库 │ │
│ │ • 配置 Caffeine 缓存 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ DataInitializer.run() (CommandLineRunner)│ │
│ │ ────────────────────────────── │ │
│ │ • 调用 MovieCrawler.crawl(100) │ │
│ │ • 设置部分作品类型(电影/电视剧/纪录片) │ │
│ │ • 调用 MovieService.refreshData() │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ MovieService.refreshData() │ │
│ │ ───────────────────── │ │
│ │ • movieRepository.deleteAll() │ │
│ │ • movieRepository.saveAll(movies) │ │
│ │ • 清除缓存 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 应用就绪,等待 HTTP 请求 │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 2. 用户请求处理 │
│ │
│ 浏览器访问 http://localhost:8080/directors │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ DirectorController.showDirectorRankings()│ │
│ │ ──────────────────────────────── │ │
│ │ • 接收参数:name, type, page, size │ │
│ │ • 调用 MovieService.getDirectorRankings()│ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ MovieService.getDirectorRankings() │ │
│ │ ──────────────────────────── │ │
│ │ • 检查缓存 (Caffeine) │ │
│ │ • 缓存未命中 → 调用 Repository │ │
│ │ • 返回 Page<DirectorStats> │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ MovieRepository.findDirectorRankings() │ │
│ │ ──────────────────────────────── │ │
│ │ • 执行 JPQL 聚合查询 │ │
│ │ • GROUP BY director │ │
│ │ • 返回导演统计数据 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Thymeleaf 模板渲染 │ │
│ │ ──────────────── │ │
│ │ • director_rankings.html │ │
│ │ • 返回 HTML 页面给浏览器 │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4. 核心模块详解
4.1 爬虫模块 (MovieCrawler)
职责:从豆瓣电影 Top 250 抓取数据
流程:
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ 构造请求URL │───▶│ 发送HTTP请求│───▶│ 解析HTML │───▶│ 封装Movie │
│ (分页参数) │ │ (Jsoup) │ │ (CSS选择器)│ │ 对象列表 │
└────────────┘ └────────────┘ └────────────┘ └────────────┘
关键代码:
// 发送请求
Document doc = Jsoup.connect(url).userAgent(USER_AGENT).get();
// 解析数据
movie.setRank(Integer.parseInt(item.select(".pic em").text()));
movie.setTitle(item.select(".title").first().text());
movie.setRating(Double.parseDouble(item.select(".rating_num").text()));
数据提取字段:
| 字段 | 提取方式 |
|---|---|
| 排名 | .pic em |
| 标题 | .title |
| 评分 | .rating_num |
| 海报 | .pic img 的 src 属性 |
| 年份 | 正则匹配 \d{4} |
| 导演 | 文本中提取 导演: xxx |
| 国家 | 按 / 分割取倒数第二项 |
| 评价人数 | 正则匹配 ([\d,]+)人评价 |
4.2 数据模型 (Movie)
职责:定义电影数据的结构,使用 JPA 注解映射到数据库
类图:
┌─────────────────────────────────────────┐
│ Movie │
├─────────────────────────────────────────┤
│ - id: Long (主键, 自增) │
│ - title: String (标题) │
│ - rating: double (评分) │
│ - releaseYear: int (年份) │
│ - rank: int (排名) │
│ - quote: String (简评) │
│ - director: String (导演) │
│ - reviewCount: int (评价人数) │
│ - country: String (国家) │
│ - boxOffice: double (票房) │
│ - type: String (类型) │
│ - posterUrl: String (海报链接) │
├─────────────────────────────────────────┤
│ + getter/setter 方法 │
│ + toString(): String │
│ + equals(Object): boolean │
│ + hashCode(): int │
└─────────────────────────────────────────┘
JPA 注解说明:
@Entity— 标记为数据库实体@Table(name = "movies")— 指定表名@Id+@GeneratedValue— 主键自增策略
4.3 数据分析模块 (DataAnalyzer)
职责:对电影数据进行多维度统计分析
分析方法:
┌──────────────────────────────────────────────────────────────────┐
│ DataAnalyzer 方法 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ analyzeRatings(List<Movie>) │
│ └──▶ 返回 DoubleSummaryStatistics (平均值、最大值、最小值、计数) │
│ │
│ countMoviesByRatingRange(List<Movie>) │
│ └──▶ 返回 Map<String, Long> (评分段 → 电影数量) │
│ 评分段: 9.5-10.0, 9.0-9.4, 8.5-8.9, 8.5以下 │
│ │
│ findMostReviewed(List<Movie>, int n) │
│ └──▶ 返回评价人数最多的前 N 部电影 │
│ │
│ analyzeYearRatingCorrelation(List<Movie>) │
│ └──▶ 返回 CorrelationResult (Pearson 相关系数 + 显著性检验) │
│ │
│ getTopDirectors(List<Movie>, int topN) │
│ └──▶ 返回 List<DirectorStats> (按作品数排序的导演排行) │
│ │
└──────────────────────────────────────────────────────────────────┘
内部类:
DirectorStats— 导演统计结果(姓名、作品数、平均分、总票房)CorrelationResult— 相关性分析结果(系数、p 值、显著性描述)
4.4 数据持久化层
MovieRepository 接口:
┌─────────────────────────────────────────────────────────────────┐
│ MovieRepository │
│ extends JpaRepository<Movie, Long> │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 继承自 JpaRepository 的方法: │
│ • save(Movie), saveAll(List<Movie>) │
│ • deleteAll(), deleteById(Long) │
│ • findById(Long), findAll() │
│ │
│ 自定义方法: │
│ • findDirectorRankings(name, type, pageable) │
│ └── JPQL 聚合查询,返回导演排行榜 │
│ • findByDirector(String director) │
│ └── 按导演名查询作品列表 │
│ • findAllTypes() │
│ └── 查询所有不同的作品类型 │
│ │
└─────────────────────────────────────────────────────────────────┘
JPQL 聚合查询示例:
@Query("SELECT new com.movieratings.model.DirectorStats(" +
"m.director, COUNT(m), MAX(m.posterUrl), AVG(m.rating), SUM(m.boxOffice)) " +
"FROM Movie m " +
"WHERE (:name IS NULL OR m.director LIKE %:name%) " +
"GROUP BY m.director " +
"ORDER BY COUNT(m) DESC")
Page<DirectorStats> findDirectorRankings(...);
4.5 业务服务层 (MovieService)
职责:封装业务逻辑,管理缓存和事务
方法说明:
| 方法 | 功能 | 缓存策略 |
|---|---|---|
getDirectorRankings() |
获取导演排行榜 | @Cacheable 缓存 10 分钟 |
getAllTypes() |
获取所有作品类型 | @Cacheable 缓存 |
saveAll() |
批量保存电影 | @CacheEvict 清除缓存 |
refreshData() |
清空并重新加载数据 | @CacheEvict 清除缓存 |
4.6 Web 控制层 (DirectorController)
职责:处理 HTTP 请求,返回 Web 页面
路由映射:
| URL | 方法 | 功能 |
|---|---|---|
GET / |
index() |
重定向到导演排行 |
GET /directors |
showDirectorRankings() |
导演排行榜页面 |
GET /director/{name} |
showDirectorMovies() |
指定导演的作品列表 |
请求参数:
GET /directors?name=张艺谋&type=电影&page=0&size=20
5. 数据流图
5.1 数据抓取流程
豆瓣服务器 本地应用
│ │
│ 1. HTTP GET 请求 │
│◀──────────────────────────│
│ │
│ 2. HTML 响应 │
│──────────────────────────▶│
│ │
│ ┌────┴────┐
│ │ Jsoup │
│ │ 解析 │
│ └────┬────┘
│ │
│ ┌────┴────┐
│ │ Movie │
│ │ 对象 │
│ └────┬────┘
│ │
│ 存入 List<Movie>
5.2 Web 请求处理流程
浏览器 服务器
│ │
│ GET /directors │
│──────────────────────────▶│
│ ┌────┴────┐
│ │Controller│
│ └────┬────┘
│ │
│ ┌────┴────┐
│ │ Service │
│ │ (检查缓存)│
│ └────┬────┘
│ │
│ ┌────┴────┐
│ │Repository│
│ │ (查询DB) │
│ └────┬────┘
│ │
│ ┌────┴────┐
│ │Thymeleaf│
│ │ 渲染 │
│ └────┬────┘
│ │
│ HTML 响应 │
│◀──────────────────────────│
6. 配置说明
6.1 数据库配置 (application.properties)
# H2 内存数据库
spring.datasource.url=jdbc:h2:mem:moviedb;DB_CLOSE_DELAY=-1
spring.h2.console.enabled=true # 启用 H2 控制台
# JPA 配置
spring.jpa.hibernate.ddl-auto=update # 自动创建表结构
6.2 缓存配置
spring.cache.type=caffeine
spring.cache.cache-names=directorRankings,movieTypes
spring.cache.caffeine.spec=expireAfterWrite=10m,maximumSize=100
- 缓存 10 分钟后过期
- 最大缓存 100 条记录
7. 运行指南
7.1 环境要求
- JDK 11+
- Maven 3.6+
7.2 运行命令
独立控制台模式:
mvn clean compile exec:java -Dexec.mainClass="com.movieratings.Main"
Spring Boot Web 模式:
mvn spring-boot:run
7.3 访问地址
| 功能 | 地址 |
|---|---|
| 导演排行榜 | http://localhost:8080/directors |
| H2 数据库控制台 | http://localhost:8080/h2-console |
| JDBC URL | jdbc:h2:mem:moviedb |
8. 输出文件说明
8.1 独立模式输出
| 文件 | 说明 |
|---|---|
movies_data.json |
完整的电影数据 JSON |
movies_analysis.csv |
电影数据 CSV 表格 |
rating_distribution.png |
评分分布柱状图 |
year_rating_scatter.png |
年份-评分散点图 |
8.2 控制台输出示例
=== 电影数据抓取与分析项目开始 ===
正在抓取: https://movie.douban.com/top250?start=0&filter=
...
--- 电影抓取结果展示 (前 10 条展示) ---
--------------------------------------------------------------------------------------------------
| 排名 | 标题 | 年份 | 评分 | 导演 | 评价人数 |
--------------------------------------------------------------------------------------------------
| 1 | 肖申克的救赎 | 1994 | 9.7 | 弗兰克·德拉邦特 | 2900000 |
| 2 | 霸王别姬 | 1993 | 9.6 | 陈凯歌 | 1900000 |
...
--- 基础统计分析报告 ---
总计分析电影数量: 50
平均评分: 8.92
最高评分: 9.70
最低评分: 8.50
--- 导演作品排行榜 (前 20) ---
------------------------------------------------------------------
| 导演 | 作品数 | 平均分 | 总模拟票房 |
------------------------------------------------------------------
| 克里斯托弗·诺兰 | 7 | 8.9 | 1250000.00 |
| 斯蒂芬·斯皮尔伯格 | 6 | 8.7 | 980000.00 |
...
=== 项目执行完毕 ===
9. 扩展阅读
9.1 相关文档
OOP_封装_继承_多态.md— 项目中的面向对象概念详解README.md— 项目基本信息DEVELOPMENT.md— 开发日志
9.2 学习要点
- 爬虫技术:Jsoup 的使用、HTTP 请求、HTML 解析
- 数据持久化:JPA 实体映射、Repository 模式
- 业务分层:Controller → Service → Repository 架构
- 缓存机制:Caffeine 缓存的使用场景
- 数据分析:Stream API、统计计算、相关性分析
- 可视化:JFreeChart 图表生成
文档版本:1.0 更新日期:2026-04-09