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.

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解析、数据提取。 所学知识:

  1. Jsoup的基本用法:连接、获取Document、CSS选择器提取元素。
  2. 豆瓣电影页面的HTML结构分析。
  3. Java集合类(ArrayList)存储数据。 遇到的困难:
  4. 初次使用Jsoup时,不了解如何正确选择CSS选择器,导致数据提取失败。
  5. 豆瓣可能有简单反爬,直接请求偶尔返回验证页。 如何解决的: 1.通过浏览器开发者工具查看元素,逐个尝试选择器,最终确定".item"".title"等。 2.在Jsoup连接时添加User-Agent头,模拟浏览器访问。 AI是如何帮助的: 提供了示例代码,解释了CSS选择器的用法,帮助快速上手。

W2:面向对象重构(继承、接口、多态)

本周任务:

  1. 将单一爬虫代码重构为面向对象结构:定义Movie模型类、MovieRepository仓库类。
  2. 创建Crawler抽象类和具体子类DoubanCrawler,使用继承复用公共爬取逻辑。
  3. 引入接口ICrawler,定义统一的crawl()方法,利用多态调用不同爬虫。 所学知识:
  4. Java的继承、接口、多态在实际项目中的应用。
  5. 设计模式中的“模板方法”思想(抽象类定义框架,子类实现细节)。 遇到的困难:
  6. 如何设计抽象类才能既复用代码又允许子类灵活扩展?例如,不同网站的解析逻辑差异很大。
  7. 仓库类存储数据后,如何在多个爬虫之间共享?

如何解决的:

  1. 将网络请求和分页逻辑放在抽象类中,将parse()方法定义为抽象方法,由子类实现。
  2. 使用依赖注入让所有爬虫共享同一个MovieRepository实例。 AI是如何帮助的: 指导抽象类的设计,给出了BaseCrawler的代码示例,解释了何时使用继承、何时使用接口。

W3:扩展为多网站爬取(策略模式)

本周任务:

  1. 将原有的继承结构改为策略模式:定义MovieCrawlStrategy接口,为每个网站编写具体策略类(DoubanTop250StrategySinaNewsStrategyDoubanBookStrategy
  2. 实现策略工厂MovieStrategyFactory,根据URL动态返回对应策略。
  3. 修改CrawlCommand,支持用户输入URL后自动选择合适的策略进行爬取。
  4. 支持豆瓣电影Top250全部分页(250条)、新浪新闻(约100条)、豆瓣图书Top25(1页) 所学知识:
  5. 策略模式的定义与优点:消除大量的if-else,便于扩展新网站。
  6. 工厂模式配合策略模式的使用。
  7. 不同网站的分页参数差异(start vs offset)。 遇到的困难: 豆瓣图书的HTML结构与豆瓣电影不同,需要重新写解析逻辑。 如何解决的:
  8. 为豆瓣图书单独编写DoubanBookStrategy,选择正确的CSS选择器(".item"".pl2 a"等)。
  9. 在工厂中同时注册三个策略,确保URL匹配正确。 AI是如何帮助的: 提供SinaNewsStrategy的完整实现,调整分页参数,帮助解决豆瓣图书解析问题。

W4:改造为CLI项目(命令模式)

本周任务: 设计命令行交互界面:使用ConsoleView读取用户输入,循环处理命令。

  • 实现命令模式:定义Command接口,分别实现CrawlCommandListCommandSearchCommandStatCommandExportCommandHelpCommandExitCommandAutoCommand
  • 创建MovieController,负责注册命令和分发用户输入。

所学知识: 命令模式在CLI中的典型应用。 如何解析用户输入的参数(args.split("\\s+"))。 控制台彩色输出(ANSI转义码)。

遇到的困难:

  1. 如何管理多个命令实例?如何保证命令能够访问仓库和视图?
  2. 用户在命令中输入的URL可能包含空格或特殊字符,需要正确处理。

如何解决的:

  1. MovieController的构造函数中集中注册所有命令,每个命令都持有ConsoleViewMovieRepository的引用(通过构造函数注入)。
  2. 使用String.trim().split("\\s+")按空白符分割。

AI是如何帮助的:

  1. 提供了命令模式的完整代码示例,包括Command接口和各个命令类的实现。
  2. 指导了MovieController的设计,并建议增加auto命令实现一键自动执行。

W5:异常处理与数据存储完善

本周任务:

  1. 建立自定义异常体系:CrawlerException基类,派生CrawlFailedExceptionParseFailedExceptionSaveFailedException
  2. 在爬取、解析、保存等关键位置使用自定义异常包装原始异常,并打印堆栈信息。
  3. 完善数据存储功能:每个网站爬取后立即保存为独立的CSV文件(douban_movies.csvsina_news.csvdouban_books.csv),同时保留export命令导出所有数据的movies.csv。 4.实现StatCommand对评分进行统计,SearchCommand支持关键词搜索。

所学知识: 1.自定义异常的设计与使用。 2.OpenCSV库的使用:导出CSV文件。 3.Java Stream API进行数据过滤和分组统计。 遇到的困难:

  1. 添加自定义异常后,接口方法签名需要声明throws,导致大量修改。
  2. CSV文件生成后,Eclipse工作区不自动刷新,找不到文件。 如何解决的:
  3. 保持接口的parse方法声明throws ParseFailedException,在命令实现中捕获并处理,不向上抛(保证CLI不崩溃)。
  4. 在Eclipse中刷新项目(F5)或在文件管理器中查看项目根目录。 AI是如何帮助的:
  5. 提供异常类的完整代码,指导在CrawlCommandExportCommand中合理使用自定义异常。
  6. 帮助调试CSV文件保存路径问题,指出相对路径指向项目根目录。

W6:集成与测试

本周任务:

  1. 整体联调,确保三个网站都能稳定爬取并生成CSV。
  2. 测试所有命令功能,修复边界情况(如仓库为空时的提示)。
  3. 编写实验报告,整理项目文档和类图。

所学知识:

  • 项目集成与测试策略。
  • Mermaid语法绘制类图(用于实验报告)。
  • 技术文档撰写。

遇到的困难:

  1. 豆瓣电影分页时偶尔超时,导致部分数据缺失。
  2. 新浪新闻的CSS选择器不够精确,抓取到非新闻链接。

如何解决的:

  1. 增加超时时间到15秒,并在每页之间增加1.5秒延迟,减少请求频率。
  2. 调整新浪新闻解析逻辑:优先使用".news-item",若为空则使用"a[href*=/c/]"作为备用,并限制最多30条。

AI是如何帮助的:

  1. 提供类图的Mermaid代码,指导如何绘制和插入报告。
  2. 帮助总结项目亮点和不足,完善总结部分。

三、项目结构

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 运行截图

图1:程序启动后显示欢迎信息及help命令列表 alt text

图2:爬取豆瓣电影Top250,分页进度显示 alt text

图3:list命令输出前35条电影 alt text

图4:stat命令输出评分分布 alt text

图5:auto命令依次执行三个网站爬取并保存独立CSV alt text

图6:展台数据展示 豆瓣电影: alt text ... 新浪新闻 alt text ... alt text 豆瓣图书 alt text alt text 评分分布: alt text

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)支持定时自动爬取和数据持久化到数据库。