# 电影数据抓取与分析项目 - 流程文档 > 本文档面向 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 │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ 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 │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ MovieRepository.findDirectorRankings() │ │ │ │ ──────────────────────────────── │ │ │ │ • 执行 JPQL 聚合查询 │ │ │ │ • GROUP BY director │ │ │ │ • 返回导演统计数据 │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ Thymeleaf 模板渲染 │ │ │ │ ──────────────── │ │ │ │ • director_rankings.html │ │ │ │ • 返回 HTML 页面给浏览器 │ │ │ └──────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 4. 核心模块详解 ### 4.1 爬虫模块 (MovieCrawler) **职责**:从豆瓣电影 Top 250 抓取数据 **流程**: ``` ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ 构造请求URL │───▶│ 发送HTTP请求│───▶│ 解析HTML │───▶│ 封装Movie │ │ (分页参数) │ │ (Jsoup) │ │ (CSS选择器)│ │ 对象列表 │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ ``` **关键代码**: ```java // 发送请求 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) │ │ └──▶ 返回 DoubleSummaryStatistics (平均值、最大值、最小值、计数) │ │ │ │ countMoviesByRatingRange(List) │ │ └──▶ 返回 Map (评分段 → 电影数量) │ │ 评分段: 9.5-10.0, 9.0-9.4, 8.5-8.9, 8.5以下 │ │ │ │ findMostReviewed(List, int n) │ │ └──▶ 返回评价人数最多的前 N 部电影 │ │ │ │ analyzeYearRatingCorrelation(List) │ │ └──▶ 返回 CorrelationResult (Pearson 相关系数 + 显著性检验) │ │ │ │ getTopDirectors(List, int topN) │ │ └──▶ 返回 List (按作品数排序的导演排行) │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` **内部类**: - `DirectorStats` — 导演统计结果(姓名、作品数、平均分、总票房) - `CorrelationResult` — 相关性分析结果(系数、p 值、显著性描述) --- ### 4.4 数据持久化层 **MovieRepository 接口**: ``` ┌─────────────────────────────────────────────────────────────────┐ │ MovieRepository │ │ extends JpaRepository │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 继承自 JpaRepository 的方法: │ │ • save(Movie), saveAll(List) │ │ • deleteAll(), deleteById(Long) │ │ • findById(Long), findAll() │ │ │ │ 自定义方法: │ │ • findDirectorRankings(name, type, pageable) │ │ └── JPQL 聚合查询,返回导演排行榜 │ │ • findByDirector(String director) │ │ └── 按导演名查询作品列表 │ │ • findAllTypes() │ │ └── 查询所有不同的作品类型 │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **JPQL 聚合查询示例**: ```java @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 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 ``` ### 5.2 Web 请求处理流程 ``` 浏览器 服务器 │ │ │ GET /directors │ │──────────────────────────▶│ │ ┌────┴────┐ │ │Controller│ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │ Service │ │ │ (检查缓存)│ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │Repository│ │ │ (查询DB) │ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │Thymeleaf│ │ │ 渲染 │ │ └────┬────┘ │ │ │ HTML 响应 │ │◀──────────────────────────│ ``` --- ## 6. 配置说明 ### 6.1 数据库配置 (application.properties) ```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 缓存配置 ```properties 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 运行命令 **独立控制台模式**: ```bash mvn clean compile exec:java -Dexec.mainClass="com.movieratings.Main" ``` **Spring Boot Web 模式**: ```bash 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 学习要点 1. **爬虫技术**:Jsoup 的使用、HTTP 请求、HTML 解析 2. **数据持久化**:JPA 实体映射、Repository 模式 3. **业务分层**:Controller → Service → Repository 架构 4. **缓存机制**:Caffeine 缓存的使用场景 5. **数据分析**:Stream API、统计计算、相关性分析 6. **可视化**:JFreeChart 图表生成 --- *文档版本:1.0* *更新日期:2026-04-09*