Browse Source

添加 'W3'

pull/2/head
pangyaxuan 4 weeks ago
parent
commit
5c46764251
  1. 179
      W3

179
W3

@ -0,0 +1,179 @@
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 网易云音乐热门评论爬虫
*/
public class NeteaseCommentCrawler {
// 网易云固定加密密钥
private static final String FIRST_KEY = "0CoJUm6Qyw8W8jud";
private static final String IV = "0102030405060708";
// 固定encSecKey(适配热门评论接口)
private static final String ENC_SEC_KEY = "00e0b50bcfcc08f1f8d6a8a9a0c96969b6f928a5069f07aa09f6062446f4224f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f42f4";
static {
// 注册BouncyCastle加密提供者
Security.addProvider(new BouncyCastleProvider());
}
/**
* AES加密(CBC模式,PKCS7填充)
*/
private static String aesEncrypt(String content, String key) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(pad(content).getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* PKCS7补位
*/
private static String pad(String s) {
int blockSize = 16;
int padCount = blockSize - (s.length() % blockSize);
StringBuilder sb = new StringBuilder(s);
for (int i = 0; i < padCount; i++) {
sb.append((char) padCount);
}
return sb.toString();
}
/**
* 生成加密参数(params + encSecKey)
*/
private static Map<String, String> getEncryptedParams(String songId) throws Exception {
// 构造原始请求参数
JSONObject originParam = new JSONObject();
originParam.put("rid", "R_SO_4_" + songId);
originParam.put("offset", 0);
originParam.put("total", true);
originParam.put("limit", 20);
originParam.put("csrf_token", "");
// 生成16位随机密钥
String secondKey = RandomStringUtils.randomAlphanumeric(16);
// 双重AES加密
String params = aesEncrypt(aesEncrypt(originParam.toJSONString(), FIRST_KEY), secondKey);
Map<String, String> result = new HashMap<>();
result.put("params", params);
result.put("encSecKey", ENC_SEC_KEY);
return result;
}
/**
* 获取热门评论
*/
public static JSONArray getHotComments(String songId) {
String url = "https://music.163.com/weapi/v1/resource/comments/R_SO_4_" + songId + "?csrf_token=";
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
// 1. 构造POST请求
HttpPost httpPost = new HttpPost(url);
// 2. 设置请求头
httpPost.setHeader("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");
httpPost.setHeader("Referer", "https://music.163.com/song?id=" + songId);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
// 3. 生成加密参数并构造请求体
Map<String, String> encryptedParams = getEncryptedParams(songId);
String requestBody = "params=" + encryptedParams.get("params") + "&encSecKey=" + encryptedParams.get("encSecKey");
httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
// 4. 发送请求并解析响应
CloseableHttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
String responseStr = EntityUtils.toString(entity, StandardCharsets.UTF_8);
JSONObject responseJson = JSON.parseObject(responseStr);
// 5. 返回热门评论数组
return responseJson.getJSONArray("hotComments");
} catch (Exception e) {
System.err.println("爬取失败:" + e.getMessage());
e.printStackTrace();
} finally {
try {
httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return new JSONArray();
}
/**
* 格式化输出评论 + 保存到CSV
*/
public static void printAndSaveComments(JSONArray hotComments) {
// 1. 格式化打印
System.out.println("===== 网易云热门评论 =====");
for (int i = 0; i < hotComments.size(); i++) {
JSONObject comment = hotComments.getJSONObject(i);
JSONObject user = comment.getJSONObject("user");
String nickname = user.getString("nickname");
String content = comment.getString("content").replace("\n", " ");
int likedCount = comment.getInteger("likedCount");
String timeStr = comment.getString("timeStr");
System.out.printf("%d. %s:%s(%d赞)- %s%n",
i + 1, nickname, content, likedCount, timeStr);
}
// 2. 保存到CSV(可选)
/*
try (FileWriter writer = new FileWriter("netease_hot_comments.csv", StandardCharsets.UTF_8)) {
// 写入表头
writer.write("序号,用户,评论,点赞数,时间\n");
// 写入评论数据
for (int i = 0; i < hotComments.size(); i++) {
JSONObject comment = hotComments.getJSONObject(i);
JSONObject user = comment.getJSONObject("user");
String nickname = user.getString("nickname");
String content = comment.getString("content").replace("\n", " ").replace(",", ",");
int likedCount = comment.getInteger("likedCount");
String timeStr = comment.getString("timeStr");
writer.write(String.format("%d,%s,%s,%d,%s%n",
i + 1, nickname, content, likedCount, timeStr));
}
System.out.println("评论已保存到 netease_hot_comments.csv");
} catch (Exception e) {
System.err.println("保存CSV失败:" + e.getMessage());
}
*/
}
// 主方法:测试爬取《晴天》(ID:186016)热门评论
public static void main(String[] args) {
String songId = "186016"; // 晴天的歌曲ID
JSONArray hotComments = getHotComments(songId);
if (!hotComments.isEmpty()) {
printAndSaveComments(hotComments);
} else {
System.out.println("未获取到评论,请检查歌曲ID或网络");
}
}
}
Loading…
Cancel
Save