19 changed files with 0 additions and 910 deletions
@ -1,21 +0,0 @@ |
|||
package com.example.datacollect; |
|||
|
|||
import com.example.datacollect.controller.CrawlerController; |
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.strategy.StrategyFactory; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
|
|||
public class Main { |
|||
|
|||
public static void main(String[] args) { |
|||
ConsoleView view = new ConsoleView(); |
|||
ArticleRepository repository = new ArticleRepository(); |
|||
StrategyFactory strategyFactory = new StrategyFactory(); |
|||
CrawlerController controller = new CrawlerController(view, repository, strategyFactory); |
|||
|
|||
view.printSuccess("Welcome to CLI Crawler (w10_3)! Type help for commands."); |
|||
while (true) { |
|||
controller.handle(view.readLine()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,144 +0,0 @@ |
|||
# W10 作业提交:设计模式实战 |
|||
|
|||
## 目录结构 |
|||
|
|||
``` |
|||
w10/ |
|||
└── src/ |
|||
└── main/ |
|||
└── java/ |
|||
└── com/ |
|||
└── example/ |
|||
└── datacollect/ |
|||
├── Main.java |
|||
├── command/ |
|||
│ ├── Command.java |
|||
│ ├── CrawlCommand.java |
|||
│ ├── AnalyzeCommand.java |
|||
│ ├── ListCommand.java |
|||
│ ├── HelpCommand.java |
|||
│ ├── ExitCommand.java |
|||
│ └── HistoryCommand.java |
|||
├── controller/ |
|||
│ └── CrawlerController.java |
|||
├── model/ |
|||
│ └── Article.java |
|||
├── repository/ |
|||
│ └── ArticleRepository.java |
|||
├── strategy/ |
|||
│ ├── CrawlStrategy.java |
|||
│ ├── StrategyFactory.java |
|||
│ ├── HnuNewsStrategy.java |
|||
│ ├── BlogStrategy.java |
|||
│ ├── NewsStrategy.java |
|||
│ └── GenericNewsStrategy.java |
|||
└── view/ |
|||
└── ConsoleView.java |
|||
``` |
|||
|
|||
## 必做任务完成情况 |
|||
|
|||
### 1. ArticleRepository 完善 ✅ |
|||
- `add()`: 拒绝 null,抛出 IllegalArgumentException |
|||
- `addAll()`: 拒绝 null 列表和列表中的 null 元素 |
|||
- `getAll()`: 返回 `Collections.unmodifiableList()` 不可变视图 |
|||
- `size()`: 返回文章数量 |
|||
- `clear()`: 清空所有文章 |
|||
|
|||
### 2. AnalyzeCommand ✅ |
|||
- 复用策略解析但**不存储**到 Repository |
|||
- 输出统计信息:文章总数、含作者/日期/内容的数量、使用的策略名称 |
|||
- 显示前 3 篇文章标题作为预览 |
|||
|
|||
### 3. AI 架构审计 ✅ |
|||
|
|||
#### 类签名汇总 |
|||
|
|||
```java |
|||
// Command 层 |
|||
interface Command { void execute(String[], ArticleRepository); } |
|||
class CrawlCommand(ConsoleView, StrategyFactory) |
|||
class AnalyzeCommand(ConsoleView, StrategyFactory) |
|||
class ListCommand(ConsoleView) |
|||
class HelpCommand(ConsoleView) |
|||
class ExitCommand(ConsoleView) |
|||
class HistoryCommand(ConsoleView, List<String>) |
|||
|
|||
// Controller 层 |
|||
class CrawlerController(ConsoleView, ArticleRepository, StrategyFactory) |
|||
|
|||
// Repository 层 |
|||
class ArticleRepository { add(), addAll(), getAll(), size(), clear() } |
|||
|
|||
// Strategy 层 |
|||
interface CrawlStrategy { parse(), supports(), getPriority(), getPattern() } |
|||
class StrategyFactory { getStrategy(url), register(), setDefaultStrategy() } |
|||
class HnuNewsStrategy implements CrawlStrategy |
|||
class BlogStrategy implements CrawlStrategy |
|||
class NewsStrategy implements CrawlStrategy |
|||
class GenericNewsStrategy implements CrawlStrategy (正则匹配) |
|||
|
|||
// Model 层 |
|||
class Article { title, url, content, author, publishDate } |
|||
|
|||
// View 层 |
|||
class ConsoleView |
|||
``` |
|||
|
|||
#### 架构审计结果 |
|||
|
|||
| 检查项 | 结果 | 说明 | |
|||
|--------|------|------| |
|||
| **策略解耦** | ✅ 优秀 | 策略接口与实现完全分离 | |
|||
| **Repository 封装** | ✅ 优秀 | 使用不可变视图 + null 防御 | |
|||
| **开闭原则** | ✅ 达标 | 新增网站只需加策略类 + 注册一行 | |
|||
| **依赖倒置** | ✅ 良好 | Command/Strategy 依赖抽象接口 | |
|||
| **单一职责** | ✅ 达标 | 每个类职责清晰 | |
|||
| **循环依赖** | ✅ 无 | 依赖链单向 | |
|||
|
|||
## 选做任务完成情况 |
|||
|
|||
### 正则策略匹配 ✅ |
|||
- `GenericNewsStrategy` 使用正则表达式 `.*\.(news|press|article)s?\..*` 匹配新闻类网站 |
|||
|
|||
### 默认策略 ✅ |
|||
- `StrategyFactory` 内置 `DefaultStrategy`,当没有匹配策略时返回空列表 |
|||
|
|||
### 策略优先级 ✅ |
|||
- `CrawlStrategy` 接口新增 `getPriority()` 默认方法 |
|||
- `GenericNewsStrategy` 设置优先级为 5(高于默认优先级 1) |
|||
- `StrategyFactory.getStrategy()` 遍历所有策略,选择优先级最高的匹配策略 |
|||
|
|||
### 思考题答案 |
|||
|
|||
**Q: 两个策略都 supports 同一 URL 时怎么办?** |
|||
|
|||
**A:** 采用**优先级机制**解决: |
|||
|
|||
1. 每个策略实现可以通过 `getPriority()` 返回优先级值 |
|||
2. `StrategyFactory.getStrategy()` 遍历所有策略时,记录最高优先级 |
|||
3. 如果多个策略都支持同一 URL,选择优先级最高的那个 |
|||
4. 如果优先级相同,选择最先注册的策略(遍历顺序决定) |
|||
|
|||
这种设计的优势: |
|||
- 允许通用策略(如 `GenericNewsStrategy`)和专用策略(如 `HnuNewsStrategy`)共存 |
|||
- 专用策略可设置更高优先级,确保精确匹配优先 |
|||
- 通用策略作为兜底,提高系统兼容性 |
|||
|
|||
## 命令功能对比 |
|||
|
|||
| 命令 | 功能 | 是否存储 | |
|||
|------|------|----------| |
|||
| `crawl <url>` | 爬取并存储文章 | ✅ 是 | |
|||
| `analyze <url>` | 分析文章统计(不存储) | ❌ 否 | |
|||
| `list` | 列出已存储文章 | - | |
|||
| `history` | 显示命令历史 | - | |
|||
| `help` | 显示帮助 | - | |
|||
| `exit` | 退出程序 | - | |
|||
|
|||
## 设计模式应用 |
|||
|
|||
1. **策略模式**:`CrawlStrategy` 接口定义标准,各策略独立实现 |
|||
2. **工厂模式**:`StrategyFactory` 根据 URL 自动选择策略 |
|||
3. **Repository 模式**:数据访问封装,防御式编程 |
|||
4. **命令模式**:所有 Command 统一签名,易于扩展 |
|||
@ -1,87 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.strategy.CrawlStrategy; |
|||
import com.example.datacollect.strategy.StrategyFactory; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class AnalyzeCommand implements Command { |
|||
private final ConsoleView view; |
|||
private final StrategyFactory strategyFactory; |
|||
|
|||
public AnalyzeCommand(ConsoleView view, StrategyFactory strategyFactory) { |
|||
this.view = view; |
|||
this.strategyFactory = strategyFactory; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "analyze"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
if (args.length < 2) { |
|||
view.printError("Usage: analyze <url>"); |
|||
return; |
|||
} |
|||
String url = args[1]; |
|||
|
|||
CrawlStrategy strategy = strategyFactory.getStrategy(url); |
|||
if (strategy == null) { |
|||
view.printError("No strategy found for: " + url); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
view.printInfo("Analyzing: " + url); |
|||
Document doc = Jsoup.connect(url).get(); |
|||
List<Article> parsed = strategy.parse(url, doc); |
|||
|
|||
view.printInfo("=== Analysis Report ==="); |
|||
view.printInfo("Total articles found: " + parsed.size()); |
|||
|
|||
int titlesWithAuthor = 0; |
|||
int titlesWithDate = 0; |
|||
int titlesWithContent = 0; |
|||
|
|||
for (Article article : parsed) { |
|||
if (article.getAuthor() != null && !article.getAuthor().isEmpty()) { |
|||
titlesWithAuthor++; |
|||
} |
|||
if (article.getPublishDate() != null && !article.getPublishDate().isEmpty()) { |
|||
titlesWithDate++; |
|||
} |
|||
if (article.getContent() != null && !article.getContent().isEmpty()) { |
|||
titlesWithContent++; |
|||
} |
|||
} |
|||
|
|||
view.printInfo("Articles with author: " + titlesWithAuthor); |
|||
view.printInfo("Articles with publish date: " + titlesWithDate); |
|||
view.printInfo("Articles with content: " + titlesWithContent); |
|||
view.printInfo("Strategy used: " + strategy.getClass().getSimpleName()); |
|||
|
|||
if (parsed.size() > 0) { |
|||
view.printInfo("\nSample article titles:"); |
|||
int limit = Math.min(3, parsed.size()); |
|||
for (int i = 0; i < limit; i++) { |
|||
view.printInfo("- " + parsed.get(i).getTitle()); |
|||
} |
|||
if (parsed.size() > 3) { |
|||
view.printInfo("... and " + (parsed.size() - 3) + " more"); |
|||
} |
|||
} |
|||
|
|||
view.printSuccess("Analysis completed (not stored)"); |
|||
|
|||
} catch (Exception e) { |
|||
view.printError("Failed to analyze: " + e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
|
|||
public interface Command { |
|||
String getName(); |
|||
void execute(String[] args, ArticleRepository repository); |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.strategy.CrawlStrategy; |
|||
import com.example.datacollect.strategy.StrategyFactory; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
|
|||
public class CrawlCommand implements Command { |
|||
private final ConsoleView view; |
|||
private final StrategyFactory strategyFactory; |
|||
|
|||
public CrawlCommand(ConsoleView view, StrategyFactory strategyFactory) { |
|||
this.view = view; |
|||
this.strategyFactory = strategyFactory; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "crawl"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
if (args.length < 2) { |
|||
view.printError("Usage: crawl <url>"); |
|||
return; |
|||
} |
|||
String url = args[1]; |
|||
|
|||
CrawlStrategy strategy = strategyFactory.getStrategy(url); |
|||
if (strategy == null) { |
|||
view.printError("No strategy found for: " + url); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
view.printInfo("Crawling: " + url); |
|||
Document doc = Jsoup.connect(url).get(); |
|||
var articles = strategy.parse(url, doc); |
|||
for (var article : articles) { |
|||
repository.add(article); |
|||
} |
|||
view.printSuccess("Crawled " + articles.size() + " articles."); |
|||
} catch (Exception e) { |
|||
view.printError("Failed to crawl: " + e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
|
|||
public class ExitCommand implements Command { |
|||
private final ConsoleView view; |
|||
|
|||
public ExitCommand(ConsoleView view) { |
|||
this.view = view; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "exit"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
view.printSuccess("Bye!"); |
|||
System.exit(0); |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
|
|||
public class HelpCommand implements Command { |
|||
private final ConsoleView view; |
|||
|
|||
public HelpCommand(ConsoleView view) { |
|||
this.view = view; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "help"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
view.printInfo("Commands:"); |
|||
view.printInfo(" crawl <url> - Crawl articles from URL and store"); |
|||
view.printInfo(" analyze <url> - Analyze URL without storing"); |
|||
view.printInfo(" list - List all stored articles"); |
|||
view.printInfo(" history - Show command history"); |
|||
view.printInfo(" help - Show this help"); |
|||
view.printInfo(" exit - Exit the program"); |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class HistoryCommand implements Command { |
|||
private final ConsoleView view; |
|||
private final List<String> commandHistory; |
|||
|
|||
public HistoryCommand(ConsoleView view, List<String> commandHistory) { |
|||
this.view = view; |
|||
this.commandHistory = commandHistory; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "history"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
if (commandHistory.isEmpty()) { |
|||
view.printInfo("No command history."); |
|||
return; |
|||
} |
|||
view.printInfo("Command history:"); |
|||
for (int i = 0; i < commandHistory.size(); i++) { |
|||
view.printInfo((i + 1) + ". " + commandHistory.get(i)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
package com.example.datacollect.command; |
|||
|
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
|
|||
public class ListCommand implements Command { |
|||
private final ConsoleView view; |
|||
|
|||
public ListCommand(ConsoleView view) { |
|||
this.view = view; |
|||
} |
|||
|
|||
@Override |
|||
public String getName() { |
|||
return "list"; |
|||
} |
|||
|
|||
@Override |
|||
public void execute(String[] args, ArticleRepository repository) { |
|||
view.display(repository.getAll()); |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
package com.example.datacollect.controller; |
|||
|
|||
import com.example.datacollect.command.AnalyzeCommand; |
|||
import com.example.datacollect.command.Command; |
|||
import com.example.datacollect.command.CrawlCommand; |
|||
import com.example.datacollect.command.ExitCommand; |
|||
import com.example.datacollect.command.HelpCommand; |
|||
import com.example.datacollect.command.HistoryCommand; |
|||
import com.example.datacollect.command.ListCommand; |
|||
import com.example.datacollect.repository.ArticleRepository; |
|||
import com.example.datacollect.strategy.StrategyFactory; |
|||
import com.example.datacollect.view.ConsoleView; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
public class CrawlerController { |
|||
private final Map<String, Command> commands = new HashMap<>(); |
|||
private final ConsoleView view; |
|||
private final ArticleRepository repository; |
|||
private final List<String> commandHistory = new ArrayList<>(); |
|||
|
|||
public CrawlerController(ConsoleView view, ArticleRepository repository, StrategyFactory strategyFactory) { |
|||
this.view = view; |
|||
this.repository = repository; |
|||
register(new HelpCommand(view)); |
|||
register(new ListCommand(view)); |
|||
register(new CrawlCommand(view, strategyFactory)); |
|||
register(new AnalyzeCommand(view, strategyFactory)); |
|||
register(new ExitCommand(view)); |
|||
register(new HistoryCommand(view, commandHistory)); |
|||
} |
|||
|
|||
private void register(Command command) { |
|||
commands.put(command.getName(), command); |
|||
} |
|||
|
|||
public void handle(String input) { |
|||
String text = input == null ? "" : input.trim(); |
|||
if (text.isEmpty()) { |
|||
return; |
|||
} |
|||
|
|||
commandHistory.add(text); |
|||
|
|||
String[] args = text.split("\\s+"); |
|||
String cmdName = args[0].toLowerCase(); |
|||
Command command = commands.get(cmdName); |
|||
if (command == null) { |
|||
view.printError("Unknown command: " + cmdName); |
|||
return; |
|||
} |
|||
command.execute(args, repository); |
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
package com.example.datacollect.model; |
|||
|
|||
public class Article { |
|||
private String title; |
|||
private String url; |
|||
private String content; |
|||
private String author; |
|||
private String publishDate; |
|||
|
|||
public Article(String title, String url, String content) { |
|||
this.title = title; |
|||
this.url = url; |
|||
this.content = content; |
|||
} |
|||
|
|||
public Article(String title, String url, String content, String author, String publishDate) { |
|||
this.title = title; |
|||
this.url = url; |
|||
this.content = content; |
|||
this.author = author; |
|||
this.publishDate = publishDate; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public void setTitle(String title) { |
|||
this.title = title; |
|||
} |
|||
|
|||
public String getUrl() { |
|||
return url; |
|||
} |
|||
|
|||
public void setUrl(String url) { |
|||
this.url = url; |
|||
} |
|||
|
|||
public String getContent() { |
|||
return content; |
|||
} |
|||
|
|||
public void setContent(String content) { |
|||
this.content = content; |
|||
} |
|||
|
|||
public String getAuthor() { |
|||
return author; |
|||
} |
|||
|
|||
public void setAuthor(String author) { |
|||
this.author = author; |
|||
} |
|||
|
|||
public String getPublishDate() { |
|||
return publishDate; |
|||
} |
|||
|
|||
public void setPublishDate(String publishDate) { |
|||
this.publishDate = publishDate; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Article{" |
|||
+ "title='" + title + '\'' |
|||
+ ", url='" + url + '\'' |
|||
+ ", author='" + author + '\'' |
|||
+ ", publishDate='" + publishDate + '\'' |
|||
+ '}'; |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
package com.example.datacollect.repository; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
public class ArticleRepository { |
|||
private final List<Article> articles = new ArrayList<>(); |
|||
|
|||
public void add(Article article) { |
|||
if (article == null) { |
|||
throw new IllegalArgumentException("Article cannot be null"); |
|||
} |
|||
articles.add(article); |
|||
} |
|||
|
|||
public void addAll(List<Article> newArticles) { |
|||
if (newArticles == null) { |
|||
throw new IllegalArgumentException("Article list cannot be null"); |
|||
} |
|||
for (Article article : newArticles) { |
|||
if (article == null) { |
|||
throw new IllegalArgumentException("Article in list cannot be null"); |
|||
} |
|||
articles.add(article); |
|||
} |
|||
} |
|||
|
|||
public List<Article> getAll() { |
|||
return Collections.unmodifiableList(articles); |
|||
} |
|||
|
|||
public int size() { |
|||
return articles.size(); |
|||
} |
|||
|
|||
public void clear() { |
|||
articles.clear(); |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class BlogStrategy implements CrawlStrategy { |
|||
@Override |
|||
public boolean supports(String url) { |
|||
return url.contains("blog.example.com"); |
|||
} |
|||
|
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
List<Article> articles = new ArrayList<>(); |
|||
Elements titles = doc.select(".post-title"); |
|||
for (Element e : titles) { |
|||
articles.add(new Article(e.text(), url, "")); |
|||
} |
|||
return articles; |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import org.jsoup.nodes.Document; |
|||
import java.util.List; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public interface CrawlStrategy { |
|||
List<Article> parse(String url, Document doc); |
|||
boolean supports(String url); |
|||
|
|||
default int getPriority() { |
|||
return 1; |
|||
} |
|||
|
|||
default Pattern getPattern() { |
|||
return null; |
|||
} |
|||
} |
|||
@ -1,57 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public class GenericNewsStrategy implements CrawlStrategy { |
|||
private static final Pattern PATTERN = Pattern.compile(".*\\.(news|press|article)s?\\..*"); |
|||
private static final int PRIORITY = 5; |
|||
|
|||
@Override |
|||
public boolean supports(String url) { |
|||
return PATTERN.matcher(url).find(); |
|||
} |
|||
|
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
List<Article> articles = new ArrayList<>(); |
|||
|
|||
Elements items = doc.select("article, .news-item, .article-item, [class*='news'], [class*='article']"); |
|||
|
|||
for (Element item : items) { |
|||
String title = item.selectFirst("h1, h2, h3, .title, [class*='title']") != null |
|||
? item.selectFirst("h1, h2, h3, .title, [class*='title']").text().trim() |
|||
: ""; |
|||
|
|||
String articleUrl = item.selectFirst("a[href]") != null |
|||
? item.selectFirst("a[href]").attr("abs:href") |
|||
: url; |
|||
|
|||
String content = item.selectFirst("p, .content, [class*='content']") != null |
|||
? item.selectFirst("p, .content, [class*='content']").text().trim() |
|||
: ""; |
|||
|
|||
if (!title.isEmpty()) { |
|||
articles.add(new Article(title, articleUrl, content)); |
|||
} |
|||
} |
|||
|
|||
return articles; |
|||
} |
|||
|
|||
@Override |
|||
public int getPriority() { |
|||
return PRIORITY; |
|||
} |
|||
|
|||
@Override |
|||
public Pattern getPattern() { |
|||
return PATTERN; |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class HnuNewsStrategy implements CrawlStrategy { |
|||
@Override |
|||
public boolean supports(String url) { |
|||
return url.contains("news.hnu.edu.cn"); |
|||
} |
|||
|
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
List<Article> articles = new ArrayList<>(); |
|||
Elements listItems = doc.select("ul.list11 li"); |
|||
|
|||
for (Element li : listItems) { |
|||
Element link = li.selectFirst("a"); |
|||
if (link == null) continue; |
|||
|
|||
String articleUrl = link.attr("href"); |
|||
if (!articleUrl.startsWith("http")) { |
|||
articleUrl = "https://news.hnu.edu.cn" + articleUrl.replace("..", ""); |
|||
} |
|||
|
|||
String title = ""; |
|||
Element titleEl = link.selectFirst("h4.l2.h4s2"); |
|||
if (titleEl != null) { |
|||
title = titleEl.text().trim(); |
|||
} |
|||
|
|||
String content = ""; |
|||
Element contentEl = link.selectFirst("p.l3.ps3"); |
|||
if (contentEl != null) { |
|||
content = contentEl.text().trim(); |
|||
} |
|||
|
|||
if (!title.isEmpty()) { |
|||
articles.add(new Article(title, articleUrl, content)); |
|||
} |
|||
} |
|||
|
|||
return articles; |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class NewsStrategy implements CrawlStrategy { |
|||
@Override |
|||
public boolean supports(String url) { |
|||
return url.contains("news.example.com"); |
|||
} |
|||
|
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
List<Article> articles = new ArrayList<>(); |
|||
Elements items = doc.select(".article-headline"); |
|||
for (Element e : items) { |
|||
articles.add(new Article(e.text(), url, "")); |
|||
} |
|||
return articles; |
|||
} |
|||
} |
|||
@ -1,107 +0,0 @@ |
|||
package com.example.datacollect.strategy; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public class StrategyFactory { |
|||
private final List<CrawlStrategy> strategies = new ArrayList<>(); |
|||
private CrawlStrategy defaultStrategy; |
|||
|
|||
public StrategyFactory() { |
|||
strategies.add(new HnuNewsStrategy()); |
|||
strategies.add(new BlogStrategy()); |
|||
strategies.add(new NewsStrategy()); |
|||
strategies.add(new GenericNewsStrategy()); |
|||
defaultStrategy = new DefaultStrategy(); |
|||
} |
|||
|
|||
public CrawlStrategy getStrategy(String url) { |
|||
CrawlStrategy matched = null; |
|||
int highestPriority = Integer.MIN_VALUE; |
|||
|
|||
for (CrawlStrategy s : strategies) { |
|||
boolean supports = false; |
|||
|
|||
Pattern pattern = s.getPattern(); |
|||
if (pattern != null) { |
|||
supports = pattern.matcher(url).find(); |
|||
} else { |
|||
supports = s.supports(url); |
|||
} |
|||
|
|||
if (supports) { |
|||
int priority = s.getPriority(); |
|||
if (priority > highestPriority) { |
|||
highestPriority = priority; |
|||
matched = s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (matched != null) { |
|||
return matched; |
|||
} |
|||
|
|||
return defaultStrategy; |
|||
} |
|||
|
|||
public void register(CrawlStrategy strategy) { |
|||
strategies.add(strategy); |
|||
} |
|||
|
|||
public void register(CrawlStrategy strategy, int priority) { |
|||
strategies.add(new PrioritizedStrategy(strategy, priority)); |
|||
} |
|||
|
|||
public void setDefaultStrategy(CrawlStrategy defaultStrategy) { |
|||
this.defaultStrategy = defaultStrategy; |
|||
} |
|||
|
|||
private static class PrioritizedStrategy implements CrawlStrategy { |
|||
private final CrawlStrategy delegate; |
|||
private final int priority; |
|||
|
|||
public PrioritizedStrategy(CrawlStrategy delegate, int priority) { |
|||
this.delegate = delegate; |
|||
this.priority = priority; |
|||
} |
|||
|
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
return delegate.parse(url, doc); |
|||
} |
|||
|
|||
@Override |
|||
public boolean supports(String url) { |
|||
return delegate.supports(url); |
|||
} |
|||
|
|||
@Override |
|||
public int getPriority() { |
|||
return priority; |
|||
} |
|||
|
|||
@Override |
|||
public Pattern getPattern() { |
|||
return delegate.getPattern(); |
|||
} |
|||
} |
|||
|
|||
private static class DefaultStrategy implements CrawlStrategy { |
|||
@Override |
|||
public List<Article> parse(String url, Document doc) { |
|||
return List.of(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean supports(String url) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public int getPriority() { |
|||
return Integer.MIN_VALUE; |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
package com.example.datacollect.view; |
|||
|
|||
import com.example.datacollect.model.Article; |
|||
import java.util.List; |
|||
import java.util.Scanner; |
|||
|
|||
public class ConsoleView { |
|||
private static final String ANSI_RESET = "\u001B[0m"; |
|||
private static final String ANSI_GREEN = "\u001B[32m"; |
|||
private static final String ANSI_RED = "\u001B[31m"; |
|||
private static final String ANSI_BLUE = "\u001B[34m"; |
|||
|
|||
private final Scanner scanner = new Scanner(System.in); |
|||
|
|||
public String readLine() { |
|||
System.out.print("> "); |
|||
return scanner.nextLine(); |
|||
} |
|||
|
|||
public void printSuccess(String msg) { |
|||
System.out.println(ANSI_GREEN + msg + ANSI_RESET); |
|||
} |
|||
|
|||
public void printError(String msg) { |
|||
System.out.println(ANSI_RED + msg + ANSI_RESET); |
|||
} |
|||
|
|||
public void printInfo(String msg) { |
|||
System.out.println(ANSI_BLUE + msg + ANSI_RESET); |
|||
} |
|||
|
|||
public void display(List<Article> articles) { |
|||
if (articles.isEmpty()) { |
|||
printInfo("暂无文章,请先执行 crawl。"); |
|||
return; |
|||
} |
|||
for (int i = 0; i < articles.size(); i++) { |
|||
Article a = articles.get(i); |
|||
System.out.println((i + 1) + ". " + a.getTitle() + " | " + a.getUrl()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue