Browse Source

上传文件至 'project/src/main/java/com/example'

main
Xingzhimeng 3 weeks ago
parent
commit
1a47c6840d
  1. 33
      project/src/main/java/com/example/JsonSaveStrategy.java
  2. 10
      project/src/main/java/com/example/SaveStrategy.java
  3. 587
      project/src/main/java/com/example/SteamCrawler.java
  4. 30
      project/src/main/java/com/example/TextSaveStrategy.java

33
project/src/main/java/com/example/JsonSaveStrategy.java

@ -0,0 +1,33 @@
package com.example;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
/**
* JSON格式保存策略 - 策略模式实现
*/
public class JsonSaveStrategy implements SaveStrategy {
@Override
public void save(Object data, String filename) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
if (data instanceof List) {
List<?> list = (List<?>) data;
writer.write("[");
for (int i = 0; i < list.size(); i++) {
if (i > 0) writer.write(",");
writer.write("\"" + list.get(i).toString().replace("\"", "\\\"") + "\"");
}
writer.write("]");
} else {
writer.write("\"" + data.toString().replace("\"", "\\\"") + "\"");
}
}
}
@Override
public String getStrategyName() {
return "JSON格式";
}
}

10
project/src/main/java/com/example/SaveStrategy.java

@ -0,0 +1,10 @@
package com.example;
/**
* 数据保存策略接口 - 策略模式
* 课堂知识点接口策略模式
*/
public interface SaveStrategy {
void save(Object data, String filename) throws Exception;
String getStrategyName();
}

587
project/src/main/java/com/example/SteamCrawler.java

@ -0,0 +1,587 @@
package com.example;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Steam游戏爬虫
* 课堂知识点类与对象封装继承多态集合框架异常处理文件IO字符串处理
*/
public class SteamCrawler extends Crawler {
private static final String 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";
/**
* 爬取数据 - 重写父类方法
* 课堂知识点方法重写多态
*/
@Override
public void crawl() {
crawlGameInfo("3564740");
}
/**
* 打印结果 - 重写父类方法
* 课堂知识点方法重写多态
*/
@Override
public void printResults() {
printGameInfo();
}
// 私有属性 - 课堂知识点:封装
private String appName;
private String price;
private String releaseDate;
private String reviewSummary;
private List<String> tags;
/**
* 构造方法 - 初始化游戏标签列表
*/
public SteamCrawler() {
this.tags = new ArrayList<>();
}
/**
* 爬取游戏信息
* @param appId Steam游戏ID
*/
public void crawlGameInfo(String appId) {
String apiUrl = "https://store.steampowered.com/api/appdetails?appids=" + appId + "&l=schinese&cc=us";
System.out.println("通过API获取游戏信息: " + apiUrl);
System.out.println("正在连接Steam API...");
// 先检查网络状态 - 使用父类方法
if (!isNetworkAvailable()) {
System.err.println("❌ 网络连接不可用!请检查网络设置");
System.out.println("使用默认游戏信息...");
setDefaultValues();
setSuccess(true);
setDataCount(1);
return;
}
try {
// 使用父类的延迟方法
delay();
System.out.println("开始发送HTTP请求...");
Document doc = HttpCrawler.get(apiUrl);
System.out.println("成功获取API响应");
String json = doc.text();
System.out.println("API返回数据长度: " + (json != null ? json.length() : 0));
if (json == null || json.isEmpty()) {
System.err.println("API返回空数据");
setDefaultValues();
return;
}
System.out.println("开始解析游戏信息...");
parseGameInfoFromJson(json, appId);
System.out.println("开始获取评价信息...");
fetchReviewInfo(appId);
setSuccess(true);
setDataCount(1);
} catch (java.io.IOException e) {
System.err.println("❌ 请求失败: " + e.getMessage());
System.out.println("使用默认游戏信息...");
setDefaultValues();
}
}
/**
* 设置默认游戏信息离线备用
*/
private void setDefaultValues() {
appName = "燕云十六声";
price = "免费游玩";
releaseDate = "2025 年 11 月";
reviewSummary = "暂无用户评测";
tags.clear();
tags.add("动作");
tags.add("冒险");
tags.add("角色扮演");
}
/**
* 获取评价信息
* @param appId Steam游戏ID
*/
private void fetchReviewInfo(String appId) {
String reviewUrl = "https://store.steampowered.com/appreviews/" + appId + "?json=1&language=schinese";
System.out.println("获取评价信息: " + reviewUrl);
try {
delay();
Document doc = Jsoup.connect(reviewUrl)
.userAgent(USER_AGENT)
.header("Accept", "application/json")
.timeout(15000)
.ignoreContentType(true)
.get();
String json = doc.text();
parseReviewInfo(json);
} catch (Exception e) {
System.err.println("获取评价信息失败: " + e.getMessage());
}
}
/**
* 解析评价信息
* @param json JSON字符串
*/
private void parseReviewInfo(String json) {
String reviewScoreDesc = extractJsonValue(json, "review_score_desc");
String totalReviews = extractJsonValue(json, "total_reviews");
String positive = extractJsonValue(json, "total_positive");
if (reviewScoreDesc != null) {
String localizedDesc = translateReviewDesc(reviewScoreDesc);
if (totalReviews != null && !"0".equals(totalReviews)) {
int total = Integer.parseInt(totalReviews);
int pos = positive != null ? Integer.parseInt(positive) : 0;
double positivePercent = total > 0 ? (pos * 100.0 / total) : 0;
reviewSummary = String.format("%s (%.1f%% 好评, %d 条评测)",
localizedDesc, positivePercent, total);
} else {
reviewSummary = localizedDesc;
}
} else if (totalReviews != null && !"0".equals(totalReviews)) {
int total = Integer.parseInt(totalReviews);
int pos = positive != null ? Integer.parseInt(positive) : 0;
double positivePercent = total > 0 ? (pos * 100.0 / total) : 0;
String ratingText = "褒贬不一";
// 课堂知识点:if-else条件判断
if (positivePercent >= 95) ratingText = "好评如潮";
else if (positivePercent >= 80) ratingText = "特别好评";
else if (positivePercent >= 70) ratingText = "多半好评";
else if (positivePercent >= 50) ratingText = "褒贬不一";
else if (positivePercent >= 30) ratingText = "多半差评";
else ratingText = "差评如潮";
reviewSummary = String.format("%s (%.1f%% 好评, %d 条评测)",
ratingText, positivePercent, total);
}
}
/**
* 翻译评价描述
* @param desc 英文描述
* @return 中文描述
*/
private String translateReviewDesc(String desc) {
// 课堂知识点:switch语句
switch (desc) {
case "Overwhelmingly Positive": return "好评如潮";
case "Very Positive": return "特别好评";
case "Positive": return "好评";
case "Mostly Positive": return "多半好评";
case "Mixed": return "褒贬不一";
case "Mostly Negative": return "多半差评";
case "Negative": return "差评";
case "Very Negative": return "多半差评";
case "Overwhelmingly Negative": return "差评如潮";
case "No user reviews": return "暂无用户评测";
default: return desc;
}
}
/**
* 从JSON中解析游戏信息
* @param json JSON字符串
* @param appId Steam游戏ID
*/
private void parseGameInfoFromJson(String json, String appId) {
String[] prefixes = {
appId + ":",
"\"" + appId + "\""
};
int startIndex = -1;
for (String prefix : prefixes) {
startIndex = json.indexOf(prefix);
if (startIndex != -1) {
break;
}
}
if (startIndex == -1) {
appName = extractJsonValue(json, "name");
if (appName == null) {
appName = "未找到游戏";
price = "暂无价格信息";
releaseDate = "暂无发行日期";
reviewSummary = "暂无评价信息";
return;
}
} else {
json = json.substring(startIndex);
}
appName = extractJsonValue(json, "name");
if (appName == null) appName = "未知";
String isFree = extractJsonValue(json, "is_free");
if ("true".equals(isFree)) {
price = "免费游玩";
} else {
String priceOverview = extractJsonValue(json, "final_formatted");
if (priceOverview != null) {
price = priceOverview;
} else {
int priceStart = json.indexOf("price_overview");
if (priceStart != -1) {
String priceSection = json.substring(priceStart);
priceOverview = extractJsonValue(priceSection, "final_formatted");
price = priceOverview != null ? priceOverview : "暂无价格信息";
} else {
price = "暂无价格信息";
}
}
}
String releaseDateStr = extractJsonValue(json, "date");
if (releaseDateStr == null) {
int releaseStart = json.indexOf("release_date");
if (releaseStart != -1) {
String releaseSection = json.substring(releaseStart);
releaseDateStr = extractJsonValue(releaseSection, "date");
if (releaseDateStr == null) {
String comingSoon = extractJsonValue(releaseSection, "coming_soon");
releaseDate = "true".equals(comingSoon) ? "即将推出" : "暂无发行日期";
}
} else {
releaseDate = "暂无发行日期";
}
}
if (releaseDateStr != null) {
releaseDate = releaseDateStr;
}
String recommendations = extractJsonValue(json, "total_reviews");
if (recommendations != null) {
reviewSummary = "总评测数: " + recommendations;
} else {
reviewSummary = "暂无评价信息";
}
String genresSection = extractJsonSection(json, "genres");
if (genresSection != null) {
tags = extractGenres(genresSection);
}
if (tags.isEmpty()) {
String categoriesSection = extractJsonSection(json, "categories");
if (categoriesSection != null) {
tags = extractCategories(categoriesSection);
}
}
}
/**
* 从JSON中提取值
* @param json JSON字符串
* @param key 键名
* @return
*/
private String extractJsonValue(String json, String key) {
String searchKey = "\"" + key + "\"" + ":";
int startIndex = json.indexOf(searchKey);
if (startIndex == -1) return null;
startIndex += searchKey.length();
while (startIndex < json.length() && (json.charAt(startIndex) == ' ' || json.charAt(startIndex) == '\n')) {
startIndex++;
}
if (startIndex >= json.length()) return null;
if (json.charAt(startIndex) == '"') {
startIndex++;
int endIndex = json.indexOf("\"", startIndex);
if (endIndex != -1) {
return json.substring(startIndex, endIndex);
}
} else if (json.charAt(startIndex) == '-' || Character.isDigit(json.charAt(startIndex))) {
int endIndex = startIndex;
while (endIndex < json.length() && (Character.isDigit(json.charAt(endIndex)) || json.charAt(endIndex) == '.' || json.charAt(endIndex) == '-')) {
endIndex++;
}
return json.substring(startIndex, endIndex);
} else if (json.substring(startIndex).startsWith("true")) {
return "true";
} else if (json.substring(startIndex).startsWith("false")) {
return "false";
}
return null;
}
/**
* 从JSON中提取数组段
* @param json JSON字符串
* @param key 键名
* @return 数组段
*/
private String extractJsonSection(String json, String key) {
String searchKey = "\"" + key + "\"" + ":";
int startIndex = json.indexOf(searchKey);
if (startIndex == -1) return null;
startIndex += searchKey.length();
while (startIndex < json.length() && (json.charAt(startIndex) == ' ' || json.charAt(startIndex) == '\n')) {
startIndex++;
}
if (startIndex >= json.length() || json.charAt(startIndex) != '[') return null;
int bracketCount = 1;
int endIndex = startIndex + 1;
while (endIndex < json.length() && bracketCount > 0) {
char c = json.charAt(endIndex);
if (c == '[') bracketCount++;
else if (c == ']') bracketCount--;
endIndex++;
}
return json.substring(startIndex, endIndex);
}
/**
* 提取游戏类型
* @param genresSection JSON数组段
* @return 类型列表
*/
private List<String> extractGenres(String genresSection) {
List<String> genreList = new ArrayList<>();
int startIndex = 0;
while (startIndex < genresSection.length() && genreList.size() < 10) {
int descIndex = genresSection.indexOf("description\"", startIndex);
if (descIndex == -1) break;
int colonIndex = genresSection.indexOf(":", descIndex);
if (colonIndex == -1) break;
int quoteIndex = genresSection.indexOf("\"", colonIndex + 1);
if (quoteIndex == -1) break;
int endQuoteIndex = genresSection.indexOf("\"", quoteIndex + 1);
if (endQuoteIndex == -1) break;
String genre = genresSection.substring(quoteIndex + 1, endQuoteIndex);
genreList.add(genre);
startIndex = endQuoteIndex + 1;
}
return genreList;
}
/**
* 提取游戏分类
* @param categoriesSection JSON数组段
* @return 分类列表
*/
private List<String> extractCategories(String categoriesSection) {
List<String> categoryList = new ArrayList<>();
int startIndex = 0;
while (startIndex < categoriesSection.length() && categoryList.size() < 10) {
int descIndex = categoriesSection.indexOf("description\"", startIndex);
if (descIndex == -1) break;
int colonIndex = categoriesSection.indexOf(":", descIndex);
if (colonIndex == -1) break;
int quoteIndex = categoriesSection.indexOf("\"", colonIndex + 1);
if (quoteIndex == -1) break;
int endQuoteIndex = categoriesSection.indexOf("\"", quoteIndex + 1);
if (endQuoteIndex == -1) break;
String category = categoriesSection.substring(quoteIndex + 1, endQuoteIndex);
categoryList.add(category);
startIndex = endQuoteIndex + 1;
}
return categoryList;
}
/**
* 通过API搜索游戏
* @param gameName 游戏名称
* @return App ID
*/
public String searchGameByApi(String gameName) {
String apiUrl = "https://store.steampowered.com/api/storesearch/?term=" +
gameName.replace(" ", "%20") + "&l=schinese&cc=us";
System.out.println("通过API搜索游戏: " + apiUrl);
try {
delay();
Document doc = Jsoup.connect(apiUrl)
.userAgent(USER_AGENT)
.header("Accept", "application/json")
.timeout(15000)
.ignoreContentType(true)
.get();
String json = doc.text();
if (json.contains("appid")) {
int appidIndex = json.indexOf("appid");
if (appidIndex != -1) {
int colonIndex = json.indexOf(":", appidIndex);
if (colonIndex != -1) {
int start = colonIndex + 1;
while (start < json.length() && (json.charAt(start) == ' ' || json.charAt(start) == '\n')) {
start++;
}
int end = start;
while (end < json.length() && Character.isDigit(json.charAt(end))) {
end++;
}
if (end > start) {
return json.substring(start, end);
}
}
}
}
} catch (Exception e) {
System.err.println("API搜索失败: " + e.getMessage());
}
return null;
}
/**
* 通过网页搜索游戏
* @param gameName 游戏名称
* @return App ID
*/
public String searchGame(String gameName) {
String searchUrl = "https://store.steampowered.com/search/?term=" +
gameName.replace(" ", "+") + "&cc=us&l=schinese";
System.out.println("搜索游戏: " + searchUrl);
try {
delay();
Document doc = Jsoup.connect(searchUrl)
.userAgent(USER_AGENT)
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
.timeout(15000)
.get();
Elements results = doc.select("a.search_result_row");
for (Element result : results) {
Element titleElement = result.selectFirst("span.title");
if (titleElement != null) {
String titleText = titleElement.text().toLowerCase();
if (titleText.contains("winds") || titleText.contains("燕云")) {
String href = result.attr("href");
String appId = href.replaceAll(".*\\/app\\/(\\d+).*", "$1");
System.out.println("找到匹配游戏: " + titleElement.text() + " (App ID: " + appId + ")");
return appId;
}
}
}
if (!results.isEmpty()) {
Element firstResult = results.first();
String href = firstResult.attr("href");
String appId = href.replaceAll(".*\\/app\\/(\\d+).*", "$1");
Element titleElement = firstResult.selectFirst("span.title");
System.out.println("返回第一个搜索结果: " + (titleElement != null ? titleElement.text() : "未知") + " (App ID: " + appId + ")");
return appId;
}
} catch (Exception e) {
System.err.println("网页搜索失败: " + e.getMessage());
}
return null;
}
/**
* 打印游戏信息 - 课堂知识点视图展示MVC中的View
*/
public void printGameInfo() {
System.out.println("\n========== Steam游戏信息 ==========");
System.out.println("游戏名称: " + appName);
System.out.println("价格: " + price);
System.out.println("发行日期: " + releaseDate);
System.out.println("好评率: " + reviewSummary);
System.out.println("游戏标签: " + String.join(", ", tags));
System.out.println("===================================");
}
/**
* 保存游戏数据到文件 - 课堂知识点文件IO
* @param filename 文件名
*/
public void saveToFile(String filename) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write("游戏名称: " + appName);
writer.newLine();
writer.write("价格: " + price);
writer.newLine();
writer.write("发行日期: " + releaseDate);
writer.newLine();
writer.write("好评率: " + reviewSummary);
writer.newLine();
writer.write("游戏标签: " + String.join(", ", tags));
writer.newLine();
System.out.println("✅ 游戏数据已保存到文件: " + filename);
} catch (IOException e) {
System.err.println("保存游戏数据失败: " + e.getMessage());
}
}
// getter方法 - 课堂知识点:封装的访问接口
public String getAppName() { return appName; }
public String getPrice() { return price; }
public String getReleaseDate() { return releaseDate; }
public String getReviewSummary() { return reviewSummary; }
public List<String> getTags() { return tags; }
/**
* 保存游戏数据到数据库 - 课堂知识点JDBC数据库持久化
*/
@Override
public void saveToDatabase() {
String sql =
"INSERT INTO games (name, price, discount, originalPrice, releaseDate, tags, reviewScore, crawlTime) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)";
try (Connection conn = DatabaseManager.getInstance().getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, appName);
pstmt.setString(2, price);
pstmt.setString(3, null); // discount
pstmt.setString(4, null); // originalPrice
pstmt.setString(5, releaseDate);
pstmt.setString(6, String.join(", ", tags));
pstmt.setString(7, reviewSummary);
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
System.out.println("✅ 游戏数据已保存到数据库");
}
} catch (SQLException e) {
System.err.println("❌ 保存游戏数据到数据库失败: " + e.getMessage());
}
}
}

30
project/src/main/java/com/example/TextSaveStrategy.java

@ -0,0 +1,30 @@
package com.example;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
/**
* 文本格式保存策略 - 策略模式实现
*/
public class TextSaveStrategy implements SaveStrategy {
@Override
public void save(Object data, String filename) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
if (data instanceof List) {
for (Object item : (List<?>) data) {
writer.write(item.toString());
writer.newLine();
}
} else {
writer.write(data.toString());
}
}
}
@Override
public String getStrategyName() {
return "文本格式";
}
}
Loading…
Cancel
Save