15 KiB
《高级程序设计》项目报告
爬虫项目开发全纪录
一、项目目标
1.1 功能目标
| 功能 | 描述 | 优先级 |
|---|---|---|
| 基础爬虫 | 能够爬取单个网站的指定数据 | 高 |
| 面向对象重构 | 使用继承、接口、多态提高代码复用性 | 高 |
| 多网站支持 | 通过策略模式实现不同网站的解析,至少支持3个网站 | 高 |
| CLI交互 | 提供命令行界面,用户输入命令执行爬取、查询、导出等操作 | 高 |
| 命令模式 | 支持crawl、list、search、stat、export、help、exit、auto等命令 | 高 |
| 异常处理 | 自定义异常体系,统一处理爬取、解析、保存过程中的错误 | 中 |
| 数据持久化 | 爬取数据保存为CSV文件,每个网站独立保存,同时支持整体导出 | 高 |
| 一键自动执行 | auto命令自动依次爬取所有网站并导出统计结果 | 中 |
1.2 预期效果
程序启动后显示命令提示,用户通过help查看命令列表。
输入crawl <url>,根据URL自动选择对应策略,爬取数据并保存为独立的CSV文件。
输入list显示已爬取的所有记录。
输入search <关键词>按标题搜索。
输入stat统计评分分布(或新闻数量)。
输入export将所有数据导出到all.csv。
输入auto一键完成三个网站的爬取、列表、统计和导出。
程序稳定运行,异常时有友好提示并打印堆栈。
二、项目进展(按周填写)
W1:基础爬虫实现
本周任务: 编写一个简单的爬虫,能够爬取豆瓣电影Top250的数据;将爬取到的数据在控制台打印输出;实现基础的URL请求、HTML解析、数据提取。 所学知识:
- Jsoup的基本用法:连接、获取Document、CSS选择器提取元素。
- 豆瓣电影页面的HTML结构分析。
- Java集合类(ArrayList)存储数据。 遇到的困难:
- 初次使用Jsoup时,不了解如何正确选择CSS选择器,导致数据提取失败。
- 豆瓣可能有简单反爬,直接请求偶尔返回验证页。
如何解决的:
1.通过浏览器开发者工具查看元素,逐个尝试选择器,最终确定
".item"、".title"等。 2.在Jsoup连接时添加User-Agent头,模拟浏览器访问。 AI是如何帮助的: 提供了示例代码,解释了CSS选择器的用法,帮助快速上手。
W2:面向对象重构(继承、接口、多态)
本周任务:
- 将单一爬虫代码重构为面向对象结构:定义
Movie模型类、MovieRepository仓库类。 - 创建
Crawler抽象类和具体子类DoubanCrawler,使用继承复用公共爬取逻辑。 - 引入接口
ICrawler,定义统一的crawl()方法,利用多态调用不同爬虫。 所学知识: - Java的继承、接口、多态在实际项目中的应用。
- 设计模式中的“模板方法”思想(抽象类定义框架,子类实现细节)。 遇到的困难:
- 如何设计抽象类才能既复用代码又允许子类灵活扩展?例如,不同网站的解析逻辑差异很大。
- 仓库类存储数据后,如何在多个爬虫之间共享?
如何解决的:
- 将网络请求和分页逻辑放在抽象类中,将
parse()方法定义为抽象方法,由子类实现。 - 使用依赖注入让所有爬虫共享同一个
MovieRepository实例。 AI是如何帮助的: 指导抽象类的设计,给出了BaseCrawler的代码示例,解释了何时使用继承、何时使用接口。
W3:扩展为多网站爬取(策略模式)
本周任务:
- 将原有的继承结构改为策略模式:定义
MovieCrawlStrategy接口,为每个网站编写具体策略类(DoubanTop250Strategy、SinaNewsStrategy、DoubanBookStrategy) - 实现策略工厂
MovieStrategyFactory,根据URL动态返回对应策略。 - 修改
CrawlCommand,支持用户输入URL后自动选择合适的策略进行爬取。 - 支持豆瓣电影Top250全部分页(250条)、新浪新闻(约100条)、豆瓣图书Top25(1页) 所学知识:
- 策略模式的定义与优点:消除大量的if-else,便于扩展新网站。
- 工厂模式配合策略模式的使用。
- 不同网站的分页参数差异(
startvsoffset)。 遇到的困难: 豆瓣图书的HTML结构与豆瓣电影不同,需要重新写解析逻辑。 如何解决的: - 为豆瓣图书单独编写
DoubanBookStrategy,选择正确的CSS选择器(".item"、".pl2 a"等)。 - 在工厂中同时注册三个策略,确保URL匹配正确。
AI是如何帮助的:
提供
SinaNewsStrategy的完整实现,调整分页参数,帮助解决豆瓣图书解析问题。
W4:改造为CLI项目(命令模式)
本周任务:
设计命令行交互界面:使用ConsoleView读取用户输入,循环处理命令。
- 实现命令模式:定义
Command接口,分别实现CrawlCommand、ListCommand、SearchCommand、StatCommand、ExportCommand、HelpCommand、ExitCommand、AutoCommand。 - 创建
MovieController,负责注册命令和分发用户输入。
所学知识:
命令模式在CLI中的典型应用。
如何解析用户输入的参数(args.split("\\s+"))。
控制台彩色输出(ANSI转义码)。
遇到的困难:
- 如何管理多个命令实例?如何保证命令能够访问仓库和视图?
- 用户在命令中输入的URL可能包含空格或特殊字符,需要正确处理。
如何解决的:
- 在
MovieController的构造函数中集中注册所有命令,每个命令都持有ConsoleView和MovieRepository的引用(通过构造函数注入)。 - 使用
String.trim().split("\\s+")按空白符分割。
AI是如何帮助的:
- 提供了命令模式的完整代码示例,包括
Command接口和各个命令类的实现。 - 指导了
MovieController的设计,并建议增加auto命令实现一键自动执行。
W5:异常处理与数据存储完善
本周任务:
- 建立自定义异常体系:
CrawlerException基类,派生CrawlFailedException、ParseFailedException、SaveFailedException。 - 在爬取、解析、保存等关键位置使用自定义异常包装原始异常,并打印堆栈信息。
- 完善数据存储功能:每个网站爬取后立即保存为独立的CSV文件(
douban_movies.csv、sina_news.csv、douban_books.csv),同时保留export命令导出所有数据的movies.csv。 4.实现StatCommand对评分进行统计,SearchCommand支持关键词搜索。
所学知识: 1.自定义异常的设计与使用。 2.OpenCSV库的使用:导出CSV文件。 3.Java Stream API进行数据过滤和分组统计。 遇到的困难:
- 添加自定义异常后,接口方法签名需要声明
throws,导致大量修改。 - CSV文件生成后,Eclipse工作区不自动刷新,找不到文件。 如何解决的:
- 保持接口的
parse方法声明throws ParseFailedException,在命令实现中捕获并处理,不向上抛(保证CLI不崩溃)。 - 在Eclipse中刷新项目(F5)或在文件管理器中查看项目根目录。 AI是如何帮助的:
- 提供异常类的完整代码,指导在
CrawlCommand和ExportCommand中合理使用自定义异常。 - 帮助调试CSV文件保存路径问题,指出相对路径指向项目根目录。
W6:集成与测试
本周任务:
- 整体联调,确保三个网站都能稳定爬取并生成CSV。
- 测试所有命令功能,修复边界情况(如仓库为空时的提示)。
- 编写实验报告,整理项目文档和类图。
所学知识:
- 项目集成与测试策略。
- Mermaid语法绘制类图(用于实验报告)。
- 技术文档撰写。
遇到的困难:
- 豆瓣电影分页时偶尔超时,导致部分数据缺失。
- 新浪新闻的CSS选择器不够精确,抓取到非新闻链接。
如何解决的:
- 增加超时时间到15秒,并在每页之间增加1.5秒延迟,减少请求频率。
- 调整新浪新闻解析逻辑:优先使用
".news-item",若为空则使用"a[href*=/c/]"作为备用,并限制最多30条。
AI是如何帮助的:
- 提供类图的Mermaid代码,指导如何绘制和插入报告。
- 帮助总结项目亮点和不足,完善总结部分。
三、项目结构
3.1 最终包结构
DoubanMovieCLI/ ├── src/ │ └── main/ │ └── java/ │ └── com/ │ └── example/ │ └── moviecli/ │ ├── Main.java │ ├── controller/ │ │ └── MovieController.java │ ├── command/ │ │ ├── Command.java │ │ ├── CrawlCommand.java │ │ ├── ListCommand.java │ │ ├── SearchCommand.java │ │ ├── StatCommand.java │ │ ├── ExportCommand.java │ │ ├── HelpCommand.java │ │ ├── ExitCommand.java │ │ └── AutoCommand.java │ ├── model/ │ │ └── Movie.java │ ├── repository/ │ │ └── MovieRepository.java │ ├── strategy/ │ │ ├── MovieCrawlStrategy.java │ │ ├── DoubanTop250Strategy.java │ │ ├── SinaNewsStrategy.java │ │ ├── DoubanBookStrategy.java │ │ └── MovieStrategyFactory.java │ ├── view/ │ │ └── ConsoleView.java │ └── exception/ │ ├── CrawlerException.java │ ├── CrawlFailedException.java │ ├── ParseFailedException.java │ └── SaveFailedException.java
3.2 类图
classDiagram
class Main {
+main()
}
class MovieController {
-Map~String,Command~ commands
+handle(String input)
}
class Command {
<<interface>>
+getName() String
+execute(String[]~args~, MovieRepository)
}
class CrawlCommand
class ListCommand
class SearchCommand
class StatCommand
class ExportCommand
class HelpCommand
class ExitCommand
class AutoCommand
Command <|.. CrawlCommand
Command <|.. ListCommand
Command <|.. SearchCommand
Command <|.. StatCommand
Command <|.. ExportCommand
Command <|.. HelpCommand
Command <|.. ExitCommand
Command <|.. AutoCommand
class MovieRepository {
-List~Movie~ movies
+add(Movie)
+addAll(List~Movie~)
+getAll() List~Movie~
}
class Movie {
-int rank
-String title
-String originalTitle
-String score
-String year
-String director
+getRank() int
+getTitle() String
}
class ConsoleView {
+readLine() String
+printSuccess(String)
+printError(String)
+printInfo(String)
}
class MovieCrawlStrategy {
<<interface>>
+supports(String) boolean
+parse(Document) List~Movie~
}
class DoubanTop250Strategy
class SinaNewsStrategy
class DoubanBookStrategy
class MovieStrategyFactory {
-List~MovieCrawlStrategy~ strategies
+getStrategy(String) MovieCrawlStrategy
+register(MovieCrawlStrategy)
}
MovieCrawlStrategy <|.. DoubanTop250Strategy
MovieCrawlStrategy <|.. SinaNewsStrategy
MovieCrawlStrategy <|.. DoubanBookStrategy
class CrawlerException
class CrawlFailedException
class ParseFailedException
class SaveFailedException
CrawlerException <|-- CrawlFailedException
CrawlerException <|-- ParseFailedException
CrawlerException <|-- SaveFailedException
四、成果展示
4.1 运行截图
4.2 功能测试
| 功能 | 测试结果 | 备注 |
|---|---|---|
help |
✅ 通过 | 显示所有命令及URL示例 |
crawl https://movie.douban.com/top250 |
✅ 通过 | 成功爬取250条,生成douban_movies.csv |
crawl https://news.sina.com.cn/ |
✅ 通过 | 成功爬取约100条新闻,生成sina_news.csv |
crawl https://book.douban.com/top250 |
✅ 通过 | 成功爬取25条图书,生成douban_books.csv |
list |
✅ 通过 | 显示所有已爬取记录 |
search 肖申克 |
✅ 通过 | 筛选出包含关键词的记录 |
stat |
✅ 通过 | 统计评分(或新闻来源)分布 |
export |
✅ 通过 | 合并导出movies.csv |
auto |
✅ 通过 | 自动依次完成三个网站爬取、列表、统计、导出 |
exit |
✅ 通过 | 正常退出程序 |
五、总结
本项目按照迭代开发的方式,从最简单的单一网站爬虫起步,逐步引入面向对象特性(继承、接口、多态),然后重构为策略模式以支持多网站,再添加CLI命令模式提升交互性,最后完善异常体系和数据存储。整个过程完整实践了软件工程的常见设计模式,并成功实现了三个不同网站的数据爬取与持久化。
1.主要成果包括: (1)逐步完善程序: 每周一个里程碑,代码逐步演进,避免了前期过度设计。 (2)设计模式应用: 策略模式:解决了不同网站解析逻辑的封装与切换。 命令模式:使CLI功能易于扩展,新增命令只需实现接口并注册。 工厂模式:策略工厂根据URL动态创建对应的策略。 (3)异常处理: 自定义异常体系,在关键环节包装原始异常并打印堆栈,便于调试。 (4)完整的CLI交互: 支持8个命令,提供auto一键自动化功能,用户体验良好。 (5)数据持久化: 每个网站独立保存CSV,同时支持合并导出,数据可重复使用。
通过本项目,我深入理解了Java设计模式在实际开发中的价值,掌握了Jsoup爬虫的基本技巧以及应对反爬虫的简单策略(如User-Agent、请求延迟)。同时,对Maven/Eclipse的项目管理、CSV文件操作也有了实践认识。
2.未来可以继续改进的方向:
(1)增加更多的网站策略(如知乎热榜、微博热搜)。 (2)引入代理池应对更严格的反爬。 (3)增加图形化界面或Web界面。 (4)支持定时自动爬取和数据持久化到数据库。












