重构评论
This commit is contained in:
50
src/main/java/cn/van/business/model/pl/Comment.java
Normal file
50
src/main/java/cn/van/business/model/pl/Comment.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package cn.van.business.model.pl;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Leo
|
||||
* @version 1.0
|
||||
* @create 2025/6/4
|
||||
* @description:评论实体类
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "comments")
|
||||
@Data
|
||||
public class Comment {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "product_id", nullable = false, columnDefinition = "TEXT")
|
||||
private String productId;
|
||||
|
||||
@Column(name = "user_name", columnDefinition = "TEXT")
|
||||
private String userName;
|
||||
|
||||
@Lob
|
||||
@Column(name = "comment_text")
|
||||
private String commentText;
|
||||
|
||||
@Column(name = "comment_id", length = 64, unique = true)
|
||||
private String commentId;
|
||||
|
||||
@Column(name = "picture_urls", columnDefinition = "TEXT")
|
||||
private String pictureUrls;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "comment_date")
|
||||
private LocalDateTime commentDate;
|
||||
|
||||
@Column(name = "is_use")
|
||||
private Integer isUse;
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.van.business.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import cn.van.business.model.pl.Comment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leo
|
||||
* @version 1.0
|
||||
* @create 2025/5/2 19:16
|
||||
* @description:评论数据访问层接口
|
||||
*/
|
||||
@Repository
|
||||
public interface CommentRepository extends JpaRepository<Comment, Integer> {
|
||||
|
||||
List<Comment> findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(String productId, Integer isUse);
|
||||
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
package cn.van.business.util;
|
||||
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.van.business.model.jd.OrderRow;
|
||||
import cn.van.business.model.pl.Comment;
|
||||
import cn.van.business.repository.CommentRepository;
|
||||
import cn.van.business.repository.OrderRowRepository;
|
||||
import cn.van.business.util.ds.DeepSeekClientUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jd.open.api.sdk.DefaultJdClient;
|
||||
import com.jd.open.api.sdk.JdClient;
|
||||
@@ -78,7 +83,8 @@ public class JDUtil {
|
||||
private static final String ACCESS_TOKEN = "";
|
||||
private static final Logger logger = LoggerFactory.getLogger(JDUtil.class);
|
||||
private static final String INTERACTION_STATE_PREFIX = "interaction_state:";
|
||||
private static final String COMMENT_TEMPLATES = "comment_templates:";
|
||||
private static final String PRODUCT_TYPE_MAP_PREFIX = "product_type_map" ;
|
||||
private static HashMap<String, String> productTypeMap = new HashMap<>();
|
||||
private static final String COMMENT_TEMPLATES_DS = "我需要为我的商品模拟一些商品评论。你协助我生成3条评价内容,京东商品评价的风格,每条评价100字即可,不需要太浮夸,也不要太像ai生成,尽量模拟真实客户评价,不要提到以旧换新和国家补贴。我会为你提供其他真实用户的评论:";
|
||||
private static final long TIMEOUT_MINUTES = 2;
|
||||
private static final String WENAN_FANAN_LQD = "提供方法自己下单\n" + "全程都是自己的账号下单\n" + "标价就是下单的到手价\n" + "本人有耐心,会一步一步提供教程\n" + "以后有什么质量问题都是用自己的账号走京东售后\n" + "\n" + "更新\n" + "\n" + "用你自己的账号下单\n" + "官方店铺 提供方法自己下单\n" + "——————————————————————————————————————————\n" + "同行可长久合作,可提供神级家电线报\n" + "\n" + "配合家电线报可以自己下单,不用找代购和代下,订单和利润都掌握在自己手中。\n" + "\n" + "一次入会永久使用,包含家电帮,雷神价,韭菜帮,河南&湖南帮,各种暗号帮后返等内部独家家电线报\n" + "\n" + "JD采销采购不定时发放独家优惠券\n" + "\n" + "基本上你能看到的京东家电低价都是从这些渠道里面出来。\n" + "\n" + "2025年家电项目新方向,配合家电线报下单,秒省1K+。";
|
||||
@@ -105,6 +111,8 @@ public class JDUtil {
|
||||
final WXUtil wxUtil;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final OrderRowRepository orderRowRepository;
|
||||
private final CommentRepository commentRepository;
|
||||
|
||||
private final OrderUtil orderUtil;
|
||||
private final DeepSeekClientUtil deepSeekClientUtil;
|
||||
// 添加ObjectMapper来序列化和反序列化UserInteractionState
|
||||
@@ -114,12 +122,14 @@ public class JDUtil {
|
||||
|
||||
// 构造函数中注入StringRedisTemplate
|
||||
@Autowired
|
||||
public JDUtil(StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil, DeepSeekClientUtil deepSeekClientUtil) {
|
||||
public JDUtil(StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil, DeepSeekClientUtil deepSeekClientUtil, CommentRepository commentRepository) {
|
||||
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.orderRowRepository = orderRowRepository;
|
||||
this.wxUtil = wxUtil;
|
||||
this.orderUtil = orderUtil;
|
||||
this.deepSeekClientUtil = deepSeekClientUtil;
|
||||
this.commentRepository = commentRepository;
|
||||
}
|
||||
|
||||
private List<OrderRow> filterOrdersByDate(List<OrderRow> orderRows, int daysBack) {
|
||||
@@ -1577,7 +1587,7 @@ public class JDUtil {
|
||||
return;
|
||||
}
|
||||
String remark = "";
|
||||
if (!split[0].trim().replace("单", "").isEmpty()){
|
||||
if (!split[0].trim().replace("单", "").isEmpty()) {
|
||||
remark = split[0].trim().replace("{单}", "").replace("\n", "");
|
||||
}
|
||||
// 今天的日期
|
||||
@@ -1612,10 +1622,8 @@ public class JDUtil {
|
||||
for (int i = 0; i < num; i++) {
|
||||
int currentCount = startCount + i;
|
||||
String orderID = today + String.format("%03d", currentCount); // 格式化为三位数
|
||||
|
||||
// 每次使用原始模板进行替换,避免污染
|
||||
String currentTemp = WENAN_D.replace("{单的备注}", remark).replace("{单号}", orderID).replace("{分销标记}", split[1]).replace("{链接}", split[3]).replace("{地址}", split[5]).replace("{型号}", split[2]).replaceAll("[|]", "");
|
||||
|
||||
// 发送订单信息到微信
|
||||
wxUtil.sendTextMessage(fromWxid, currentTemp, 1, fromWxid, true);
|
||||
}
|
||||
@@ -1658,17 +1666,21 @@ public class JDUtil {
|
||||
}
|
||||
}
|
||||
|
||||
//getProductTypeMap 从redis中获取PRODUCT_TYPE_MAP_PREFIX
|
||||
public void getProductTypeMap() {
|
||||
String json = redisTemplate.opsForValue().get(PRODUCT_TYPE_MAP_PREFIX);
|
||||
productTypeMap = JSON.parseObject(json, new TypeReference<Map<String, String>>() {
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 处理生成评论流程中的用户交互
|
||||
*/
|
||||
private void handleCommentInteraction(String fromWxid, String message, UserInteractionState state) {
|
||||
String superOrder = "";
|
||||
try {
|
||||
String[] split = message.replaceAll("\r", "").split("\n");
|
||||
if (split.length != 0) {
|
||||
message = split[0].trim().replace("\n", "");
|
||||
for (int i = 1; i < split.length; i++) {
|
||||
superOrder += split[i] + " ";
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -1681,23 +1693,11 @@ public class JDUtil {
|
||||
if (!"commentTypeSelection".equals(state.getCurrentField())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据用户输入生成对应的评论
|
||||
switch (message) {
|
||||
case "1":
|
||||
generateComment(fromWxid, "1", superOrder);
|
||||
break;
|
||||
case "1+":
|
||||
generateComment(fromWxid, "1+", superOrder.replace("+", ""));
|
||||
break;
|
||||
case "2":
|
||||
generateComment(fromWxid, "2", superOrder);
|
||||
break;
|
||||
case "2+":
|
||||
generateComment(fromWxid, "2+", superOrder.replace("+", ""));
|
||||
break;
|
||||
default:
|
||||
wxUtil.sendTextMessage(fromWxid, "无效的选择,请回复 1 或 2", 1, fromWxid, false);
|
||||
getProductTypeMap();
|
||||
if (productTypeMap.containsKey(message)) {
|
||||
generateComment(fromWxid, message);
|
||||
} else {
|
||||
wxUtil.sendTextMessage(fromWxid, "无效的选择,请回复 1 或 2", 1, fromWxid, false);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -1713,42 +1713,87 @@ public class JDUtil {
|
||||
/**
|
||||
* 生成评论内容
|
||||
*/
|
||||
private void generateComment(String fromWxid, String productType, String superOrder) {
|
||||
if (productType.equals("1") || productType.equals("2")) {
|
||||
wxUtil.sendTextMessage(fromWxid, "DS生成中,等候过程请勿重复输入", 1, fromWxid, false);
|
||||
// 这里可以调用缓存或AI生成文案,目前先返回固定模板
|
||||
//用redis的 list存放评论模板,
|
||||
List<String> commentTemplates = redisTemplate.opsForList().range(COMMENT_TEMPLATES + productType, 0, -1);
|
||||
if (commentTemplates == null || commentTemplates.isEmpty()) {
|
||||
wxUtil.sendTextMessage(fromWxid, "暂无评论模板,请先添加评论模板", 1, fromWxid, false);
|
||||
private void generateComment(String fromWxid, String productType) {
|
||||
wxUtil.sendTextMessage(fromWxid, "已接收到评论生成指令,等候过程请勿重复输入", 1, fromWxid, false);
|
||||
|
||||
// 获取产品ID
|
||||
getProductTypeMap();
|
||||
String product_id = productTypeMap.get(productType);
|
||||
if (product_id == null || product_id.isEmpty()) {
|
||||
wxUtil.sendTextMessage(fromWxid, "缺失对应的SKUID", 1, fromWxid, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从数据库获取可用评论
|
||||
List<Comment> availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
||||
|
||||
Comment commentToUse = null;
|
||||
|
||||
if (!availableComments.isEmpty()) {
|
||||
// 随机选取一个未使用的评论
|
||||
Collections.shuffle(availableComments);
|
||||
commentToUse = availableComments.get(0);
|
||||
} else {
|
||||
// 没有本地评论,调用外部接口抓取
|
||||
try {
|
||||
String fetchUrl = "http://192.168.8.169:5000/fetch_comments?product_id=" + product_id;
|
||||
// 用hutool发起post请求
|
||||
HttpResponse response = HttpRequest.post(fetchUrl).execute();
|
||||
|
||||
logger.info("fetchUrl: {}", fetchUrl);
|
||||
// code = 200 表示成功,-200 表示失败
|
||||
if (response.getStatus() == 200) {
|
||||
// ✅ 关键修改:重新从数据库中查询,而不是使用内存中的 fetchedComments
|
||||
availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
||||
if (!availableComments.isEmpty()) {
|
||||
Collections.shuffle(availableComments);
|
||||
commentToUse = availableComments.get(0);
|
||||
}
|
||||
} else if (response.getStatus() == -200) {
|
||||
wxUtil.sendTextMessage(fromWxid, "暂时无法获取新的评论,请稍后再试", 1, fromWxid, false);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("调用外部接口获取评论失败", e);
|
||||
wxUtil.sendTextMessage(fromWxid, "调用外部接口获取评论失败", 1, fromWxid, false);
|
||||
return;
|
||||
}
|
||||
// 全部追加到成一个文本
|
||||
StringBuilder comment = new StringBuilder();
|
||||
int count = 0;
|
||||
for (String commentTemplate : commentTemplates) {
|
||||
count++;
|
||||
comment.append(count).append(commentTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
if (commentToUse == null) {
|
||||
wxUtil.sendTextMessage(fromWxid, "没有找到可用的评论数据", 1, fromWxid, false);
|
||||
return;
|
||||
}
|
||||
|
||||
String deepSeekResponse = "ds响应失败";
|
||||
try {
|
||||
deepSeekResponse = deepSeekClientUtil.getDeepSeekResponse(COMMENT_TEMPLATES_DS + comment);
|
||||
} catch (IOException e) {
|
||||
logger.error("生成评论异常 - 用户: {}, ", fromWxid, e);
|
||||
}
|
||||
wxUtil.sendTextMessage(fromWxid, deepSeekResponse, 1, fromWxid, false);
|
||||
// 重置状态
|
||||
resetState(fromWxid, loadOrCreateState(INTERACTION_STATE_PREFIX + fromWxid));
|
||||
} else {
|
||||
if (!superOrder.isEmpty()) {
|
||||
productType = productType.replace("+", "");
|
||||
// 追加评论模板到redis
|
||||
redisTemplate.opsForList().leftPush(COMMENT_TEMPLATES + productType, superOrder);
|
||||
wxUtil.sendTextMessage(fromWxid, "评论模板添加成功", 1, fromWxid, false);
|
||||
// 调用 DeepSeek 生成新的评论内容
|
||||
String deepSeekPrompt = COMMENT_TEMPLATES_DS + commentToUse.getCommentText();
|
||||
String deepSeekResponse;
|
||||
try {
|
||||
deepSeekResponse = deepSeekClientUtil.getDeepSeekResponse(deepSeekPrompt);
|
||||
} catch (IOException e) {
|
||||
logger.error("生成评论异常 - 用户: {}", fromWxid, e);
|
||||
wxUtil.sendTextMessage(fromWxid, "评论生成失败,请重试", 1, fromWxid, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送生成的评论文本
|
||||
wxUtil.sendTextMessage(fromWxid, deepSeekResponse, 1, fromWxid, false);
|
||||
|
||||
// 发送图片(如果有)
|
||||
String pictureUrls = commentToUse.getPictureUrls();
|
||||
if (pictureUrls != null && !pictureUrls.isEmpty()) {
|
||||
String[] urls = pictureUrls.split(",");
|
||||
for (String url : urls) {
|
||||
wxUtil.sendImageMessage(fromWxid, url.trim()); // 假设 sendImageMessage 支持 URL
|
||||
}
|
||||
}
|
||||
|
||||
// 更新评论状态为已使用
|
||||
commentToUse.setIsUse(1);
|
||||
commentRepository.save(commentToUse);
|
||||
|
||||
// 重置状态
|
||||
resetState(fromWxid, loadOrCreateState(INTERACTION_STATE_PREFIX + fromWxid));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package cn.van.business.util.ds.pl;
|
||||
|
||||
/**
|
||||
* @author Leo
|
||||
* @version 1.0
|
||||
* @create 2025/5/1 16:33
|
||||
* @description:
|
||||
*/
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FetchEquivalent {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FetchEquivalent.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
String url = "https://api.m.jd.com/client.action";
|
||||
|
||||
// 关键参数,可根据需求动态调整
|
||||
String appid = "pc-rate-qa";
|
||||
String functionId = "getCommentListPage";
|
||||
String spuId = "100104238904";
|
||||
String sku = "100104238904";
|
||||
int pageNum = 1;
|
||||
int pageSize = 10;
|
||||
String sortType = "5";
|
||||
String h5st = "20250501163137742;gggwzzg93mhaqp39;01a47;tk03wd91c1d5f18nmJkCbdT3fAvSVSRvhgBegKk_JURNSt4iorNQ5bdUGcosuVPknJ2RSpEjzcPmpjpW5mMLWtm95dd2;67e4ba5594c53473cec3c2ac9d0f2bb45a27256c33aa0cf0c0fc53f5c3e61f5f;5.1;1746088294742;ri_uxFOm22ciAuLmOGLm9aHWMusmk_Mm1irS9poUBhbgHFHQ3R4VHlsm0msSIlsmOGuj6mrm0mMTLhImOuMsCmsVJZIi3ebV1O7WLdrg_uLi3Kbi3ebg1e7h9iIhLJbi8msm0msSo94VMZ4RMusmk_MmIVrV7O7WKNbWJl4h5WIh8abi2aYh7qIhJZog2arg7WLmOGLm7pIRAp4WMusmk_siOGLm6aHWMusmk_Mm72ciAaLRPZoTFV4X5OImOGLm4lsmOGujMqnZMtLibd4i9VLmOGLmBxoVApISMusmk_Mm8iLTFRJmOGLmItHmOuMsC6nmOG_iOGLm9qbRMlsmOusmk_si9uMgMubi5lImOusmOGuj26sm0mMi9aHWMusmOuMsCmsQf16XNhai9Zph7WacMuMgM64TK1YW8lsmOusmk_siOGLm2aHWMusmOuMsCurm0m8h5lImOusmOGuj5irm0mMh5lImOusmOGuj_uMgMabRMlsmOusmk_siOGLm6aHWMusmOuMsCibh8uMgMibRMlsmOusmk_Mm12ciAuLmOGLm9aHWMusmOuMsCurm0m8U3lsmOusmk_chOGLm79ImOusmOGuj_uMgM_ImOusmOGuj_uMgMe4RMusmOuMsztMgMeITJdnQJlsmOGujxtsmkmMi9abW6a4iIRri4mohPdIUMuMgMmrSMusmOuMsztMgMunSMusmk_Mm6WrQOCrh42YUXt8g_2si9usZgt8S3xoVAJ4ZMuMgMqYR7lsmOG_Q;ecdd8239ff33baed5b5b19f9167325c045d13be878c9c3812dd9bcbc630d6b84;tenjKJKT-JoRL1YRI9MT-J4S8ZIZ61YVF94WCeHTJJoTL9cQKxIWCeYU_tXW";
|
||||
long t = System.currentTimeMillis();
|
||||
String uuid = "1744804395425546198998";
|
||||
|
||||
// 参数校验
|
||||
if (functionId == null || functionId.isEmpty() || appid == null || appid.isEmpty() || spuId == null || spuId.isEmpty() || sku == null || sku.isEmpty()) {
|
||||
logger.error("关键参数不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
String body = String.format("appid=%s&body={\"requestSource\":\"pc\",\"shopComment\":0,\"sameComment\":0,\"channel\":null,\"extInfo\":{\"isQzc\":\"0\",\"spuId\":\"%s\",\"commentRate\":\"1\",\"needTopAlbum\":\"1\",\"bbtf\":\"\",\"userGroupComment\":\"1\"},\"num\":\"10\",\"pictureCommentType\":\"A\",\"scval\":null,\"shadowMainSku\":\"0\",\"shopType\":\"0\",\"firstCommentGuid\":\"e341be12da46b787bf5cd21a76ae3295\",\"sku\":\"%s\",\"category\":\"737;13297;1301\",\"shieldCurrentComment\":\"1\",\"pageSize\":\"%d\",\"isFirstRequest\":false,\"style\":\"0\",\"isCurrentSku\":false,\"sortType\":\"%s\",\"tagId\":\"\",\"tagType\":\"\",\"type\":\"4\",\"pageNum\":\"%d\"}&client=pc&clientVersion=1.0.0&functionId=%s&h5st=%s&loginType=3&t=%d&uuid=%s",
|
||||
appid, spuId, sku, pageSize, sortType, pageNum, functionId, h5st, t, uuid);
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("accept", "application/json, text/plain, */*");
|
||||
headers.put("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||
headers.put("cache-control", "no-cache");
|
||||
headers.put("content-type", "application/x-www-form-urlencoded");
|
||||
headers.put("pragma", "no-cache");
|
||||
headers.put("priority", "u=1, i");
|
||||
headers.put("sec-ch-ua", "\"Chromium\";v=\"136\", \"Google Chrome\";v=\"136\", \"Not.A/Brand\";v=\"99\"");
|
||||
headers.put("sec-ch-ua-mobile", "?0");
|
||||
headers.put("sec-ch-ua-platform", "\"Windows\"");
|
||||
headers.put("sec-fetch-dest", "empty");
|
||||
headers.put("sec-fetch-mode", "cors");
|
||||
headers.put("sec-fetch-site", "same-site");
|
||||
headers.put("x-referer-page", "https://item.jd.com/100104238904.html");
|
||||
headers.put("x-rp-client", "h5_1.0.0");
|
||||
headers.put("referrer", "https://item.jd.com/");
|
||||
headers.put("referrerPolicy", "strict-origin-when-cross-origin");
|
||||
|
||||
try {
|
||||
URL obj = new URL(url);
|
||||
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
|
||||
|
||||
// 设置请求方法
|
||||
con.setRequestMethod("POST");
|
||||
|
||||
// 添加请求头
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
con.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// 允许输出流
|
||||
con.setDoOutput(true);
|
||||
|
||||
// 写入请求体
|
||||
try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) {
|
||||
wr.writeBytes(body);
|
||||
wr.flush();
|
||||
}
|
||||
|
||||
int responseCode = con.getResponseCode();
|
||||
logger.info("Response Code : " + responseCode);
|
||||
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
try (BufferedReader in = new BufferedReader(
|
||||
new InputStreamReader(con.getInputStream()))) {
|
||||
String inputLine;
|
||||
StringBuilder response = new StringBuilder();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
logger.info("Response Content : " + response.toString());
|
||||
}
|
||||
} else {
|
||||
try (BufferedReader errorReader = new BufferedReader(
|
||||
new InputStreamReader(con.getErrorStream()))) {
|
||||
String errorLine;
|
||||
StringBuilder errorResponse = new StringBuilder();
|
||||
while ((errorLine = errorReader.readLine()) != null) {
|
||||
errorResponse.append(errorLine);
|
||||
}
|
||||
logger.error("请求失败,响应码: " + responseCode + ",错误信息: " + errorResponse.toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("请求过程中发生异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package cn.van.business.util.ds.pl;
|
||||
|
||||
/**
|
||||
* @author Leo
|
||||
* @version 1.0
|
||||
* @create 2025/4/30 19:16
|
||||
* @description:
|
||||
*/
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JDItemReviewCrawler {
|
||||
private static final String API_URL = "https://api.jd.com/routerjson";
|
||||
private static final String APP_KEY = "98e21c89ae5610240ec3f5f575f86a59";
|
||||
private static final String APP_SECRET = "3dcb6b23a1104639ac433fd07adb6dfb";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String itemId = "100075270739"; // 商品ID
|
||||
String page = "1"; // 页码
|
||||
String result = getItemReviews(itemId, page);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
public static String getItemReviews(String itemId, String page) throws IOException {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("method", "jd.item.review.get");
|
||||
params.put("app_key", APP_KEY);
|
||||
params.put("v", "2.0");
|
||||
params.put("format", "json");
|
||||
params.put("sign_method", "md5");
|
||||
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
|
||||
params.put("num_iid", itemId);
|
||||
params.put("page", page);
|
||||
|
||||
String sign = generateSign(params, APP_SECRET);
|
||||
params.put("sign", sign);
|
||||
|
||||
String url = buildRequestUrl(params);
|
||||
return sendHttpGetRequest(url);
|
||||
}
|
||||
|
||||
private static String generateSign(Map<String, String> params, String appSecret) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
sb.append(entry.getKey()).append(entry.getValue());
|
||||
}
|
||||
sb.append(appSecret);
|
||||
return URLEncoder.encode(sb.toString(), StandardCharsets.UTF_8.name());
|
||||
}
|
||||
|
||||
private static String buildRequestUrl(Map<String, String> params) throws IOException {
|
||||
StringBuilder urlBuilder = new StringBuilder(API_URL);
|
||||
urlBuilder.append("?");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
urlBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
||||
}
|
||||
return urlBuilder.toString();
|
||||
}
|
||||
|
||||
private static String sendHttpGetRequest(String url) throws IOException {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
String result = httpClient.execute(httpGet, httpResponse -> EntityUtils.toString(httpResponse.getEntity()));
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode rootNode = objectMapper.readTree(result);
|
||||
return rootNode.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package cn.van.business.util.ds.pl;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
public class SimpleCommentScraper {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SimpleCommentScraper.class);
|
||||
private static final int SLEEP_TIME = 1000; // 休眠时间,单位:毫秒
|
||||
private static final String JD_PROD_PIC_SAVE_PATH = "jd_prod_pic";
|
||||
private static final int PAGE_RANGE = 1;
|
||||
private static final String BIG_PAG_PATH = "//img30.360buyimg.com/shaidan/s616x405_jfs/";
|
||||
private static final String TINY_PAG_PATH = "//img30.360buyimg.com/n0/s128x96_jfs/";
|
||||
|
||||
private static final Map<String, String> headers = new HashMap<>();
|
||||
|
||||
static {
|
||||
headers.put("cookie", "shshshfpa=0e6670b1-47be-d3a7-049d-3c9daa179153-1691582935; shshshfpx=0e6670b1-47be-d3a7-049d-3c9daa179153-1691582935; jcap_dvzw_fp=AMQNEJZc35A3KmQ7fLb-4FSnB2SbuA9auLr22ALve7Qyx9JarjpZCIHN8NIz4Bl_IB7RVdQ3LaO4xSrC0YsR3g==; __jdu=1744804395425546198998; whwswswws=; warehistory=\"10076121538879,10076121538879,10076121538879,\"; autoOpenApp_downCloseDate_autoOpenApp_autoPromptly=1744804996891_1; autoOpenApp_downCloseDate_jd_independent_coupon_openapp=1744948913486_1; pt_st=1_NWSLixpIv7w0rF2ENYK3Jf9y6xeCZi7by-s-cZ_os-zspi7V1bGhPcAZ3w02h0KlZuRNO4sgikpci-xjqrABXbJ-y524iEqm9uq2V0mkVH2RqLEesj0chjJtg-tcF6UBnUVWtVOqIaHczH4Ksux0Hlvk5oBhTziEhk7vR2wnLPciQ-W46Lld2YFdN_RldDpP-zVCY0pvepwAP8h8Ji4aNM-AueIvKCFFdqUYBq0K; autoOpenApp_downCloseDate_auto=1744948934067_1800000; unpl=JF8EAL1nNSttDxhRB0wEExJFTApdWwgASR4HO24GVgoKHFYNEgJMEBh7XlVdWRRKEh9vbxRUXlNLXA4ZASsSEHtdVV9eC04WAGdvNWRdWUpVBR8FGRUWe15Ublw4SxEGam4EUV9QT1EGHwYeFRBDVFxWVDhKJwNnYDVUXFhJXAAeBBwUGEleVG5tCXsUAWthBWRfWEpVDRsAExMTexw6Xl8KTREAa29IVFtdTl0EHgATFhVIWVBbWghDHgtnbjVVbVs; PCSYCityID=CN_440000_440400_0; __jdv=95931165%7Cbaidu%7C-%7Corganic%7Cnot%20set%7C1745683529792; areaId=19; 3AB9D23F7A4B3CSS=jdd03S5H5QMW2J75PKHW4AFEYH4WL3LTTPLHA33OM55RXWWZHHN54YMZYWQVIP72VO6IQPERN7263IEW3QS2MCHMDXKAHOAAAAAMWQXDYBRYAAAAAC66XIXT2PW7RNEX; TrackID=1HV4we0cR8GOSM8bCj15ATZWJRCkRxt_zC9NsV07_Rw_Hpjbg27hW-EOoflSpUAwRiCF64Gv0rs2svV4zySkqmD2IGl3twHr85Kl7cz6UD04; thor=BD7A0187F5FD056EAF98728DE029360B7A8A0541C8F77CB45F7B0147C979FC4B63263DC7060C0A0E024D245FED98C39E1965497BF8A52DD2B804B7A43DC8E62AAFCCC8375C8F1DAFD06EB149963A9C059E6A5A3794ED7091F3030BB35714F349356967BE1C5BCF323F577F074EBAB2972C8AFCEA4E692A2AB838FE098D171E77577D42ED90A56C77C4C41BED4EDEE82EBE2C73DD4186579625DD374F4EB7C081; light_key=AASBKE7rOxgWQziEhC_QY6yaHACwW-20kQ1Dcwl6grswVJjfXVytbgQhtenpZa5NPqZ_cGo0; pinId=0M8ocQ7iEWaVVjS3qk-0cbV9-x-f3wj7; pin=jd_4963d6886b0f2; unick=jd_136704tjkx; ceshi3.com=201; _tp=3CK%2BmBvk2ojeH2NRIdd5njR3CccoiLr28y8aBChbZuU%3D; _pst=jd_4963d6886b0f2; mail_times=1%2C1; cn=37; umc_count=1; source=PC; platform=pc; 3AB9D23F7A4B3C9B=S5H5QMW2J75PKHW4AFEYH4WL3LTTPLHA33OM55RXWWZHHN54YMZYWQVIP72VO6IQPERN7263IEW3QS2MCHMDXKAHOA; token=ed9f1fe0e7b340ca631c803a236bea46,3,970000; jsavif=1; __jda=181111935.1744804395425546198998.1744804395.1745683529.1746001166.13; __jdb=181111935.5.1744804395425546198998|13.1746001166; __jdc=181111935; flash=3_y4qAJzSRoYzofvat3DYn_5Klmf98wlnb_Rfv-1RxzFYoUuB2ue9FJsma9Kg_PtrLc8D00wMqYgHYuvmQMG1DLgI0h-0u-g9CY33yr4S_VQWoWVsGCMQFF-X_lYW455Y34pydLsc45q6G13Fs3KZzzRMoONO9VhAnXvXn5HyJW36pUpWl4XA0wV**; shshshfpb=BApXSqdjBhvNAkN6htfk9E7ZUVeoR0SCJB9MZLhp89xJ1MkNSh4O2; ipLoc-djd=19-1609-41653-0; sdtoken=AAbEsBpEIOVjqTAKCQtvQu17hnR3nn92tx0xIv0BMmdNKx5EOYU9EjhDQyt4HcTnNd06jjQqdJS-xPUQBOfQ5bB2jxUBytH6hCZmvHMLyZ5w55ox8Q80a0chH_sopEs");
|
||||
|
||||
headers.put("referer", "https://item.jd.com/");
|
||||
|
||||
headers.put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 Edg/91.0.864.37");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
getJDProdComment("10111812014432");
|
||||
}
|
||||
|
||||
public static void getJDProdComment(String prod_skuId) {
|
||||
for (int i = 0; i < PAGE_RANGE; i++) {
|
||||
long t = System.currentTimeMillis();
|
||||
String comments_url = "https://api.m.jd.com/?appid=item-v3&functionId=pc_club_productPageComments&client=pc&clientVersion=1.0.0"+"&t="+t+"9&loginType=3&uuid=181111935.16953916127351737253418.1695391613.1705847111.1705852812.35&productId=" + prod_skuId + "&score=0&sortType=5&page=" + i + "&pageSize=10&isShadowSku=0&fold=1&bbtf=&shield=";
|
||||
try {
|
||||
String response = sendRequest(comments_url);
|
||||
if (response != null) {
|
||||
JSONObject json_data = JSONObject.parseObject(response);
|
||||
System.out.println(json_data);
|
||||
if (i == 0) {
|
||||
JSONObject productCommentSummary = json_data.getJSONObject("productCommentSummary");
|
||||
System.out.println(prod_skuId + " " + productCommentSummary);
|
||||
JSONArray hotCommentTagStatistics = json_data.getJSONArray("hotCommentTagStatistics");
|
||||
System.out.println(prod_skuId + " " + hotCommentTagStatistics);
|
||||
}
|
||||
JSONArray comments = json_data.getJSONArray("comments");
|
||||
if (comments != null && comments.size() > 0) {
|
||||
for (int j = 0; j < comments.size(); j++) {
|
||||
JSONObject comment = comments.getJSONObject(j);
|
||||
String prod_comment_guid = comment.getString("guid");
|
||||
String prod_comment_content = comment.getString("content");
|
||||
String prod_comment = comment.toString();
|
||||
JSONArray images = comment.getJSONArray("images");
|
||||
if (images != null && images.size() > 0) {
|
||||
for (int k = 0; k < images.size(); k++) {
|
||||
JSONObject image = images.getJSONObject(k);
|
||||
String imgUrl = image.getString("imgUrl");
|
||||
logger.info("图片 {}", imgUrl);
|
||||
|
||||
Thread.sleep(SLEEP_TIME);
|
||||
}
|
||||
}
|
||||
System.out.println(prod_comment_guid + " " + prod_skuId + " " + prod_comment_content + " " + prod_comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
Thread.sleep(SLEEP_TIME);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String sendRequest(String urlStr) throws IOException {
|
||||
URL url = new URL(urlStr);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
reader.close();
|
||||
return response.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void downloadfile(String downloadurl) {
|
||||
logger.info("downloadurl:{}", downloadurl);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
headers.put("cookie", "shshshfpa=0e6670b1-47be-d3a7-049d-3c9daa179153-1691582935; shshshfpx=0e6670b1-47be-d3a7-049d-3c9daa179153-1691582935; jcap_dvzw_fp=AMQNEJZc35A3KmQ7fLb-4FSnB2SbuA9auLr22ALve7Qyx9JarjpZCIHN8NIz4Bl_IB7RVdQ3LaO4xSrC0YsR3g==; __jdu=1744804395425546198998; whwswswws=; warehistory=\"10076121538879,10076121538879,10076121538879,\"; autoOpenApp_downCloseDate_autoOpenApp_autoPromptly=1744804996891_1; autoOpenApp_downCloseDate_jd_independent_coupon_openapp=1744948913486_1; pt_st=1_NWSLixpIv7w0rF2ENYK3Jf9y6xeCZi7by-s-cZ_os-zspi7V1bGhPcAZ3w02h0KlZuRNO4sgikpci-xjqrABXbJ-y524iEqm9uq2V0mkVH2RqLEesj0chjJtg-tcF6UBnUVWtVOqIaHczH4Ksux0Hlvk5oBhTziEhk7vR2wnLPciQ-W46Lld2YFdN_RldDpP-zVCY0pvepwAP8h8Ji4aNM-AueIvKCFFdqUYBq0K; autoOpenApp_downCloseDate_auto=1744948934067_1800000; unpl=JF8EAL1nNSttDxhRB0wEExJFTApdWwgASR4HO24GVgoKHFYNEgJMEBh7XlVdWRRKEh9vbxRUXlNLXA4ZASsSEHtdVV9eC04WAGdvNWRdWUpVBR8FGRUWe15Ublw4SxEGam4EUV9QT1EGHwYeFRBDVFxWVDhKJwNnYDVUXFhJXAAeBBwUGEleVG5tCXsUAWthBWRfWEpVDRsAExMTexw6Xl8KTREAa29IVFtdTl0EHgATFhVIWVBbWghDHgtnbjVVbVs; PCSYCityID=CN_440000_440400_0; __jdv=95931165%7Cbaidu%7C-%7Corganic%7Cnot%20set%7C1745683529792; areaId=19; 3AB9D23F7A4B3CSS=jdd03S5H5QMW2J75PKHW4AFEYH4WL3LTTPLHA33OM55RXWWZHHN54YMZYWQVIP72VO6IQPERN7263IEW3QS2MCHMDXKAHOAAAAAMWQXDYBRYAAAAAC66XIXT2PW7RNEX; TrackID=1HV4we0cR8GOSM8bCj15ATZWJRCkRxt_zC9NsV07_Rw_Hpjbg27hW-EOoflSpUAwRiCF64Gv0rs2svV4zySkqmD2IGl3twHr85Kl7cz6UD04; thor=BD7A0187F5FD056EAF98728DE029360B7A8A0541C8F77CB45F7B0147C979FC4B63263DC7060C0A0E024D245FED98C39E1965497BF8A52DD2B804B7A43DC8E62AAFCCC8375C8F1DAFD06EB149963A9C059E6A5A3794ED7091F3030BB35714F349356967BE1C5BCF323F577F074EBAB2972C8AFCEA4E692A2AB838FE098D171E77577D42ED90A56C77C4C41BED4EDEE82EBE2C73DD4186579625DD374F4EB7C081; light_key=AASBKE7rOxgWQziEhC_QY6yaHACwW-20kQ1Dcwl6grswVJjfXVytbgQhtenpZa5NPqZ_cGo0; pinId=0M8ocQ7iEWaVVjS3qk-0cbV9-x-f3wj7; pin=jd_4963d6886b0f2; unick=jd_136704tjkx; ceshi3.com=201; _tp=3CK%2BmBvk2ojeH2NRIdd5njR3CccoiLr28y8aBChbZuU%3D; _pst=jd_4963d6886b0f2; mail_times=1%2C1; cn=37; umc_count=1; source=PC; platform=pc; 3AB9D23F7A4B3C9B=S5H5QMW2J75PKHW4AFEYH4WL3LTTPLHA33OM55RXWWZHHN54YMZYWQVIP72VO6IQPERN7263IEW3QS2MCHMDXKAHOA; token=ed9f1fe0e7b340ca631c803a236bea46,3,970000; jsavif=1; __jda=181111935.1744804395425546198998.1744804395.1745683529.1746001166.13; __jdb=181111935.5.1744804395425546198998|13.1746001166; __jdc=181111935; flash=3_y4qAJzSRoYzofvat3DYn_5Klmf98wlnb_Rfv-1RxzFYoUuB2ue9FJsma9Kg_PtrLc8D00wMqYgHYuvmQMG1DLgI0h-0u-g9CY33yr4S_VQWoWVsGCMQFF-X_lYW455Y34pydLsc45q6G13Fs3KZzzRMoONO9VhAnXvXn5HyJW36pUpWl4XA0wV**; shshshfpb=BApXSqdjBhvNAkN6htfk9E7ZUVeoR0SCJB9MZLhp89xJ1MkNSh4O2; ipLoc-djd=19-1609-41653-0; sdtoken=AAbEsBpEIOVjqTAKCQtvQu17hnR3nn92tx0xIv0BMmdNKx5EOYU9EjhDQyt4HcTnNd06jjQqdJS-xPUQBOfQ5bB2jxUBytH6hCZmvHMLyZ5w55ox8Q80a0chH_sopEs");
|
||||
Reference in New Issue
Block a user