diff --git a/project/.vscode/launch.json b/project/.vscode/launch.json
new file mode 100644
index 0000000..1fda4c7
--- /dev/null
+++ b/project/.vscode/launch.json
@@ -0,0 +1,29 @@
+{
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+
+ {
+ "type": "java",
+ "name": "Current File",
+ "request": "launch",
+ "mainClass": "${file}"
+ },
+ {
+ "type": "java",
+ "name": "WeiboStarHotSearcha",
+ "request": "launch",
+ "mainClass": "WeiboStarHotSearcha",
+ "projectName": "weibo-hotsearch"
+ },
+ {
+ "type": "java",
+ "name": "Main",
+ "request": "launch",
+ "mainClass": "com.weibo.hotsearch.Main",
+ "projectName": "weibo-hotsearch"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/project/.vscode/settings.json b/project/.vscode/settings.json
new file mode 100644
index 0000000..c5f3f6b
--- /dev/null
+++ b/project/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/project/202506050225-毕磊-高级程序设计实验报告.docx b/project/202506050225-毕磊-高级程序设计实验报告.docx
new file mode 100644
index 0000000..7ff03ff
Binary files /dev/null and b/project/202506050225-毕磊-高级程序设计实验报告.docx differ
diff --git a/project/hotsearch_results/hotsearch_20260528_140606.txt b/project/hotsearch_results/hotsearch_20260528_140606.txt
new file mode 100644
index 0000000..b6152b9
--- /dev/null
+++ b/project/hotsearch_results/hotsearch_20260528_140606.txt
@@ -0,0 +1,56 @@
+热搜数据采集报告
+================
+采集时间: 2026年05月28日 14:06:04
+
+【微博热搜数据】
+
+--- 明星相关热搜 ---
+排名:6 热度:414478 热搜:VOGUE直播间两位明星不出镜
+排名:32 热度:373370 热搜:歌手第二期歌单
+排名:34 热度:370959 热搜:小红书官宣获得世界杯版权
+
+明星相关热搜总数:3 条
+
+--- 体育相关热搜 ---
+排名:34 热度:370959 热搜:小红书官宣获得世界杯版权
+
+体育相关热搜总数:1 条
+
+--- 国家政策相关热搜 ---
+排名:37 热度:364897 热搜:双汇发布致歉声明
+
+国家政策相关热搜总数:1 条
+
+【百度贴吧热搜数据】
+
+--- 明星相关热搜 ---
+
+明星相关热搜总数:0 条
+当前贴吧热搜暂无明星相关内容
+
+--- 体育相关热搜 ---
+
+体育相关热搜总数:0 条
+当前贴吧热搜暂无体育相关内容
+
+--- 国家政策相关热搜 ---
+
+国家政策相关热搜总数:0 条
+当前贴吧热搜暂无国家政策相关内容
+
+【知乎热搜数据】
+
+--- 明星相关热搜 ---
+排名:6 热度:0 热搜:动作演员吴樾为什么火不起来
+
+明星相关热搜总数:1 条
+
+--- 体育相关热搜 ---
+
+体育相关热搜总数:0 条
+当前知乎热搜暂无体育相关内容
+
+--- 国家政策相关热搜 ---
+
+国家政策相关热搜总数:0 条
+当前知乎热搜暂无国家政策相关内容
diff --git a/project/hotsearch_results/hotsearch_20260531_121726.txt b/project/hotsearch_results/hotsearch_20260531_121726.txt
new file mode 100644
index 0000000..1405ade
--- /dev/null
+++ b/project/hotsearch_results/hotsearch_20260531_121726.txt
@@ -0,0 +1,60 @@
+热搜数据采集报告
+================
+采集时间: 2026年05月31日 12:17:24
+
+【微博热搜数据】
+
+--- 明星相关热搜 ---
+排名:43 热度:186709 热搜:敖瑞鹏新剧演鞠婧祎哥哥
+
+明星相关热搜总数:1 条
+
+--- 体育相关热搜 ---
+排名:10 热度:387068 热搜:陈星旭王玉雯一起去看欧冠了
+排名:21 热度:261492 热搜:姆巴佩欧冠金靴
+排名:23 热度:250168 热搜:文班亚马西决MVP
+排名:37 热度:191114 热搜:文班亚马回应成为西部决赛MVP
+排名:41 热度:188900 热搜:巴黎欧冠夺冠后多地爆发骚乱
+排名:42 热度:188188 热搜:孙千去看欧冠了
+排名:49 热度:172821 热搜:世界杯
+
+体育相关热搜总数:7 条
+
+--- 国家政策相关热搜 ---
+
+国家政策相关热搜总数:0 条
+当前微博热搜暂无国家政策相关内容
+
+【百度贴吧热搜数据】
+
+--- 明星相关热搜 ---
+
+明星相关热搜总数:0 条
+当前贴吧热搜暂无明星相关内容
+
+--- 体育相关热搜 ---
+
+体育相关热搜总数:0 条
+当前贴吧热搜暂无体育相关内容
+
+--- 国家政策相关热搜 ---
+
+国家政策相关热搜总数:0 条
+当前贴吧热搜暂无国家政策相关内容
+
+【知乎热搜数据】
+
+--- 明星相关热搜 ---
+
+明星相关热搜总数:0 条
+当前知乎热搜暂无明星相关内容
+
+--- 体育相关热搜 ---
+
+体育相关热搜总数:0 条
+当前知乎热搜暂无体育相关内容
+
+--- 国家政策相关热搜 ---
+
+国家政策相关热搜总数:0 条
+当前知乎热搜暂无国家政策相关内容
diff --git a/project/总代码/.vscode/settings.json b/project/总代码/.vscode/settings.json
new file mode 100644
index 0000000..8e9c83c
--- /dev/null
+++ b/project/总代码/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "git.ignoreLimitWarning": true,
+ "java.debug.settings.onBuildFailureProceed": true
+}
\ No newline at end of file
diff --git a/project/总代码/pom.xml b/project/总代码/pom.xml
new file mode 100644
index 0000000..264f219
--- /dev/null
+++ b/project/总代码/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+ com.weibo
+ hotsearch
+ 1.0.0
+ jar
+
+ HotSearch CLI Tool
+ 热搜数据采集工具 - CLI + MVC + Command + Strategy + Exception体系
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.jsoup
+ jsoup
+ 1.17.2
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.3.1
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5-fluent
+ 5.3.1
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.52
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+
+ com.weibo.hotsearch.Main
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/project/总代码/src/main/java/WeiboStarHotSearcha.java b/project/总代码/src/main/java/WeiboStarHotSearcha.java
new file mode 100644
index 0000000..a57d190
--- /dev/null
+++ b/project/总代码/src/main/java/WeiboStarHotSearcha.java
@@ -0,0 +1,667 @@
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import org.apache.hc.client5.http.fluent.Request;
+import org.apache.hc.core5.util.Timeout;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class WeiboStarHotSearcha {
+
+ private static final String WEIBO_HOT_URL = "https://weibo.com/ajax/side/hotSearch";
+ private static final String TIEBA_HOT_URL = "https://tieba.baidu.com/hottopic/browse/topicList";
+ private static final String ZHIHU_HOT_URL = "https://www.zhihu.com/api/v4/search/top_search";
+ private static final String OUTPUT_DIR = "hotsearch_results";
+
+ private static final String[] STAR_KEYWORDS = {
+ "明星", "演员", "歌手", "爱豆", "艺人", "红毯", "综艺", "新剧",
+ "恋情", "官宣", "演唱会", "代言", "造型", "封面"
+ };
+
+ private static final String[] SPORTS_KEYWORDS = {
+ "足球", "篮球", "世界杯", "NBA", "CBA", "奥运会", "世锦赛",
+ "冠军", "比赛", "夺冠", "进球", "比分", "运动员", "国足",
+ "乒乓", "排球", "羽毛球", "游泳", "田径", "体操", "跳水",
+ "MVP", "转会", "联赛", "中超", "英超", "西甲", "欧冠"
+ };
+
+ private static final String[] POLICY_KEYWORDS = {
+ "政策", "新规", "条例", "法规", "通知", "公告", "发布",
+ "国务院", "发改委", "财政部", "教育部", "工信部", "科技部",
+ "税收", "补贴", "优惠", "扶持", "改革", "开放", "创新",
+ "十四五", "计划", "规划", "方案", "意见", "办法", "细则",
+ "经济", "金融", "市场", "监管", "安全", "环保", "绿色"
+ };
+
+ private static final int CONNECT_TIMEOUT = 10000;
+ private static final int RESPONSE_TIMEOUT = 10000;
+ private static final int MAX_RETRIES = 3;
+
+ private static final StringBuilder outputBuilder = new StringBuilder();
+
+ public static void main(String[] args) {
+ try {
+ outputBuilder.append("热搜数据采集报告\n");
+ outputBuilder.append("================\n");
+ outputBuilder.append("采集时间: ").append(getCurrentTime()).append("\n\n");
+
+ // ========== 微博热搜 ==========
+ System.out.println("正在请求微博热搜数据...");
+ outputBuilder.append("【微博热搜数据】\n");
+
+ String weiboJson = fetchWithRetry(WEIBO_HOT_URL, MAX_RETRIES, "https://weibo.com/");
+
+ if (weiboJson == null || weiboJson.isEmpty()) {
+ System.out.println("获取微博热搜数据失败");
+ outputBuilder.append("获取微博热搜数据失败\n\n");
+ } else {
+ parseAndFilterWeibo(weiboJson);
+ }
+
+ // ========== 百度贴吧热搜 ==========
+ System.out.println("\n\n正在请求百度贴吧热搜数据...");
+ outputBuilder.append("\n【百度贴吧热搜数据】\n");
+
+ String tiebaJson = fetchWithRetry(TIEBA_HOT_URL, MAX_RETRIES, "https://tieba.baidu.com/");
+
+ if (tiebaJson == null || tiebaJson.isEmpty()) {
+ System.out.println("获取百度贴吧热搜数据失败");
+ outputBuilder.append("获取百度贴吧热搜数据失败\n");
+ } else {
+ parseAndFilterTieba(tiebaJson);
+ }
+
+ // ========== 知乎热搜 ==========
+ System.out.println("\n\n正在请求知乎热搜数据...");
+ outputBuilder.append("\n【知乎热搜数据】\n");
+
+ try {
+ String zhihuJson = fetchWithRetry(ZHIHU_HOT_URL, MAX_RETRIES, "https://zhuanlan.zhihu.com/");
+
+ if (zhihuJson == null || zhihuJson.isEmpty()) {
+ System.out.println("获取知乎热搜数据失败");
+ outputBuilder.append("获取知乎热搜数据失败\n");
+ } else {
+ System.out.println("知乎返回数据长度: " + zhihuJson.length() + " 字符");
+ if (zhihuJson.length() > 0) {
+ System.out.println("知乎返回数据预览: " + zhihuJson.substring(0, Math.min(500, zhihuJson.length())) + "...");
+ }
+ parseAndFilterZhihu(zhihuJson);
+ }
+ } catch (Exception e) {
+ System.out.println("获取知乎热搜数据异常: " + e.getMessage());
+ outputBuilder.append("获取知乎热搜数据异常: " + e.getMessage() + "\n");
+ e.printStackTrace();
+ }
+
+ // ========== 保存到文件 ==========
+ String filename = saveToFile();
+ System.out.println("\n\n结果已保存到文件: " + filename);
+
+ } catch (IOException e) {
+ System.err.println("网络请求失败: " + e.getMessage());
+ e.printStackTrace();
+ } catch (Exception e) {
+ System.err.println("数据解析失败: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private static String fetchWithRetry(String url, int maxRetries, String referer) throws IOException {
+ int retryCount = 0;
+ IOException lastException = null;
+
+ while (retryCount < maxRetries) {
+ try {
+ System.out.println("正在请求: " + url);
+ String result = Request.get(url)
+ .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
+ .addHeader("Referer", referer)
+ .addHeader("Accept", "application/json, text/plain, */*;charset=UTF-8")
+ .addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
+ .addHeader("Accept-Encoding", "identity")
+ .addHeader("Connection", "keep-alive")
+ .addHeader("Content-Type", "application/json;charset=UTF-8")
+ .connectTimeout(Timeout.ofMilliseconds(CONNECT_TIMEOUT))
+ .responseTimeout(Timeout.ofMilliseconds(RESPONSE_TIMEOUT))
+ .execute()
+ .returnContent()
+ .asString(StandardCharsets.UTF_8);
+
+ // 修复可能的编码问题
+ result = new String(result.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
+ return result;
+ } catch (IOException e) {
+ lastException = e;
+ retryCount++;
+ System.out.println("请求失败 (" + retryCount + "/" + maxRetries + "): " + e.getMessage());
+ if (retryCount < maxRetries) {
+ System.out.println("2秒后重试...");
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new IOException("重试被中断", ie);
+ }
+ }
+ }
+ }
+ throw lastException != null ? lastException : new IOException("请求失败");
+ }
+
+ private static void parseAndFilterWeibo(String json) {
+ JSONObject root = JSONObject.parseObject(json);
+ if (root == null || !root.containsKey("data")) {
+ System.out.println("微博数据格式错误或接口返回异常");
+ outputBuilder.append("数据格式错误或接口返回异常\n");
+ return;
+ }
+
+ JSONObject data = root.getJSONObject("data");
+ if (data == null || !data.containsKey("realtime")) {
+ System.out.println("微博热搜数据为空");
+ outputBuilder.append("热搜数据为空\n");
+ return;
+ }
+
+ JSONArray realtime = data.getJSONArray("realtime");
+ if (realtime == null || realtime.isEmpty()) {
+ System.out.println("微博热搜列表为空");
+ outputBuilder.append("热搜列表为空\n");
+ return;
+ }
+
+ List starHotList = new ArrayList<>();
+ List sportsHotList = new ArrayList<>();
+ List policyHotList = new ArrayList<>();
+
+ System.out.println("\n===== 微博 - 明星相关热搜 =====");
+ outputBuilder.append("\n--- 明星相关热搜 ---\n");
+
+ for (int i = 0; i < realtime.size(); i++) {
+ JSONObject item = realtime.getJSONObject(i);
+ if (item == null) continue;
+
+ String word = item.getString("word");
+ if (word == null || word.isEmpty()) continue;
+
+ long num = item.getLongValue("num", 0);
+ int rank = item.getIntValue("rank", 0);
+
+ if (isStarRelated(word)) {
+ starHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, num, word);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ String summary = "\n明星相关热搜总数:" + starHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (starHotList.isEmpty()) {
+ String emptyMsg = "当前微博热搜暂无明星相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 微博 - 体育相关热搜 =====");
+ outputBuilder.append("\n--- 体育相关热搜 ---\n");
+
+ for (int i = 0; i < realtime.size(); i++) {
+ JSONObject item = realtime.getJSONObject(i);
+ if (item == null) continue;
+
+ String word = item.getString("word");
+ if (word == null || word.isEmpty()) continue;
+
+ long num = item.getLongValue("num", 0);
+ int rank = item.getIntValue("rank", 0);
+
+ if (isSportsRelated(word)) {
+ sportsHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, num, word);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n体育相关热搜总数:" + sportsHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (sportsHotList.isEmpty()) {
+ String emptyMsg = "当前微博热搜暂无体育相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 微博 - 国家政策相关热搜 =====");
+ outputBuilder.append("\n--- 国家政策相关热搜 ---\n");
+
+ for (int i = 0; i < realtime.size(); i++) {
+ JSONObject item = realtime.getJSONObject(i);
+ if (item == null) continue;
+
+ String word = item.getString("word");
+ if (word == null || word.isEmpty()) continue;
+
+ long num = item.getLongValue("num", 0);
+ int rank = item.getIntValue("rank", 0);
+
+ if (isPolicyRelated(word)) {
+ policyHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, num, word);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n国家政策相关热搜总数:" + policyHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (policyHotList.isEmpty()) {
+ String emptyMsg = "当前微博热搜暂无国家政策相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+ }
+
+ private static void parseAndFilterTieba(String json) {
+ JSONObject root = JSONObject.parseObject(json);
+ if (root == null || !root.containsKey("data")) {
+ System.out.println("贴吧数据格式错误或接口返回异常");
+ outputBuilder.append("数据格式错误或接口返回异常\n");
+ return;
+ }
+
+ JSONObject data = root.getJSONObject("data");
+ if (data == null || !data.containsKey("bang_topic")) {
+ System.out.println("贴吧热搜数据为空");
+ outputBuilder.append("热搜数据为空\n");
+ return;
+ }
+
+ JSONArray topics = data.getJSONArray("bang_topic");
+ if (topics == null || topics.isEmpty()) {
+ System.out.println("贴吧热搜列表为空");
+ outputBuilder.append("热搜列表为空\n");
+ return;
+ }
+
+ List starHotList = new ArrayList<>();
+ List sportsHotList = new ArrayList<>();
+ List policyHotList = new ArrayList<>();
+
+ System.out.println("\n===== 百度贴吧 - 明星相关热搜 =====");
+ outputBuilder.append("\n--- 明星相关热搜 ---\n");
+
+ for (int i = 0; i < topics.size(); i++) {
+ JSONObject item = topics.getJSONObject(i);
+ if (item == null) continue;
+
+ String topicName = item.getString("topic_name");
+ if (topicName == null || topicName.isEmpty()) continue;
+
+ int discussNum = item.getIntValue("discuss_num", 0);
+ int readNum = item.getIntValue("read_num", 0);
+
+ if (isStarRelated(topicName)) {
+ starHotList.add(item);
+ String line = String.format("序号:%d\t阅读:%d\t讨论:%d\t话题:%s", i + 1, readNum, discussNum, topicName);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ String summary = "\n明星相关热搜总数:" + starHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (starHotList.isEmpty()) {
+ String emptyMsg = "当前贴吧热搜暂无明星相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 百度贴吧 - 体育相关热搜 =====");
+ outputBuilder.append("\n--- 体育相关热搜 ---\n");
+
+ for (int i = 0; i < topics.size(); i++) {
+ JSONObject item = topics.getJSONObject(i);
+ if (item == null) continue;
+
+ String topicName = item.getString("topic_name");
+ if (topicName == null || topicName.isEmpty()) continue;
+
+ int discussNum = item.getIntValue("discuss_num", 0);
+ int readNum = item.getIntValue("read_num", 0);
+
+ if (isSportsRelated(topicName)) {
+ sportsHotList.add(item);
+ String line = String.format("序号:%d\t阅读:%d\t讨论:%d\t话题:%s", i + 1, readNum, discussNum, topicName);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n体育相关热搜总数:" + sportsHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (sportsHotList.isEmpty()) {
+ String emptyMsg = "当前贴吧热搜暂无体育相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 百度贴吧 - 国家政策相关热搜 =====");
+ outputBuilder.append("\n--- 国家政策相关热搜 ---\n");
+
+ for (int i = 0; i < topics.size(); i++) {
+ JSONObject item = topics.getJSONObject(i);
+ if (item == null) continue;
+
+ String topicName = item.getString("topic_name");
+ if (topicName == null || topicName.isEmpty()) continue;
+
+ int discussNum = item.getIntValue("discuss_num", 0);
+ int readNum = item.getIntValue("read_num", 0);
+
+ if (isPolicyRelated(topicName)) {
+ policyHotList.add(item);
+ String line = String.format("序号:%d\t阅读:%d\t讨论:%d\t话题:%s", i + 1, readNum, discussNum, topicName);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n国家政策相关热搜总数:" + policyHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (policyHotList.isEmpty()) {
+ String emptyMsg = "当前贴吧热搜暂无国家政策相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+ }
+
+ private static void parseAndFilterZhihu(String json) {
+ try {
+ JSONObject root = JSONObject.parseObject(json);
+ if (root == null) {
+ System.out.println("知乎数据格式错误:无法解析JSON");
+ outputBuilder.append("数据格式错误:无法解析JSON\n");
+ return;
+ }
+
+ // 尝试多种数据结构
+ JSONArray data = null;
+
+ // 结构1:直接在 data 数组中
+ if (root.containsKey("data") && root.get("data") instanceof JSONArray) {
+ data = root.getJSONArray("data");
+ }
+ // 结构2:在 data.topics 数组中
+ else if (root.containsKey("data")) {
+ JSONObject dataObj = root.getJSONObject("data");
+ if (dataObj != null && dataObj.containsKey("topics")) {
+ data = dataObj.getJSONArray("topics");
+ }
+ }
+ // 结构3:在 top_search.words 数组中(知乎搜索API)
+ else if (root.containsKey("top_search")) {
+ JSONObject topSearch = root.getJSONObject("top_search");
+ if (topSearch != null && topSearch.containsKey("words")) {
+ data = topSearch.getJSONArray("words");
+ }
+ }
+ // 结构4:直接是数组
+ else if (json.startsWith("[")) {
+ data = JSONArray.parseArray(json);
+ }
+
+ if (data == null || data.isEmpty()) {
+ System.out.println("知乎热搜数据为空或格式不匹配");
+ outputBuilder.append("热搜数据为空或格式不匹配\n");
+ return;
+ }
+
+ List starHotList = new ArrayList<>();
+ List sportsHotList = new ArrayList<>();
+ List policyHotList = new ArrayList<>();
+
+ System.out.println("\n===== 知乎 - 明星相关热搜 =====");
+ outputBuilder.append("\n--- 明星相关热搜 ---\n");
+
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject item = data.getJSONObject(i);
+ if (item == null) continue;
+
+ // 尝试多种标题字段
+ String title = null;
+ if (item.containsKey("title")) {
+ title = item.getString("title");
+ } else if (item.containsKey("topic_title")) {
+ title = item.getString("topic_title");
+ } else if (item.containsKey("name")) {
+ title = item.getString("name");
+ } else if (item.containsKey("target")) {
+ JSONObject target = item.getJSONObject("target");
+ if (target != null) {
+ title = target.getString("title");
+ }
+ } else if (item.containsKey("display_query")) {
+ title = item.getString("display_query");
+ } else if (item.containsKey("query")) {
+ title = item.getString("query");
+ }
+
+ if (title == null || title.isEmpty()) continue;
+
+ // 尝试获取热度值
+ long hotValue = 0;
+ if (item.containsKey("hot_score")) {
+ hotValue = item.getLongValue("hot_score", 0);
+ } else if (item.containsKey("score")) {
+ hotValue = item.getLongValue("score", 0);
+ } else if (item.containsKey("detail_text")) {
+ String detailText = item.getString("detail_text");
+ if (detailText != null) {
+ try {
+ String numStr = detailText.replaceAll("[^0-9]", "");
+ if (!numStr.isEmpty()) {
+ hotValue = Long.parseLong(numStr);
+ }
+ } catch (Exception e) {
+ hotValue = 0;
+ }
+ }
+ }
+
+ int rank = i + 1;
+
+ if (isStarRelated(title)) {
+ starHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, hotValue, title);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ String summary = "\n明星相关热搜总数:" + starHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (starHotList.isEmpty()) {
+ String emptyMsg = "当前知乎热搜暂无明星相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 知乎 - 体育相关热搜 =====");
+ outputBuilder.append("\n--- 体育相关热搜 ---\n");
+
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject item = data.getJSONObject(i);
+ if (item == null) continue;
+
+ String title = null;
+ if (item.containsKey("title")) {
+ title = item.getString("title");
+ } else if (item.containsKey("topic_title")) {
+ title = item.getString("topic_title");
+ } else if (item.containsKey("name")) {
+ title = item.getString("name");
+ } else if (item.containsKey("target")) {
+ JSONObject target = item.getJSONObject("target");
+ if (target != null) {
+ title = target.getString("title");
+ }
+ }
+
+ if (title == null || title.isEmpty()) continue;
+
+ long hotValue = 0;
+ if (item.containsKey("hot_score")) {
+ hotValue = item.getLongValue("hot_score", 0);
+ } else if (item.containsKey("score")) {
+ hotValue = item.getLongValue("score", 0);
+ }
+
+ int rank = i + 1;
+
+ if (isSportsRelated(title)) {
+ sportsHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, hotValue, title);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n体育相关热搜总数:" + sportsHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (sportsHotList.isEmpty()) {
+ String emptyMsg = "当前知乎热搜暂无体育相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+
+ System.out.println("\n===== 知乎 - 国家政策相关热搜 =====");
+ outputBuilder.append("\n--- 国家政策相关热搜 ---\n");
+
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject item = data.getJSONObject(i);
+ if (item == null) continue;
+
+ String title = null;
+ if (item.containsKey("title")) {
+ title = item.getString("title");
+ } else if (item.containsKey("topic_title")) {
+ title = item.getString("topic_title");
+ } else if (item.containsKey("name")) {
+ title = item.getString("name");
+ } else if (item.containsKey("target")) {
+ JSONObject target = item.getJSONObject("target");
+ if (target != null) {
+ title = target.getString("title");
+ }
+ }
+
+ if (title == null || title.isEmpty()) continue;
+
+ long hotValue = 0;
+ if (item.containsKey("hot_score")) {
+ hotValue = item.getLongValue("hot_score", 0);
+ } else if (item.containsKey("score")) {
+ hotValue = item.getLongValue("score", 0);
+ }
+
+ int rank = i + 1;
+
+ if (isPolicyRelated(title)) {
+ policyHotList.add(item);
+ String line = String.format("排名:%d\t热度:%d\t热搜:%s", rank, hotValue, title);
+ System.out.println(line);
+ outputBuilder.append(line).append("\n");
+ }
+ }
+ summary = "\n国家政策相关热搜总数:" + policyHotList.size() + " 条";
+ System.out.println(summary);
+ outputBuilder.append(summary).append("\n");
+
+ if (policyHotList.isEmpty()) {
+ String emptyMsg = "当前知乎热搜暂无国家政策相关内容";
+ System.out.println(emptyMsg);
+ outputBuilder.append(emptyMsg).append("\n");
+ }
+ } catch (Exception e) {
+ System.out.println("知乎数据解析异常: " + e.getMessage());
+ outputBuilder.append("数据解析异常: " + e.getMessage() + "\n");
+ e.printStackTrace();
+ }
+ }
+
+ private static boolean isStarRelated(String word) {
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : STAR_KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isSportsRelated(String word) {
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : SPORTS_KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isPolicyRelated(String word) {
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : POLICY_KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String getCurrentTime() {
+ return new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date());
+ }
+
+ private static String saveToFile() throws IOException {
+ File dir = new File(OUTPUT_DIR);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+ String filename = "hotsearch_" + timestamp + ".txt";
+ String filepath = OUTPUT_DIR + File.separator + filename;
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
+ writer.write(outputBuilder.toString());
+ }
+
+ return filepath;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/ConsoleOutputHandler.java b/project/总代码/src/main/java/com/weibo/hotsearch/ConsoleOutputHandler.java
new file mode 100644
index 0000000..a4787cb
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/ConsoleOutputHandler.java
@@ -0,0 +1,30 @@
+package com.weibo.hotsearch;
+
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.List;
+
+public class ConsoleOutputHandler extends OutputHandler {
+
+ @Override
+ public void output(List hotList, String filterName) {
+ System.out.println("\n===== " + filterName + " =====");
+
+ if (hotList == null || hotList.isEmpty()) {
+ System.out.println("当前暂无符合条件的热搜内容");
+ return;
+ }
+
+ for (int i = 0; i < hotList.size(); i++) {
+ JSONObject item = hotList.get(i);
+ System.out.println(formatHotItem(item, i, null));
+ }
+
+ System.out.println("\n===== 热搜总数:" + hotList.size() + " 条 =====");
+ }
+
+ @Override
+ public String getOutputType() {
+ return "控制台输出";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/HotSearchFilter.java b/project/总代码/src/main/java/com/weibo/hotsearch/HotSearchFilter.java
new file mode 100644
index 0000000..0b41951
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/HotSearchFilter.java
@@ -0,0 +1,24 @@
+package com.weibo.hotsearch;
+
+public abstract class HotSearchFilter {
+
+ protected String[] keywords;
+
+ public HotSearchFilter(String[] keywords) {
+ this.keywords = keywords;
+ }
+
+ public boolean matches(String word) {
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : keywords) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public abstract String getFilterName();
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/Main.java b/project/总代码/src/main/java/com/weibo/hotsearch/Main.java
new file mode 100644
index 0000000..5073d65
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/Main.java
@@ -0,0 +1,16 @@
+package com.weibo.hotsearch;
+
+import com.weibo.hotsearch.cli.CliHandler;
+
+public class Main {
+
+ public static void main(String[] args) {
+ try {
+ CliHandler handler = new CliHandler(args);
+ handler.handle();
+ } catch (Exception e) {
+ System.err.println("程序执行异常: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/OutputHandler.java b/project/总代码/src/main/java/com/weibo/hotsearch/OutputHandler.java
new file mode 100644
index 0000000..f54e352
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/OutputHandler.java
@@ -0,0 +1,46 @@
+package com.weibo.hotsearch;
+
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.List;
+
+public abstract class OutputHandler {
+
+ public abstract void output(List hotList, String filterName);
+
+ public abstract String getOutputType();
+
+ protected String formatHotItem(JSONObject item, int index, String dataSourceName) {
+ String word = getHotSearchWord(item);
+ long num = getHotSearchNum(item);
+ int rank = getHotSearchRank(item);
+
+ if (rank > 0) {
+ return String.format("排名:%d\t热度:%d\t热搜:%s", rank, num, word);
+ } else {
+ return String.format("序号:%d\t热度:%d\t热搜:%s", index + 1, num, word);
+ }
+ }
+
+ protected String getHotSearchWord(JSONObject item) {
+ if (item == null) return "未知";
+ String word = item.getString("word");
+ if (word == null || word.isEmpty()) {
+ word = item.getString("topic_name");
+ }
+ if (word == null || word.isEmpty()) {
+ word = item.getString("title");
+ }
+ return word != null ? word : "未知";
+ }
+
+ protected long getHotSearchNum(JSONObject item) {
+ if (item == null) return 0;
+ return item.getLongValue("num", 0);
+ }
+
+ protected int getHotSearchRank(JSONObject item) {
+ if (item == null) return 0;
+ return item.getIntValue("rank", 0);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/StarFilter.java b/project/总代码/src/main/java/com/weibo/hotsearch/StarFilter.java
new file mode 100644
index 0000000..ff1ddf4
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/StarFilter.java
@@ -0,0 +1,18 @@
+package com.weibo.hotsearch;
+
+public class StarFilter extends HotSearchFilter {
+
+ private static final String[] STAR_KEYWORDS = {
+ "明星", "演员", "歌手", "爱豆", "艺人", "红毯", "综艺", "新剧",
+ "恋情", "官宣", "演唱会", "代言", "造型", "封面"
+ };
+
+ public StarFilter() {
+ super(STAR_KEYWORDS);
+ }
+
+ @Override
+ public String getFilterName() {
+ return "明星相关热搜";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliHandler.java b/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliHandler.java
new file mode 100644
index 0000000..ea52466
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliHandler.java
@@ -0,0 +1,133 @@
+package com.weibo.hotsearch.cli;
+
+import com.weibo.hotsearch.controller.HotSearchController;
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+
+public class CliHandler {
+
+ private final CliParser parser;
+ private final HotSearchController controller;
+
+ public CliHandler(String[] args) {
+ this.parser = new CliParser();
+ this.controller = new HotSearchController();
+ this.parser.parse(args);
+ }
+
+ public void handle() {
+ String command = parser.getCommand();
+
+ if (command == null || command.isEmpty()) {
+ parser.printUsage();
+ return;
+ }
+
+ try {
+ switch (command.toLowerCase()) {
+ case "help":
+ parser.printUsage();
+ break;
+ case "fetch":
+ handleFetch();
+ break;
+ case "filter":
+ handleFilter();
+ break;
+ case "output":
+ handleOutput();
+ break;
+ case "save":
+ handleSave();
+ break;
+ case "run":
+ handleRun();
+ break;
+ default:
+ throw new HotSearchException(ErrorCode.CLI_COMMAND_NOT_FOUND, "未知命令: " + command);
+ }
+ } catch (HotSearchException e) {
+ System.err.println("\n错误 [" + e.getErrorCode().getCode() + "]: " + e.getMessage());
+ if (e.getCause() != null) {
+ e.getCause().printStackTrace();
+ }
+ }
+ }
+
+ private void handleFetch() throws HotSearchException {
+ String source = parser.getOption("s");
+ if (source == null) {
+ source = parser.getOption("source");
+ }
+ if (source == null) {
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "请指定数据源 (-s 或 --source)");
+ }
+ controller.executeFetch(source);
+ }
+
+ private void handleFilter() throws HotSearchException {
+ String filter = parser.getOption("f");
+ if (filter == null) {
+ filter = parser.getOption("filter");
+ }
+ if (filter == null) {
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "请指定过滤器 (-f 或 --filter)");
+ }
+ controller.executeFilter(filter);
+ }
+
+ private void handleOutput() throws HotSearchException {
+ String output = parser.getOption("o");
+ if (output == null) {
+ output = parser.getOption("output");
+ }
+ if (output == null) {
+ output = "console";
+ }
+ controller.executeOutput(output);
+ }
+
+ private void handleSave() throws HotSearchException {
+ String path = parser.getOption("p");
+ if (path == null) {
+ path = parser.getOption("path");
+ }
+ controller.executeSave(path);
+ }
+
+ private void handleRun() throws HotSearchException {
+ String source = parser.getOption("s");
+ if (source == null) {
+ source = parser.getOption("source");
+ }
+ if (source == null) {
+ source = "all";
+ }
+
+ String filter = parser.getOption("f");
+ if (filter == null) {
+ filter = parser.getOption("filter");
+ }
+ if (filter == null) {
+ filter = "star";
+ }
+
+ String output = parser.getOption("o");
+ if (output == null) {
+ output = parser.getOption("output");
+ }
+ if (output == null) {
+ output = "console";
+ }
+
+ String path = parser.getOption("p");
+ if (path == null) {
+ path = parser.getOption("path");
+ }
+
+ controller.executeFetch(source);
+ controller.executeFilter(filter);
+ controller.executeOutput(output);
+ controller.executeSave(path);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliParser.java b/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliParser.java
new file mode 100644
index 0000000..33843cc
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/cli/CliParser.java
@@ -0,0 +1,101 @@
+package com.weibo.hotsearch.cli;
+
+import com.weibo.hotsearch.exception.CliException;
+import com.weibo.hotsearch.exception.ErrorCode;
+
+import java.util.*;
+
+public class CliParser {
+
+ private final Map options = new HashMap<>();
+ private final List arguments = new ArrayList<>();
+ private String command;
+
+ public void parse(String[] args) {
+ if (args == null || args.length == 0) {
+ printUsage();
+ return;
+ }
+
+ int i = 0;
+ while (i < args.length) {
+ String arg = args[i];
+
+ if (arg.startsWith("--")) {
+ String[] parts = arg.substring(2).split("=", 2);
+ String key = parts[0];
+ String value = parts.length > 1 ? parts[1] : "true";
+ options.put(key, value);
+ i++;
+ } else if (arg.startsWith("-")) {
+ String key = arg.substring(1);
+ if (key.length() == 1) {
+ String value = "true";
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
+ value = args[i + 1];
+ i++;
+ }
+ options.put(key, value);
+ } else {
+ for (char c : key.toCharArray()) {
+ options.put(String.valueOf(c), "true");
+ }
+ }
+ i++;
+ } else {
+ if (command == null) {
+ command = arg;
+ } else {
+ arguments.add(arg);
+ }
+ i++;
+ }
+ }
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public String getOption(String key) {
+ return options.get(key);
+ }
+
+ public boolean hasOption(String key) {
+ return options.containsKey(key);
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+ public Map getAllOptions() {
+ return new HashMap<>(options);
+ }
+
+ public void printUsage() {
+ System.out.println("\n===== 热搜数据采集工具 =====");
+ System.out.println("用法:");
+ System.out.println(" java -jar weibo-hotsearch-1.0-SNAPSHOT-jar-with-dependencies.jar [命令] [选项]");
+ System.out.println("\n命令:");
+ System.out.println(" fetch 获取热搜数据");
+ System.out.println(" filter 过滤热搜数据");
+ System.out.println(" output 输出热搜数据");
+ System.out.println(" save 保存数据到文件");
+ System.out.println(" run 执行完整流程");
+ System.out.println(" help 显示帮助信息");
+ System.out.println("\n选项:");
+ System.out.println(" -s, --source <数据源> 指定数据源: weibo/tieba/zhihu/all");
+ System.out.println(" -f, --filter <过滤器> 指定过滤器: star/sports/policy");
+ System.out.println(" -o, --output <类型> 指定输出类型: console/text");
+ System.out.println(" -p, --path <路径> 指定保存路径");
+ System.out.println(" -h, --help 显示帮助信息");
+ System.out.println("\n示例:");
+ System.out.println(" java -jar xxx.jar run -s weibo -f star -o console");
+ System.out.println(" java -jar xxx.jar fetch -s all");
+ System.out.println(" java -jar xxx.jar filter -f sports");
+ System.out.println(" java -jar xxx.jar output -o console");
+ System.out.println(" java -jar xxx.jar save -p ./result.txt");
+ System.out.println();
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/Command.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/Command.java
new file mode 100644
index 0000000..f84d238
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/Command.java
@@ -0,0 +1,12 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.HotSearchException;
+
+public interface Command {
+
+ void execute() throws HotSearchException;
+
+ String getCommandName();
+
+ String getDescription();
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandInvoker.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandInvoker.java
new file mode 100644
index 0000000..8e8aa27
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandInvoker.java
@@ -0,0 +1,41 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommandInvoker {
+
+ private final Map commands = new HashMap<>();
+
+ public void registerCommand(String name, Command command) {
+ commands.put(name.toLowerCase(), command);
+ }
+
+ public void executeCommand(String name) throws HotSearchException {
+ Command command = commands.get(name.toLowerCase());
+ if (command == null) {
+ throw new HotSearchException(ErrorCode.CLI_COMMAND_NOT_FOUND, "命令未找到: " + name);
+ }
+ command.execute();
+ }
+
+ public boolean hasCommand(String name) {
+ return commands.containsKey(name.toLowerCase());
+ }
+
+ public void printHelp() {
+ System.out.println("\n===== 命令帮助 =====");
+ System.out.println("可用命令:");
+ for (Map.Entry entry : commands.entrySet()) {
+ System.out.printf(" %-10s - %s%n", entry.getKey(), entry.getValue().getDescription());
+ }
+ System.out.println();
+ }
+
+ public Map getCommands() {
+ return new HashMap<>(commands);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandResult.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandResult.java
new file mode 100644
index 0000000..0a75e06
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/CommandResult.java
@@ -0,0 +1,47 @@
+package com.weibo.hotsearch.command;
+
+public class CommandResult {
+
+ private boolean success;
+ private String message;
+ private Object data;
+
+ public CommandResult(boolean success, String message) {
+ this.success = success;
+ this.message = message;
+ }
+
+ public CommandResult(boolean success, String message, Object data) {
+ this.success = success;
+ this.message = message;
+ this.data = data;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Object getData() {
+ return data;
+ }
+
+ public static CommandResult success(String message) {
+ return new CommandResult(true, message);
+ }
+
+ public static CommandResult success(String message, Object data) {
+ return new CommandResult(true, message, data);
+ }
+
+ public static CommandResult failure(String message) {
+ return new CommandResult(false, message);
+ }
+
+ public static CommandResult failure(String message, Object data) {
+ return new CommandResult(false, message, data);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/FetchCommand.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/FetchCommand.java
new file mode 100644
index 0000000..38c3d00
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/FetchCommand.java
@@ -0,0 +1,34 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.service.DataFetcher;
+
+public class FetchCommand implements Command {
+
+ private final DataFetcher dataFetcher;
+ private final String source;
+
+ public FetchCommand(DataFetcher dataFetcher, String source) {
+ this.dataFetcher = dataFetcher;
+ this.source = source;
+ }
+
+ @Override
+ public void execute() throws HotSearchException {
+ if (source == null || source.isEmpty()) {
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "数据源不能为空");
+ }
+ dataFetcher.fetch(source);
+ }
+
+ @Override
+ public String getCommandName() {
+ return "fetch";
+ }
+
+ @Override
+ public String getDescription() {
+ return "从指定数据源获取热搜数据";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/FilterCommand.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/FilterCommand.java
new file mode 100644
index 0000000..95adfdb
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/FilterCommand.java
@@ -0,0 +1,35 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.service.FilterService;
+import com.weibo.hotsearch.strategy.FilterStrategy;
+
+public class FilterCommand implements Command {
+
+ private final FilterService filterService;
+ private final FilterStrategy strategy;
+
+ public FilterCommand(FilterService filterService, FilterStrategy strategy) {
+ this.filterService = filterService;
+ this.strategy = strategy;
+ }
+
+ @Override
+ public void execute() throws HotSearchException {
+ if (strategy == null) {
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "过滤策略不能为空");
+ }
+ filterService.filter(strategy);
+ }
+
+ @Override
+ public String getCommandName() {
+ return "filter";
+ }
+
+ @Override
+ public String getDescription() {
+ return "使用指定策略过滤热搜数据";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/HelpCommand.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/HelpCommand.java
new file mode 100644
index 0000000..67e1c21
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/HelpCommand.java
@@ -0,0 +1,27 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.HotSearchException;
+
+public class HelpCommand implements Command {
+
+ private final CommandInvoker invoker;
+
+ public HelpCommand(CommandInvoker invoker) {
+ this.invoker = invoker;
+ }
+
+ @Override
+ public void execute() throws HotSearchException {
+ invoker.printHelp();
+ }
+
+ @Override
+ public String getCommandName() {
+ return "help";
+ }
+
+ @Override
+ public String getDescription() {
+ return "显示帮助信息";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/OutputCommand.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/OutputCommand.java
new file mode 100644
index 0000000..82aad69
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/OutputCommand.java
@@ -0,0 +1,34 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.service.OutputService;
+
+public class OutputCommand implements Command {
+
+ private final OutputService outputService;
+ private final String outputType;
+
+ public OutputCommand(OutputService outputService, String outputType) {
+ this.outputService = outputService;
+ this.outputType = outputType;
+ }
+
+ @Override
+ public void execute() throws HotSearchException {
+ if (outputType == null || outputType.isEmpty()) {
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "输出类型不能为空");
+ }
+ outputService.output(outputType);
+ }
+
+ @Override
+ public String getCommandName() {
+ return "output";
+ }
+
+ @Override
+ public String getDescription() {
+ return "输出过滤后的热搜数据";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/command/SaveCommand.java b/project/总代码/src/main/java/com/weibo/hotsearch/command/SaveCommand.java
new file mode 100644
index 0000000..81b4fa9
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/command/SaveCommand.java
@@ -0,0 +1,30 @@
+package com.weibo.hotsearch.command;
+
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.service.OutputService;
+
+public class SaveCommand implements Command {
+
+ private final OutputService outputService;
+ private final String filePath;
+
+ public SaveCommand(OutputService outputService, String filePath) {
+ this.outputService = outputService;
+ this.filePath = filePath;
+ }
+
+ @Override
+ public void execute() throws HotSearchException {
+ outputService.saveToFile(filePath);
+ }
+
+ @Override
+ public String getCommandName() {
+ return "save";
+ }
+
+ @Override
+ public String getDescription() {
+ return "保存数据到文件";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/controller/HotSearchController.java b/project/总代码/src/main/java/com/weibo/hotsearch/controller/HotSearchController.java
new file mode 100644
index 0000000..70d5428
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/controller/HotSearchController.java
@@ -0,0 +1,69 @@
+package com.weibo.hotsearch.controller;
+
+import com.weibo.hotsearch.command.*;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.service.DataFetcher;
+import com.weibo.hotsearch.service.FilterService;
+import com.weibo.hotsearch.service.OutputService;
+import com.weibo.hotsearch.strategy.FilterStrategy;
+import com.weibo.hotsearch.strategy.FilterStrategyFactory;
+
+public class HotSearchController {
+
+ private final DataFetcher dataFetcher;
+ private final FilterService filterService;
+ private final OutputService outputService;
+ private final CommandInvoker commandInvoker;
+
+ public HotSearchController() {
+ this.dataFetcher = new DataFetcher();
+ this.filterService = new FilterService();
+ this.outputService = new OutputService();
+ this.commandInvoker = new CommandInvoker();
+ registerCommands();
+ }
+
+ private void registerCommands() {
+ commandInvoker.registerCommand("help", new HelpCommand(commandInvoker));
+ }
+
+ public void executeFetch(String source) throws HotSearchException {
+ FetchCommand command = new FetchCommand(dataFetcher, source);
+ command.execute();
+ System.out.println("已从 " + source + " 获取数据");
+ }
+
+ public void executeFilter(String filterCode) throws HotSearchException {
+ FilterStrategy strategy = FilterStrategyFactory.getStrategy(filterCode);
+ if (strategy == null) {
+ throw new HotSearchException(com.weibo.hotsearch.exception.ErrorCode.PARAMETER_ERROR,
+ "未知的过滤策略: " + filterCode);
+ }
+ FilterCommand command = new FilterCommand(filterService, strategy);
+ command.execute();
+ System.out.println("已应用过滤策略: " + strategy.getFilterName());
+ }
+
+ public void executeOutput(String outputType) throws HotSearchException {
+ OutputCommand command = new OutputCommand(outputService, outputType);
+ command.execute();
+ }
+
+ public void executeSave(String filePath) throws HotSearchException {
+ SaveCommand command = new SaveCommand(outputService, filePath);
+ command.execute();
+ }
+
+ public void showHelp() throws HotSearchException {
+ commandInvoker.executeCommand("help");
+ }
+
+ public void processFullPipeline(String source, String filterCode, String outputType, String savePath) throws HotSearchException {
+ executeFetch(source);
+ executeFilter(filterCode);
+ executeOutput(outputType);
+ if (savePath != null || !savePath.isEmpty()) {
+ executeSave(savePath);
+ }
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/exception/CliException.java b/project/总代码/src/main/java/com/weibo/hotsearch/exception/CliException.java
new file mode 100644
index 0000000..6fad0f6
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/exception/CliException.java
@@ -0,0 +1,20 @@
+package com.weibo.hotsearch.exception;
+
+public class CliException extends HotSearchException {
+
+ public CliException(ErrorCode errorCode) {
+ super(errorCode);
+ }
+
+ public CliException(ErrorCode errorCode, Throwable cause) {
+ super(errorCode, cause);
+ }
+
+ public CliException(ErrorCode errorCode, String detail) {
+ super(errorCode, detail);
+ }
+
+ public CliException(ErrorCode errorCode, String detail, Throwable cause) {
+ super(errorCode, detail, cause);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/exception/DataParseException.java b/project/总代码/src/main/java/com/weibo/hotsearch/exception/DataParseException.java
new file mode 100644
index 0000000..ea9304c
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/exception/DataParseException.java
@@ -0,0 +1,20 @@
+package com.weibo.hotsearch.exception;
+
+public class DataParseException extends HotSearchException {
+
+ public DataParseException(ErrorCode errorCode) {
+ super(errorCode);
+ }
+
+ public DataParseException(ErrorCode errorCode, Throwable cause) {
+ super(errorCode, cause);
+ }
+
+ public DataParseException(ErrorCode errorCode, String detail) {
+ super(errorCode, detail);
+ }
+
+ public DataParseException(ErrorCode errorCode, String detail, Throwable cause) {
+ super(errorCode, detail, cause);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/exception/ErrorCode.java b/project/总代码/src/main/java/com/weibo/hotsearch/exception/ErrorCode.java
new file mode 100644
index 0000000..ef8cab8
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/exception/ErrorCode.java
@@ -0,0 +1,47 @@
+package com.weibo.hotsearch.exception;
+
+public enum ErrorCode {
+
+ // 通用错误
+ SUCCESS(0, "操作成功"),
+ UNKNOWN_ERROR(1000, "未知错误"),
+ PARAMETER_ERROR(1001, "参数错误"),
+ NOT_FOUND(1002, "资源未找到"),
+ DUPLICATE_ERROR(1003, "重复操作"),
+
+ // CLI错误
+ CLI_PARSE_ERROR(2001, "命令行参数解析错误"),
+ CLI_COMMAND_NOT_FOUND(2002, "命令未找到"),
+ CLI_INVALID_OPTION(2003, "无效的选项"),
+
+ // 网络错误
+ NETWORK_ERROR(3001, "网络请求失败"),
+ CONNECTION_TIMEOUT(3002, "连接超时"),
+ HTTP_ERROR(3003, "HTTP请求错误"),
+
+ // 数据错误
+ DATA_PARSE_ERROR(4001, "数据解析失败"),
+ DATA_FORMAT_ERROR(4002, "数据格式错误"),
+ DATA_EMPTY(4003, "数据为空"),
+
+ // 服务错误
+ SERVICE_UNAVAILABLE(5001, "服务不可用"),
+ SERVICE_RATE_LIMITED(5002, "请求被限流"),
+ AUTHENTICATION_FAILED(5003, "认证失败");
+
+ private final int code;
+ private final String message;
+
+ ErrorCode(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/exception/HotSearchException.java b/project/总代码/src/main/java/com/weibo/hotsearch/exception/HotSearchException.java
new file mode 100644
index 0000000..4869c81
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/exception/HotSearchException.java
@@ -0,0 +1,30 @@
+package com.weibo.hotsearch.exception;
+
+public class HotSearchException extends Exception {
+
+ private final ErrorCode errorCode;
+
+ public HotSearchException(ErrorCode errorCode) {
+ super(errorCode.getMessage());
+ this.errorCode = errorCode;
+ }
+
+ public HotSearchException(ErrorCode errorCode, Throwable cause) {
+ super(errorCode.getMessage(), cause);
+ this.errorCode = errorCode;
+ }
+
+ public HotSearchException(ErrorCode errorCode, String detail) {
+ super(errorCode.getMessage() + ": " + detail);
+ this.errorCode = errorCode;
+ }
+
+ public HotSearchException(ErrorCode errorCode, String detail, Throwable cause) {
+ super(errorCode.getMessage() + ": " + detail, cause);
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/exception/NetworkException.java b/project/总代码/src/main/java/com/weibo/hotsearch/exception/NetworkException.java
new file mode 100644
index 0000000..d1db694
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/exception/NetworkException.java
@@ -0,0 +1,20 @@
+package com.weibo.hotsearch.exception;
+
+public class NetworkException extends HotSearchException {
+
+ public NetworkException(ErrorCode errorCode) {
+ super(errorCode);
+ }
+
+ public NetworkException(ErrorCode errorCode, Throwable cause) {
+ super(errorCode, cause);
+ }
+
+ public NetworkException(ErrorCode errorCode, String detail) {
+ super(errorCode, detail);
+ }
+
+ public NetworkException(ErrorCode errorCode, String detail, Throwable cause) {
+ super(errorCode, detail, cause);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/model/AppContext.java b/project/总代码/src/main/java/com/weibo/hotsearch/model/AppContext.java
new file mode 100644
index 0000000..2c95b9e
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/model/AppContext.java
@@ -0,0 +1,49 @@
+package com.weibo.hotsearch.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AppContext {
+
+ private static final AppContext instance = new AppContext();
+
+ private final Map attributes = new HashMap<>();
+
+ private HotSearchResult currentResult;
+
+ private AppContext() {
+ }
+
+ public static AppContext getInstance() {
+ return instance;
+ }
+
+ public void setAttribute(String key, Object value) {
+ attributes.put(key, value);
+ }
+
+ public Object getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ public void removeAttribute(String key) {
+ attributes.remove(key);
+ }
+
+ public boolean hasAttribute(String key) {
+ return attributes.containsKey(key);
+ }
+
+ public HotSearchResult getCurrentResult() {
+ return currentResult;
+ }
+
+ public void setCurrentResult(HotSearchResult currentResult) {
+ this.currentResult = currentResult;
+ }
+
+ public void clear() {
+ attributes.clear();
+ currentResult = null;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchItem.java b/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchItem.java
new file mode 100644
index 0000000..b97131c
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchItem.java
@@ -0,0 +1,69 @@
+package com.weibo.hotsearch.model;
+
+import java.time.LocalDateTime;
+
+public class HotSearchItem {
+
+ private String title;
+ private long hotValue;
+ private int rank;
+ private String source;
+ private LocalDateTime fetchTime;
+
+ public HotSearchItem() {
+ }
+
+ public HotSearchItem(String title, long hotValue, int rank, String source) {
+ this.title = title;
+ this.hotValue = hotValue;
+ this.rank = rank;
+ this.source = source;
+ this.fetchTime = LocalDateTime.now();
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public long getHotValue() {
+ return hotValue;
+ }
+
+ public void setHotValue(long hotValue) {
+ this.hotValue = hotValue;
+ }
+
+ public int getRank() {
+ return rank;
+ }
+
+ public void setRank(int rank) {
+ this.rank = rank;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public LocalDateTime getFetchTime() {
+ return fetchTime;
+ }
+
+ public void setFetchTime(LocalDateTime fetchTime) {
+ this.fetchTime = fetchTime;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("HotSearchItem{title='%s', hotValue=%d, rank=%d, source='%s'}",
+ title, hotValue, rank, source);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchResult.java b/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchResult.java
new file mode 100644
index 0000000..a8cdcd4
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/model/HotSearchResult.java
@@ -0,0 +1,75 @@
+package com.weibo.hotsearch.model;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HotSearchResult {
+
+ private List items;
+ private String filterName;
+ private String dataSource;
+ private LocalDateTime fetchTime;
+ private int totalCount;
+
+ public HotSearchResult() {
+ this.items = new ArrayList<>();
+ this.fetchTime = LocalDateTime.now();
+ }
+
+ public HotSearchResult(List items, String filterName, String dataSource) {
+ this.items = items != null ? items : new ArrayList<>();
+ this.filterName = filterName;
+ this.dataSource = dataSource;
+ this.fetchTime = LocalDateTime.now();
+ this.totalCount = this.items.size();
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items != null ? items : new ArrayList<>();
+ this.totalCount = this.items.size();
+ }
+
+ public String getFilterName() {
+ return filterName;
+ }
+
+ public void setFilterName(String filterName) {
+ this.filterName = filterName;
+ }
+
+ public String getDataSource() {
+ return dataSource;
+ }
+
+ public void setDataSource(String dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public LocalDateTime getFetchTime() {
+ return fetchTime;
+ }
+
+ public void setFetchTime(LocalDateTime fetchTime) {
+ this.fetchTime = fetchTime;
+ }
+
+ public int getTotalCount() {
+ return totalCount;
+ }
+
+ public void addItem(HotSearchItem item) {
+ if (item != null) {
+ this.items.add(item);
+ this.totalCount++;
+ }
+ }
+
+ public boolean isEmpty() {
+ return items == null || items.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/service/DataFetcher.java b/project/总代码/src/main/java/com/weibo/hotsearch/service/DataFetcher.java
new file mode 100644
index 0000000..07d2055
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/service/DataFetcher.java
@@ -0,0 +1,307 @@
+package com.weibo.hotsearch.service;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.weibo.hotsearch.exception.DataParseException;
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.exception.NetworkException;
+import com.weibo.hotsearch.model.AppContext;
+import com.weibo.hotsearch.model.HotSearchItem;
+import com.weibo.hotsearch.model.HotSearchResult;
+import org.apache.hc.client5.http.fluent.Request;
+import org.apache.hc.core5.util.Timeout;
+
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataFetcher {
+
+ private static final String WEIBO_URL = "https://weibo.com/ajax/side/hotSearch";
+ private static final String TIEBA_URL = "https://tieba.baidu.com/hottopic/browse/topicList";
+ private static final String ZHIHU_URL = "https://www.zhihu.com/api/v4/search/top_search";
+
+ private static final int CONNECT_TIMEOUT = 10000;
+ private static final int RESPONSE_TIMEOUT = 10000;
+ private static final int MAX_RETRIES = 3;
+
+ public void fetch(String source) throws HotSearchException {
+ List items = new ArrayList<>();
+
+ switch (source.toLowerCase()) {
+ case "weibo":
+ items = fetchWeibo();
+ break;
+ case "tieba":
+ items = fetchTieba();
+ break;
+ case "zhihu":
+ items = fetchZhihu();
+ break;
+ case "all":
+ items.addAll(fetchWeibo());
+ items.addAll(fetchTieba());
+ items.addAll(fetchZhihu());
+ break;
+ default:
+ throw new HotSearchException(ErrorCode.PARAMETER_ERROR, "未知数据源: " + source);
+ }
+
+ HotSearchResult result = new HotSearchResult(items, null, source);
+ AppContext.getInstance().setCurrentResult(result);
+ }
+
+ private List fetchWeibo() throws HotSearchException {
+ List items = new ArrayList<>();
+ try {
+ String json = fetchUrlWithRetry(WEIBO_URL, "https://weibo.com/");
+ JSONObject root = JSONObject.parseObject(json);
+
+ if (!root.containsKey("data")) {
+ throw new DataParseException(ErrorCode.DATA_FORMAT_ERROR, "微博数据格式错误");
+ }
+
+ JSONObject data = root.getJSONObject("data");
+ if (!data.containsKey("realtime")) {
+ throw new DataParseException(ErrorCode.DATA_EMPTY, "微博数据为空");
+ }
+
+ JSONArray realtime = data.getJSONArray("realtime");
+ for (int i = 0; i < realtime.size(); i++) {
+ JSONObject item = realtime.getJSONObject(i);
+ if (item != null) {
+ HotSearchItem hotItem = parseWeiboItem(item);
+ if (hotItem != null) {
+ items.add(hotItem);
+ }
+ }
+ }
+ } catch (NetworkException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DataParseException(ErrorCode.DATA_PARSE_ERROR, "微博数据解析失败", e);
+ }
+ return items;
+ }
+
+ private HotSearchItem parseWeiboItem(JSONObject item) {
+ String word = item.getString("word");
+ if (word == null || word.isEmpty()) {
+ return null;
+ }
+ long num = item.getLongValue("num", 0);
+ int rank = item.getIntValue("rank", 0);
+ return new HotSearchItem(word, num, rank, "微博");
+ }
+
+ private List fetchTieba() throws HotSearchException {
+ List items = new ArrayList<>();
+ try {
+ String json = fetchUrlWithRetry(TIEBA_URL, "https://tieba.baidu.com/");
+ JSONObject root = JSONObject.parseObject(json);
+
+ if (!root.containsKey("data")) {
+ throw new DataParseException(ErrorCode.DATA_FORMAT_ERROR, "贴吧数据格式错误");
+ }
+
+ JSONObject data = root.getJSONObject("data");
+ if (!data.containsKey("bang_topic")) {
+ throw new DataParseException(ErrorCode.DATA_EMPTY, "贴吧数据为空");
+ }
+
+ JSONArray topics = data.getJSONArray("bang_topic");
+ for (int i = 0; i < topics.size(); i++) {
+ JSONObject item = topics.getJSONObject(i);
+ if (item != null) {
+ HotSearchItem hotItem = parseTiebaItem(item, i + 1);
+ if (hotItem != null) {
+ items.add(hotItem);
+ }
+ }
+ }
+ } catch (NetworkException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DataParseException(ErrorCode.DATA_PARSE_ERROR, "贴吧数据解析失败", e);
+ }
+ return items;
+ }
+
+ private HotSearchItem parseTiebaItem(JSONObject item, int index) {
+ String topicName = item.getString("topic_name");
+ if (topicName == null || topicName.isEmpty()) {
+ return null;
+ }
+ int readNum = item.getIntValue("read_num", 0);
+ int discussNum = item.getIntValue("discuss_num", 0);
+ return new HotSearchItem(topicName, (long) readNum + discussNum, 0, "百度贴吧");
+ }
+
+ private List fetchZhihu() throws HotSearchException {
+ List items = new ArrayList<>();
+ try {
+ String json = fetchUrlWithRetry(ZHIHU_URL, "https://zhuanlan.zhihu.com/");
+ JSONObject root = JSONObject.parseObject(json);
+
+ JSONArray data = findZhihuData(root, json);
+ if (data == null || data.isEmpty()) {
+ throw new DataParseException(ErrorCode.DATA_EMPTY, "知乎数据为空");
+ }
+
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject item = data.getJSONObject(i);
+ if (item != null) {
+ HotSearchItem hotItem = parseZhihuItem(item, i + 1);
+ if (hotItem != null) {
+ items.add(hotItem);
+ }
+ }
+ }
+ } catch (NetworkException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DataParseException(ErrorCode.DATA_PARSE_ERROR, "知乎数据解析失败", e);
+ }
+ return items;
+ }
+
+ private JSONArray findZhihuData(JSONObject root, String json) {
+ if (root.containsKey("data") && root.get("data") instanceof JSONArray) {
+ return root.getJSONArray("data");
+ } else if (root.containsKey("data")) {
+ JSONObject dataObj = root.getJSONObject("data");
+ if (dataObj != null && dataObj.containsKey("topics")) {
+ return dataObj.getJSONArray("topics");
+ }
+ } else if (root.containsKey("top_search")) {
+ JSONObject topSearch = root.getJSONObject("top_search");
+ if (topSearch != null && topSearch.containsKey("words")) {
+ return topSearch.getJSONArray("words");
+ }
+ } else if (json.startsWith("[")) {
+ return JSONArray.parseArray(json);
+ }
+ return null;
+ }
+
+ private HotSearchItem parseZhihuItem(JSONObject item, int index) {
+ String title = getItemTitle(item);
+ if (title == null || title.isEmpty()) {
+ return null;
+ }
+
+ long hotValue = 0;
+ if (item.containsKey("hot_score")) {
+ hotValue = item.getLongValue("hot_score", 0);
+ } else if (item.containsKey("score")) {
+ hotValue = item.getLongValue("score", 0);
+ } else if (item.containsKey("detail_text")) {
+ String detailText = item.getString("detail_text");
+ if (detailText != null) {
+ try {
+ String numStr = detailText.replaceAll("[^0-9]", "");
+ if (!numStr.isEmpty()) {
+ hotValue = Long.parseLong(numStr);
+ }
+ } catch (Exception e) {
+ hotValue = 0;
+ }
+ }
+ }
+
+ return new HotSearchItem(title, hotValue, index, "知乎");
+ }
+
+ private String getItemTitle(JSONObject item) {
+ if (item == null) {
+ return null;
+ }
+
+ String title = item.getString("title");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ title = item.getString("topic_title");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ title = item.getString("name");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ if (item.containsKey("target")) {
+ JSONObject target = item.getJSONObject("target");
+ if (target != null) {
+ title = target.getString("title");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+ }
+ }
+
+ title = item.getString("display_query");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ return item.getString("query");
+ }
+
+ private String fetchUrlWithRetry(String url, String referer) throws NetworkException {
+ int retryCount = 0;
+ Exception lastException = null;
+
+ while (retryCount < MAX_RETRIES) {
+ try {
+ return Request.get(url)
+ .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
+ .addHeader("Referer", referer)
+ .addHeader("Accept", "application/json, text/plain, */*;charset=UTF-8")
+ .addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
+ .addHeader("Accept-Encoding", "identity")
+ .addHeader("Connection", "keep-alive")
+ .addHeader("Content-Type", "application/json;charset=UTF-8")
+ .connectTimeout(Timeout.ofMilliseconds(CONNECT_TIMEOUT))
+ .responseTimeout(Timeout.ofMilliseconds(RESPONSE_TIMEOUT))
+ .execute()
+ .returnContent()
+ .asString(StandardCharsets.UTF_8);
+ } catch (java.net.SocketTimeoutException e) {
+ lastException = e;
+ retryCount++;
+ if (retryCount >= MAX_RETRIES) {
+ throw new NetworkException(ErrorCode.CONNECTION_TIMEOUT, "连接超时", e);
+ }
+ sleep(2000);
+ } catch (java.net.ConnectException e) {
+ lastException = e;
+ retryCount++;
+ if (retryCount >= MAX_RETRIES) {
+ throw new NetworkException(ErrorCode.NETWORK_ERROR, "连接失败", e);
+ }
+ sleep(2000);
+ } catch (Exception e) {
+ lastException = e;
+ retryCount++;
+ if (retryCount >= MAX_RETRIES) {
+ throw new NetworkException(ErrorCode.NETWORK_ERROR, "网络请求失败", e);
+ }
+ sleep(2000);
+ }
+ }
+ throw new NetworkException(ErrorCode.NETWORK_ERROR, "网络请求失败", lastException);
+ }
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/service/FilterService.java b/project/总代码/src/main/java/com/weibo/hotsearch/service/FilterService.java
new file mode 100644
index 0000000..0778cb1
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/service/FilterService.java
@@ -0,0 +1,44 @@
+package com.weibo.hotsearch.service;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.model.AppContext;
+import com.weibo.hotsearch.model.HotSearchItem;
+import com.weibo.hotsearch.model.HotSearchResult;
+import com.weibo.hotsearch.strategy.FilterStrategy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FilterService {
+
+ public void filter(FilterStrategy strategy) throws HotSearchException {
+ HotSearchResult currentResult = AppContext.getInstance().getCurrentResult();
+
+ if (currentResult == null || currentResult.isEmpty()) {
+ throw new HotSearchException(ErrorCode.DATA_EMPTY, "没有可过滤的数据,请先获取数据");
+ }
+
+ List filteredItems = new ArrayList<>();
+
+ for (HotSearchItem item : currentResult.getItems()) {
+ JSONObject jsonItem = convertToJson(item);
+ if (strategy.match(jsonItem)) {
+ filteredItems.add(item);
+ }
+ }
+
+ HotSearchResult filteredResult = new HotSearchResult(filteredItems, strategy.getFilterName(), currentResult.getDataSource());
+ AppContext.getInstance().setCurrentResult(filteredResult);
+ }
+
+ private JSONObject convertToJson(HotSearchItem item) {
+ JSONObject json = new JSONObject();
+ json.put("word", item.getTitle());
+ json.put("title", item.getTitle());
+ json.put("num", item.getHotValue());
+ json.put("rank", item.getRank());
+ return json;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/service/OutputService.java b/project/总代码/src/main/java/com/weibo/hotsearch/service/OutputService.java
new file mode 100644
index 0000000..68fc6c4
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/service/OutputService.java
@@ -0,0 +1,66 @@
+package com.weibo.hotsearch.service;
+
+import com.weibo.hotsearch.exception.ErrorCode;
+import com.weibo.hotsearch.exception.HotSearchException;
+import com.weibo.hotsearch.model.AppContext;
+import com.weibo.hotsearch.model.HotSearchResult;
+import com.weibo.hotsearch.view.TextView;
+import com.weibo.hotsearch.view.View;
+import com.weibo.hotsearch.view.ViewFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class OutputService {
+
+ private static final DateTimeFormatter FILE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
+ private static final String OUTPUT_DIR = "hotsearch_results";
+
+ public void output(String outputType) throws HotSearchException {
+ HotSearchResult result = AppContext.getInstance().getCurrentResult();
+
+ if (result == null) {
+ throw new HotSearchException(ErrorCode.DATA_EMPTY, "没有可输出的数据");
+ }
+
+ View view = ViewFactory.getView(outputType);
+ view.render(result);
+ }
+
+ public void saveToFile(String filePath) throws HotSearchException {
+ HotSearchResult result = AppContext.getInstance().getCurrentResult();
+
+ if (result == null) {
+ throw new HotSearchException(ErrorCode.DATA_EMPTY, "没有可保存的数据");
+ }
+
+ try {
+ File dir = new File(OUTPUT_DIR);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ String actualPath = filePath;
+ if (filePath == null || filePath.isEmpty()) {
+ String timestamp = LocalDateTime.now().format(FILE_FORMATTER);
+ actualPath = OUTPUT_DIR + File.separator + "hotsearch_" + timestamp + ".txt";
+ }
+
+ TextView textView = (TextView) ViewFactory.getView("text");
+ String content = textView.renderToString(result);
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(actualPath))) {
+ writer.write(content);
+ }
+
+ System.out.println("\n结果已保存到文件: " + actualPath);
+
+ } catch (IOException e) {
+ throw new HotSearchException(ErrorCode.UNKNOWN_ERROR, "保存文件失败", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategy.java b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategy.java
new file mode 100644
index 0000000..c5924bd
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategy.java
@@ -0,0 +1,12 @@
+package com.weibo.hotsearch.strategy;
+
+import com.alibaba.fastjson2.JSONObject;
+
+public interface FilterStrategy {
+
+ boolean match(JSONObject item);
+
+ String getFilterName();
+
+ String getFilterCode();
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategyFactory.java b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategyFactory.java
new file mode 100644
index 0000000..779067f
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/FilterStrategyFactory.java
@@ -0,0 +1,30 @@
+package com.weibo.hotsearch.strategy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FilterStrategyFactory {
+
+ private static final Map strategies = new HashMap<>();
+
+ static {
+ strategies.put("star", new StarFilterStrategy());
+ strategies.put("sports", new SportsFilterStrategy());
+ strategies.put("policy", new PolicyFilterStrategy());
+ }
+
+ public static FilterStrategy getStrategy(String code) {
+ if (code == null || code.isEmpty()) {
+ return null;
+ }
+ return strategies.get(code.toLowerCase());
+ }
+
+ public static Map getAllStrategies() {
+ return new HashMap<>(strategies);
+ }
+
+ public static boolean hasStrategy(String code) {
+ return code != null && strategies.containsKey(code.toLowerCase());
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/strategy/PolicyFilterStrategy.java b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/PolicyFilterStrategy.java
new file mode 100644
index 0000000..11ccdaf
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/PolicyFilterStrategy.java
@@ -0,0 +1,55 @@
+package com.weibo.hotsearch.strategy;
+
+import com.alibaba.fastjson2.JSONObject;
+
+public class PolicyFilterStrategy implements FilterStrategy {
+
+ private static final String[] KEYWORDS = {
+ "政策", "新规", "条例", "法规", "通知", "公告", "发布",
+ "国务院", "发改委", "财政部", "教育部", "工信部", "科技部",
+ "税收", "补贴", "优惠", "扶持", "改革", "开放", "创新",
+ "十四五", "计划", "规划", "方案", "意见", "办法", "细则",
+ "经济", "金融", "市场", "监管", "安全", "环保", "绿色"
+ };
+
+ @Override
+ public boolean match(JSONObject item) {
+ if (item == null) {
+ return false;
+ }
+ String word = getItemTitle(item);
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getFilterName() {
+ return "国家政策相关热搜";
+ }
+
+ @Override
+ public String getFilterCode() {
+ return "policy";
+ }
+
+ private String getItemTitle(JSONObject item) {
+ String title = item.getString("word");
+ if (title == null || title.isEmpty()) {
+ title = item.getString("topic_name");
+ }
+ if (title == null || title.isEmpty()) {
+ title = item.getString("title");
+ }
+ if (title == null || title.isEmpty()) {
+ title = item.getString("name");
+ }
+ return title;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/strategy/SportsFilterStrategy.java b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/SportsFilterStrategy.java
new file mode 100644
index 0000000..2b26650
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/SportsFilterStrategy.java
@@ -0,0 +1,54 @@
+package com.weibo.hotsearch.strategy;
+
+import com.alibaba.fastjson2.JSONObject;
+
+public class SportsFilterStrategy implements FilterStrategy {
+
+ private static final String[] KEYWORDS = {
+ "足球", "篮球", "世界杯", "NBA", "CBA", "奥运会", "世锦赛",
+ "冠军", "比赛", "夺冠", "进球", "比分", "运动员", "国足",
+ "乒乓", "排球", "羽毛球", "游泳", "田径", "体操", "跳水",
+ "MVP", "转会", "联赛", "中超", "英超", "西甲", "欧冠"
+ };
+
+ @Override
+ public boolean match(JSONObject item) {
+ if (item == null) {
+ return false;
+ }
+ String word = getItemTitle(item);
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getFilterName() {
+ return "体育相关热搜";
+ }
+
+ @Override
+ public String getFilterCode() {
+ return "sports";
+ }
+
+ private String getItemTitle(JSONObject item) {
+ String title = item.getString("word");
+ if (title == null || title.isEmpty()) {
+ title = item.getString("topic_name");
+ }
+ if (title == null || title.isEmpty()) {
+ title = item.getString("title");
+ }
+ if (title == null || title.isEmpty()) {
+ title = item.getString("name");
+ }
+ return title;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/strategy/StarFilterStrategy.java b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/StarFilterStrategy.java
new file mode 100644
index 0000000..6c93768
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/strategy/StarFilterStrategy.java
@@ -0,0 +1,76 @@
+package com.weibo.hotsearch.strategy;
+
+import com.alibaba.fastjson2.JSONObject;
+
+public class StarFilterStrategy implements FilterStrategy {
+
+ private static final String[] KEYWORDS = {
+ "明星", "演员", "歌手", "爱豆", "艺人", "红毯", "综艺", "新剧",
+ "恋情", "官宣", "演唱会", "代言", "造型", "封面"
+ };
+
+ @Override
+ public boolean match(JSONObject item) {
+ if (item == null) {
+ return false;
+ }
+ String word = getItemTitle(item);
+ if (word == null || word.isEmpty()) {
+ return false;
+ }
+ for (String keyword : KEYWORDS) {
+ if (word.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getFilterName() {
+ return "明星相关热搜";
+ }
+
+ @Override
+ public String getFilterCode() {
+ return "star";
+ }
+
+ private String getItemTitle(JSONObject item) {
+ if (item == null) {
+ return null;
+ }
+
+ String title = item.getString("word");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ title = item.getString("topic_name");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ title = item.getString("title");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ title = item.getString("name");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+
+ if (item.containsKey("target")) {
+ JSONObject target = item.getJSONObject("target");
+ if (target != null) {
+ title = target.getString("title");
+ if (title != null && !title.isEmpty()) {
+ return title;
+ }
+ }
+ }
+
+ return title;
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/view/ConsoleView.java b/project/总代码/src/main/java/com/weibo/hotsearch/view/ConsoleView.java
new file mode 100644
index 0000000..34253d6
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/view/ConsoleView.java
@@ -0,0 +1,51 @@
+package com.weibo.hotsearch.view;
+
+import com.weibo.hotsearch.model.HotSearchItem;
+import com.weibo.hotsearch.model.HotSearchResult;
+
+import java.time.format.DateTimeFormatter;
+
+public class ConsoleView implements View {
+
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public void render(HotSearchResult result) {
+ if (result == null) {
+ System.out.println("没有数据可显示");
+ return;
+ }
+
+ System.out.println("\n===== " + (result.getFilterName() != null ? result.getFilterName() : "热搜结果") + " =====");
+ System.out.println("数据源: " + result.getDataSource());
+ System.out.println("采集时间: " + result.getFetchTime().format(FORMATTER));
+ System.out.println("----------------------------------------");
+
+ if (result.isEmpty()) {
+ System.out.println("当前暂无符合条件的热搜内容");
+ } else {
+ for (int i = 0; i < result.getItems().size(); i++) {
+ HotSearchItem item = result.getItems().get(i);
+ String line = formatItem(item, i);
+ System.out.println(line);
+ }
+ }
+
+ System.out.println("\n===== 热搜总数:" + result.getTotalCount() + " 条 =====");
+ }
+
+ private String formatItem(HotSearchItem item, int index) {
+ if (item.getRank() > 0) {
+ return String.format("排名:%d\t热度:%d\t来源:%s\t热搜:%s",
+ item.getRank(), item.getHotValue(), item.getSource(), item.getTitle());
+ } else {
+ return String.format("序号:%d\t热度:%d\t来源:%s\t热搜:%s",
+ index + 1, item.getHotValue(), item.getSource(), item.getTitle());
+ }
+ }
+
+ @Override
+ public String getViewType() {
+ return "console";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/view/TextView.java b/project/总代码/src/main/java/com/weibo/hotsearch/view/TextView.java
new file mode 100644
index 0000000..4691a24
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/view/TextView.java
@@ -0,0 +1,83 @@
+package com.weibo.hotsearch.view;
+
+import com.weibo.hotsearch.model.HotSearchItem;
+import com.weibo.hotsearch.model.HotSearchResult;
+
+import java.time.format.DateTimeFormatter;
+import java.util.StringJoiner;
+
+public class TextView implements View {
+
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ public void render(HotSearchResult result) {
+ if (result == null) {
+ System.out.println("没有数据可显示");
+ return;
+ }
+
+ StringJoiner sj = new StringJoiner("\n");
+
+ sj.add("===== " + (result.getFilterName() != null ? result.getFilterName() : "热搜结果") + " =====");
+ sj.add("数据源: " + result.getDataSource());
+ sj.add("采集时间: " + result.getFetchTime().format(FORMATTER));
+ sj.add("----------------------------------------");
+
+ if (result.isEmpty()) {
+ sj.add("当前暂无符合条件的热搜内容");
+ } else {
+ for (int i = 0; i < result.getItems().size(); i++) {
+ HotSearchItem item = result.getItems().get(i);
+ sj.add(formatItem(item, i));
+ }
+ }
+
+ sj.add("");
+ sj.add("===== 热搜总数:" + result.getTotalCount() + " 条 =====");
+
+ System.out.println(sj.toString());
+ }
+
+ private String formatItem(HotSearchItem item, int index) {
+ if (item.getRank() > 0) {
+ return String.format("排名:%d\t热度:%d\t来源:%s\t热搜:%s",
+ item.getRank(), item.getHotValue(), item.getSource(), item.getTitle());
+ } else {
+ return String.format("序号:%d\t热度:%d\t来源:%s\t热搜:%s",
+ index + 1, item.getHotValue(), item.getSource(), item.getTitle());
+ }
+ }
+
+ public String renderToString(HotSearchResult result) {
+ if (result == null) {
+ return "没有数据可显示";
+ }
+
+ StringJoiner sj = new StringJoiner("\n");
+
+ sj.add("===== " + (result.getFilterName() != null ? result.getFilterName() : "热搜结果") + " =====");
+ sj.add("数据源: " + result.getDataSource());
+ sj.add("采集时间: " + result.getFetchTime().format(FORMATTER));
+ sj.add("----------------------------------------");
+
+ if (result.isEmpty()) {
+ sj.add("当前暂无符合条件的热搜内容");
+ } else {
+ for (int i = 0; i < result.getItems().size(); i++) {
+ HotSearchItem item = result.getItems().get(i);
+ sj.add(formatItem(item, i));
+ }
+ }
+
+ sj.add("");
+ sj.add("===== 热搜总数:" + result.getTotalCount() + " 条 =====");
+
+ return sj.toString();
+ }
+
+ @Override
+ public String getViewType() {
+ return "text";
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/view/View.java b/project/总代码/src/main/java/com/weibo/hotsearch/view/View.java
new file mode 100644
index 0000000..754eff4
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/view/View.java
@@ -0,0 +1,10 @@
+package com.weibo.hotsearch.view;
+
+import com.weibo.hotsearch.model.HotSearchResult;
+
+public interface View {
+
+ void render(HotSearchResult result);
+
+ String getViewType();
+}
\ No newline at end of file
diff --git a/project/总代码/src/main/java/com/weibo/hotsearch/view/ViewFactory.java b/project/总代码/src/main/java/com/weibo/hotsearch/view/ViewFactory.java
new file mode 100644
index 0000000..719ffe9
--- /dev/null
+++ b/project/总代码/src/main/java/com/weibo/hotsearch/view/ViewFactory.java
@@ -0,0 +1,29 @@
+package com.weibo.hotsearch.view;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ViewFactory {
+
+ private static final Map views = new HashMap<>();
+
+ static {
+ views.put("console", new ConsoleView());
+ views.put("text", new TextView());
+ }
+
+ public static View getView(String type) {
+ if (type == null || type.isEmpty()) {
+ return views.get("console");
+ }
+ return views.getOrDefault(type.toLowerCase(), views.get("console"));
+ }
+
+ public static boolean hasView(String type) {
+ return type != null && views.containsKey(type.toLowerCase());
+ }
+
+ public static Map getAllViews() {
+ return new HashMap<>(views);
+ }
+}
\ No newline at end of file
diff --git a/project/总代码/src/run.bat b/project/总代码/src/run.bat
new file mode 100644
index 0000000..4fa4d4d
--- /dev/null
+++ b/project/总代码/src/run.bat
@@ -0,0 +1,4 @@
+@echo off
+set CLASSPATH=target\classes;C:\Users\ruiruirui\.m2\repository\org\jsoup\jsoup\1.17.2\jsoup-1.17.2.jar
+java WeiboHotSearcha
+pause
\ No newline at end of file
diff --git a/project/总代码/target/classes/WeiboStarHotSearcha.class b/project/总代码/target/classes/WeiboStarHotSearcha.class
new file mode 100644
index 0000000..b357cea
Binary files /dev/null and b/project/总代码/target/classes/WeiboStarHotSearcha.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/ConsoleOutputHandler.class b/project/总代码/target/classes/com/weibo/hotsearch/ConsoleOutputHandler.class
new file mode 100644
index 0000000..73999e2
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/ConsoleOutputHandler.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/HotSearchFilter.class b/project/总代码/target/classes/com/weibo/hotsearch/HotSearchFilter.class
new file mode 100644
index 0000000..56a2f04
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/HotSearchFilter.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/Main.class b/project/总代码/target/classes/com/weibo/hotsearch/Main.class
new file mode 100644
index 0000000..0326206
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/Main.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/OutputHandler.class b/project/总代码/target/classes/com/weibo/hotsearch/OutputHandler.class
new file mode 100644
index 0000000..570911b
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/OutputHandler.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/StarFilter.class b/project/总代码/target/classes/com/weibo/hotsearch/StarFilter.class
new file mode 100644
index 0000000..5677f4c
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/StarFilter.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/cli/CliHandler.class b/project/总代码/target/classes/com/weibo/hotsearch/cli/CliHandler.class
new file mode 100644
index 0000000..860afe2
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/cli/CliHandler.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/cli/CliParser.class b/project/总代码/target/classes/com/weibo/hotsearch/cli/CliParser.class
new file mode 100644
index 0000000..89aae85
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/cli/CliParser.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/Command.class b/project/总代码/target/classes/com/weibo/hotsearch/command/Command.class
new file mode 100644
index 0000000..59d6722
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/Command.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/CommandInvoker.class b/project/总代码/target/classes/com/weibo/hotsearch/command/CommandInvoker.class
new file mode 100644
index 0000000..78c5d82
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/CommandInvoker.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/CommandResult.class b/project/总代码/target/classes/com/weibo/hotsearch/command/CommandResult.class
new file mode 100644
index 0000000..f962b90
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/CommandResult.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/FetchCommand.class b/project/总代码/target/classes/com/weibo/hotsearch/command/FetchCommand.class
new file mode 100644
index 0000000..11a23d4
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/FetchCommand.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/FilterCommand.class b/project/总代码/target/classes/com/weibo/hotsearch/command/FilterCommand.class
new file mode 100644
index 0000000..dfa3c45
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/FilterCommand.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/HelpCommand.class b/project/总代码/target/classes/com/weibo/hotsearch/command/HelpCommand.class
new file mode 100644
index 0000000..ebec991
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/HelpCommand.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/OutputCommand.class b/project/总代码/target/classes/com/weibo/hotsearch/command/OutputCommand.class
new file mode 100644
index 0000000..a3714bd
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/OutputCommand.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/command/SaveCommand.class b/project/总代码/target/classes/com/weibo/hotsearch/command/SaveCommand.class
new file mode 100644
index 0000000..f7fb233
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/command/SaveCommand.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/controller/HotSearchController.class b/project/总代码/target/classes/com/weibo/hotsearch/controller/HotSearchController.class
new file mode 100644
index 0000000..7151cbe
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/controller/HotSearchController.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/exception/CliException.class b/project/总代码/target/classes/com/weibo/hotsearch/exception/CliException.class
new file mode 100644
index 0000000..ae58f8f
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/exception/CliException.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/exception/DataParseException.class b/project/总代码/target/classes/com/weibo/hotsearch/exception/DataParseException.class
new file mode 100644
index 0000000..c1eb9a7
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/exception/DataParseException.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/exception/ErrorCode.class b/project/总代码/target/classes/com/weibo/hotsearch/exception/ErrorCode.class
new file mode 100644
index 0000000..8b968d2
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/exception/ErrorCode.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/exception/HotSearchException.class b/project/总代码/target/classes/com/weibo/hotsearch/exception/HotSearchException.class
new file mode 100644
index 0000000..90cdef2
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/exception/HotSearchException.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/exception/NetworkException.class b/project/总代码/target/classes/com/weibo/hotsearch/exception/NetworkException.class
new file mode 100644
index 0000000..0b4b21f
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/exception/NetworkException.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/model/AppContext.class b/project/总代码/target/classes/com/weibo/hotsearch/model/AppContext.class
new file mode 100644
index 0000000..2c2376d
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/model/AppContext.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchItem.class b/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchItem.class
new file mode 100644
index 0000000..edb954c
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchItem.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchResult.class b/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchResult.class
new file mode 100644
index 0000000..dd94893
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/model/HotSearchResult.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/service/DataFetcher.class b/project/总代码/target/classes/com/weibo/hotsearch/service/DataFetcher.class
new file mode 100644
index 0000000..36794ce
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/service/DataFetcher.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/service/FilterService.class b/project/总代码/target/classes/com/weibo/hotsearch/service/FilterService.class
new file mode 100644
index 0000000..19550a7
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/service/FilterService.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/service/OutputService.class b/project/总代码/target/classes/com/weibo/hotsearch/service/OutputService.class
new file mode 100644
index 0000000..a45f2a5
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/service/OutputService.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategy.class b/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategy.class
new file mode 100644
index 0000000..0de4599
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategy.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategyFactory.class b/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategyFactory.class
new file mode 100644
index 0000000..f0f738d
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/strategy/FilterStrategyFactory.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/strategy/PolicyFilterStrategy.class b/project/总代码/target/classes/com/weibo/hotsearch/strategy/PolicyFilterStrategy.class
new file mode 100644
index 0000000..027051c
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/strategy/PolicyFilterStrategy.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/strategy/SportsFilterStrategy.class b/project/总代码/target/classes/com/weibo/hotsearch/strategy/SportsFilterStrategy.class
new file mode 100644
index 0000000..3c9f127
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/strategy/SportsFilterStrategy.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/strategy/StarFilterStrategy.class b/project/总代码/target/classes/com/weibo/hotsearch/strategy/StarFilterStrategy.class
new file mode 100644
index 0000000..95cd885
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/strategy/StarFilterStrategy.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/view/ConsoleView.class b/project/总代码/target/classes/com/weibo/hotsearch/view/ConsoleView.class
new file mode 100644
index 0000000..9e9e7c1
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/view/ConsoleView.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/view/TextView.class b/project/总代码/target/classes/com/weibo/hotsearch/view/TextView.class
new file mode 100644
index 0000000..c50bf96
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/view/TextView.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/view/View.class b/project/总代码/target/classes/com/weibo/hotsearch/view/View.class
new file mode 100644
index 0000000..54ae3f2
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/view/View.class differ
diff --git a/project/总代码/target/classes/com/weibo/hotsearch/view/ViewFactory.class b/project/总代码/target/classes/com/weibo/hotsearch/view/ViewFactory.class
new file mode 100644
index 0000000..ff01502
Binary files /dev/null and b/project/总代码/target/classes/com/weibo/hotsearch/view/ViewFactory.class differ
diff --git a/project/总代码/target/hotsearch-1.0.0-jar-with-dependencies.jar b/project/总代码/target/hotsearch-1.0.0-jar-with-dependencies.jar
new file mode 100644
index 0000000..8e89f60
Binary files /dev/null and b/project/总代码/target/hotsearch-1.0.0-jar-with-dependencies.jar differ
diff --git a/project/总代码/target/hotsearch-1.0.0.jar b/project/总代码/target/hotsearch-1.0.0.jar
new file mode 100644
index 0000000..b299671
Binary files /dev/null and b/project/总代码/target/hotsearch-1.0.0.jar differ
diff --git a/project/总代码/target/maven-archiver/pom.properties b/project/总代码/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..4544cae
--- /dev/null
+++ b/project/总代码/target/maven-archiver/pom.properties
@@ -0,0 +1,3 @@
+artifactId=hotsearch
+groupId=com.weibo
+version=1.0.0
diff --git a/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..33102c1
--- /dev/null
+++ b/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,37 @@
+com\weibo\hotsearch\model\AppContext.class
+com\weibo\hotsearch\strategy\PolicyFilterStrategy.class
+com\weibo\hotsearch\view\View.class
+com\weibo\hotsearch\strategy\SportsFilterStrategy.class
+com\weibo\hotsearch\model\HotSearchResult.class
+com\weibo\hotsearch\service\FilterService.class
+com\weibo\hotsearch\view\TextView.class
+com\weibo\hotsearch\HotSearchFilter.class
+com\weibo\hotsearch\strategy\FilterStrategyFactory.class
+com\weibo\hotsearch\exception\HotSearchException.class
+com\weibo\hotsearch\command\SaveCommand.class
+com\weibo\hotsearch\StarFilter.class
+com\weibo\hotsearch\strategy\FilterStrategy.class
+com\weibo\hotsearch\exception\ErrorCode.class
+com\weibo\hotsearch\command\HelpCommand.class
+com\weibo\hotsearch\command\FilterCommand.class
+com\weibo\hotsearch\exception\NetworkException.class
+com\weibo\hotsearch\OutputHandler.class
+com\weibo\hotsearch\command\CommandResult.class
+com\weibo\hotsearch\cli\CliHandler.class
+com\weibo\hotsearch\model\HotSearchItem.class
+com\weibo\hotsearch\controller\HotSearchController.class
+com\weibo\hotsearch\strategy\StarFilterStrategy.class
+com\weibo\hotsearch\service\DataFetcher.class
+com\weibo\hotsearch\command\Command.class
+com\weibo\hotsearch\ConsoleOutputHandler.class
+com\weibo\hotsearch\view\ConsoleView.class
+com\weibo\hotsearch\Main.class
+WeiboStarHotSearcha.class
+com\weibo\hotsearch\command\CommandInvoker.class
+com\weibo\hotsearch\cli\CliParser.class
+com\weibo\hotsearch\command\OutputCommand.class
+com\weibo\hotsearch\command\FetchCommand.class
+com\weibo\hotsearch\exception\CliException.class
+com\weibo\hotsearch\service\OutputService.class
+com\weibo\hotsearch\view\ViewFactory.class
+com\weibo\hotsearch\exception\DataParseException.class
diff --git a/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..6983794
--- /dev/null
+++ b/project/总代码/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,37 @@
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\OutputCommand.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\WeiboStarHotSearcha.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\Command.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\model\AppContext.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\SaveCommand.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\CommandInvoker.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\service\OutputService.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\service\DataFetcher.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\OutputHandler.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\cli\CliParser.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\CommandResult.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\cli\CliHandler.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\FetchCommand.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\strategy\FilterStrategyFactory.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\Main.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\exception\ErrorCode.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\exception\CliException.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\StarFilter.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\view\View.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\strategy\FilterStrategy.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\view\TextView.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\strategy\PolicyFilterStrategy.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\model\HotSearchResult.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\ConsoleOutputHandler.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\HelpCommand.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\exception\DataParseException.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\service\FilterService.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\command\FilterCommand.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\exception\NetworkException.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\controller\HotSearchController.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\HotSearchFilter.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\view\ViewFactory.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\strategy\StarFilterStrategy.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\model\HotSearchItem.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\view\ConsoleView.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\exception\HotSearchException.java
+C:\Users\ruiruirui\java\w11\总代码\src\main\java\com\weibo\hotsearch\strategy\SportsFilterStrategy.java