Java 解析京东商品评论 API JSON 数据:异常处理完整方案
一、核心异常类型与处理原则
1. 需重点处理的 5 类异常
| 异常场景 | 对应 Java 异常 / 错误码 | 影响范围 |
|---|---|---|
| 网络问题(超时、断网) | ConnectTimeoutException、SocketTimeoutException | 请求直接失败 |
| API 授权 / 参数错误 | 接口返回非 200 码(如 401 权限失效、400 参数错误) | 无有效 JSON 响应 |
| JSON 格式异常 | JsonParseException(响应非标准 JSON) | 解析失败 |
| JSON 字段缺失 / 类型不匹配 | NullPointerException、JsonMappingException | 部分数据提取失败 |
| 数据为空(无评论) | 接口返回 200,但 comments 为空数组 | 业务逻辑无数据可用 |
2. 处理原则
「分层捕获」:HTTP 请求、JSON 解析、数据使用分开捕获异常,不混用;
「优雅降级」:某条评论解析失败不影响整体流程,无评论时给出明确提示;
「日志留痕」:所有异常记录关键信息(如请求参数、错误栈),便于排查;
「合规兜底」:解析失败时不泄露敏感信息,不强制使用异常数据。
二、完整实现代码(含异常处理)
1. 依赖准备(Maven)
<!-- HTTP 请求库 --><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version></dependency><!-- JSON 解析库 --><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version></dependency><!-- 日志库(记录异常) --><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version></dependency>
2. 核心代码(含异常处理)
import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.utils.URIBuilder;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.net.URISyntaxException;import java.util.ArrayList;import java.util.List;// 评论数据模型(用于结构化存储解析结果)class JdComment {
private String commentId; // 评论ID
private String userNickname;// 脱敏昵称
private int score; // 评分
private String content; // 评论内容
private String productAttr; // 购买规格
private String commentTime; // 评论时间
// getter/setter 省略(按需生成)}public class JdCommentParserWithException {
// 日志记录(关键异常信息)
private static final Logger logger = LoggerFactory.getLogger(JdCommentParserWithException.class);
// JSON 解析器(全局单例,避免重复创建)
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// 超时配置(避免无限等待)
private static final int CONNECT_TIMEOUT = 5000; // 连接超时:5秒
private static final int SOCKET_TIMEOUT = 10000; // 响应超时:10秒
public static void main(String[] args) {
// 合法授权参数(替换为你的真实信息)
String appKey = "你的AppKey";
String accessToken = "你的AccessToken";
String productId = "目标商品ID";
// 调用核心方法,捕获顶层异常
try {
List<JdComment> commentList = parseJdComments(appKey, accessToken, productId);
logger.info("解析完成,共获取 {} 条有效评论", commentList.size());
// 后续业务处理(如存储、统计)
} catch (Exception e) {
// 顶层异常兜底,不影响程序退出
logger.error("京东评论解析全流程失败:{}", e.getMessage(), e);
System.out.println("评论解析失败,请稍后重试");
}
}
/**
* 核心方法:调用 API + 解析 JSON + 异常处理
*/
public static List<JdComment> parseJdComments(String appKey, String accessToken, String productId) throws Exception {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
// 1. 构建 HTTP 客户端(配置超时)
httpClient = HttpClients.custom()
.setConnectionTimeToLive(CONNECT_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS)
.setDefaultSocketConfig(org.apache.http.config.SocketConfig.custom()
.setSoTimeout(SOCKET_TIMEOUT)
.build())
.build();
// 2. 构建请求 URL(避免参数拼接错误)
URIBuilder uriBuilder = new URIBuilder("https://api.jd.com/routerjson");
uriBuilder.addParameter("app_key", appKey)
.addParameter("access_token", accessToken)
.addParameter("method", "jingdong.comment.list.get")
.addParameter("productId", productId)
.addParameter("page", "1")
.addParameter("pageSize", "20");
HttpGet httpGet = new HttpGet(uriBuilder.build());
logger.info("发送评论 API 请求:{}", httpGet.getURI());
// 3. 发送 HTTP 请求(捕获网络相关异常)
response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
// 4. 处理 API 响应状态码(非 200 均视为异常)
if (statusCode != 200) {
String errorMsg = String.format("API 调用失败,状态码:%d,响应:%s",
statusCode, EntityUtils.toString(response.getEntity(), "UTF-8"));
logger.error(errorMsg);
throw new RuntimeException(errorMsg); // 抛出业务异常,上层处理
}
// 5. 读取响应数据
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(entity, "UTF-8");
EntityUtils.consume(entity); // 释放资源
// 6. 解析 JSON 数据(单独捕获 JSON 相关异常)
return parseJsonToComments(jsonStr);
} catch (URISyntaxException e) {
// URL 构建异常(参数非法,如含特殊字符)
String errorMsg = "请求 URL 构建失败,检查参数是否合法";
logger.error(errorMsg, e);
throw new Exception(errorMsg, e);
} catch (org.apache.http.conn.ConnectTimeoutException e) {
// 连接超时(网络问题或 API 服务不可用)
String errorMsg = "API 连接超时,请检查网络或稍后重试";
logger.error(errorMsg, e);
throw new Exception(errorMsg, e);
} catch (org.apache.http.conn.HttpHostConnectException e) {
// 无法连接主机(API 地址错误或服务下线)
String errorMsg = "无法连接京东 API 服务器,请检查地址是否正确";
logger.error(errorMsg, e);
throw new Exception(errorMsg, e);
} catch (JsonProcessingException e) {
// JSON 解析异常(响应格式非法)
String errorMsg = String.format("JSON 解析失败,原始响应:%s", jsonStr);
logger.error(errorMsg, e);
throw new Exception(errorMsg, e);
} catch (Exception e) {
// 其他未捕获异常
String errorMsg = "HTTP 请求阶段异常";
logger.error(errorMsg, e);
throw new Exception(errorMsg, e);
} finally {
// 7. 释放资源(必须在 finally 中执行,避免资源泄露)
if (response != null) {
try {
response.close();
} catch (Exception e) {
logger.warn("关闭响应流失败", e);
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (Exception e) {
logger.warn("关闭 HTTP 客户端失败", e);
}
}
}
}
/**
* 解析 JSON 字符串为评论列表(处理字段缺失、类型不匹配)
*/
private static List<JdComment> parseJsonToComments(String jsonStr) throws JsonProcessingException {
List<JdComment> commentList = new ArrayList<>();
JsonNode rootNode = OBJECT_MAPPER.readTree(jsonStr);
// 校验 JSON 根节点核心字段(避免空指针)
if (!rootNode.has("code") || !rootNode.has("data")) {
logger.error("JSON 响应缺少核心字段,原始数据:{}", jsonStr);
return commentList; // 优雅降级,返回空列表
}
// 检查 API 业务状态码(部分接口 200 但 code 非 0/200)
int businessCode = rootNode.get("code").asInt(-1);
if (businessCode != 200) {
String businessMsg = rootNode.has("message") ? rootNode.get("message").asText("未知错误") : "未知错误";
logger.error("API 业务失败,错误码:{},错误信息:{}", businessCode, businessMsg);
return commentList;
}
// 提取评论列表(字段可能不存在,需判断)
JsonNode dataNode = rootNode.get("data");
if (!dataNode.has("comments")) {
logger.warn("JSON 响应中无评论数据");
return commentList;
}
JsonNode commentsNode = dataNode.get("comments");
if (!commentsNode.isArray()) {
logger.error("comments 字段不是数组,原始数据:{}", jsonStr);
return commentList;
}
// 遍历每条评论,处理字段缺失/类型不匹配
for (JsonNode commentNode : commentsNode) {
JdComment comment = new JdComment();
try {
// 字段提取:使用默认值避免空指针,类型转换失败时捕获异常
comment.setCommentId(commentNode.has("commentId") ? commentNode.get("commentId").asText("") : "");
comment.setUserNickname(commentNode.has("userNickname") ? commentNode.get("userNickname").asText("匿名用户") : "匿名用户");
// 评分:默认 0 分(字段不存在或非数字时)
comment.setScore(commentNode.has("score") && commentNode.get("score").isInt() ? commentNode.get("score").asInt() : 0);
comment.setContent(commentNode.has("content") ? commentNode.get("content").asText("") : "");
comment.setProductAttr(commentNode.has("productAttr") ? commentNode.get("productAttr").asText("未知规格") : "未知规格");
comment.setCommentTime(commentNode.has("commentTime") ? commentNode.get("commentTime").asText("") : "");
commentList.add(comment);
} catch (Exception e) {
// 单条评论解析失败,不影响整体,仅记录日志
logger.warn("单条评论解析失败,原始数据:{},错误:{}", commentNode.toString(), e.getMessage(), e);
}
}
return commentList;
}}三、关键异常处理细节说明
1. 网络相关异常:避免无限等待
配置「连接超时」和「响应超时」,防止程序因网络问题阻塞;
单独捕获
ConnectTimeoutException(连接超时)、HttpHostConnectException(无法连接),给出明确的用户提示。
2. API 响应状态码:非 200 均处理
即使 HTTP 状态码是 200,仍需检查 JSON 中的
code字段(部分接口业务失败时 HTTP 状态码仍为 200);记录完整的错误响应(如 401 时的权限提示),便于排查问题(如 Access Token 过期)。
3. JSON 解析:字段容错 + 单条失败不影响整体
所有字段提取前先判断「是否存在」+「类型匹配」(如
score必须是数字),避免NullPointerException;单条评论解析失败时,仅记录日志并跳过,不中断整个列表的解析(如某条评论缺失
productAttr字段,不影响其他评论)。
4. 资源释放:finally 中关闭流
HTTP 客户端、响应流必须在
finally中关闭,避免资源泄露;关闭资源时单独捕获异常,不影响其他逻辑执行。
5. 日志记录:便于排查
记录关键信息:请求 URL、状态码、原始 JSON 响应、异常栈;
区分日志级别:错误(如 API 授权失败)、警告(如单条评论解析失败)、信息(如请求成功)。
四、额外优化建议
重试机制:对网络超时、503(服务暂时不可用)等异常,可添加重试逻辑(使用
spring-retry等框架),避免偶发问题;参数校验:调用
parseJdComments前,校验appKey、productId等参数非空,减少无效请求;限流控制:按京东 API 约定的 QPS 调用,避免因频率超限导致 429 异常;
数据脱敏:若需打印评论内容,对用户昵称、评论中的敏感信息(如手机号)进行脱敏,符合隐私合规要求。
总结
网络层:处理超时、连接失败,配置合理超时时间;
API 层:校验 HTTP 状态码和业务状态码,记录错误响应;
JSON 层:字段容错,单条解析失败不影响整体;
资源层:确保流关闭,避免泄露。