You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
5.8 KiB

package com.music.strategy; // 注意你的包名是 com.music
import com.google.gson.*;
import com.music.exception.NetworkException;
import com.music.exception.ParseException;
import com.music.model.Song;
import com.music.util.RetryUtils;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class NetEaseStrategy implements CrawlStrategy {
private static final Logger logger = LoggerFactory.getLogger(NetEaseStrategy.class);
@Override
public boolean supports(String platform) {
return "netease".equalsIgnoreCase(platform);
}
@Override
public List<Song> crawl(int limit) throws NetworkException, ParseException {
logger.info("开始爬取网易云热歌榜,限制 {} 首", limit);
List<Song> songs = new ArrayList<>();
try {
// 使用更稳定的接口:歌单详情 API (歌单 ID: 3778678 是官方热歌榜)
String jsonData = RetryUtils.retry(() -> fetchJsonFromUrl(), 3, 1000);
// 调试:打印前500字符,查看返回结构
if (jsonData.length() > 500) {
logger.debug("API返回预览: {}", jsonData.substring(0, 500));
} else {
logger.debug("API返回: {}", jsonData);
}
JsonObject root = JsonParser.parseString(jsonData).getAsJsonObject();
int code = root.get("code").getAsInt();
if (code != 200) {
throw new ParseException("网易云API返回错误码: " + code);
}
// 新版网易云API返回的数据在 "playlist" -> "tracks" 下
JsonObject playlist = root.getAsJsonObject("playlist");
if (playlist == null) {
// 兼容旧版结构:直接 result.tracks
JsonObject result = root.getAsJsonObject("result");
if (result == null) {
throw new ParseException("JSON中既没有 playlist 也没有 result 字段,请检查API返回");
}
parseTracks(result.getAsJsonArray("tracks"), songs, limit);
} else {
JsonArray tracks = playlist.getAsJsonArray("tracks");
parseTracks(tracks, songs, limit);
}
logger.info("网易云爬取完成,共 {} 首", songs.size());
return songs;
} catch (Exception e) {
logger.error("网易云爬取失败", e);
if (e instanceof NetworkException) throw (NetworkException) e;
if (e instanceof ParseException) throw (ParseException) e;
throw new ParseException("解析网易云数据失败: " + e.getMessage(), e);
}
}
private void parseTracks(JsonArray tracks, List<Song> songs, int limit) {
if (tracks == null) {
logger.warn("tracks 数组为空");
return;
}
int count = 0;
for (int i = 0; i < tracks.size() && count < limit; i++) {
JsonObject track = tracks.get(i).getAsJsonObject();
Song song = new Song();
song.setPlatform("netease");
song.setRank(i + 1);
song.setChartType("热歌榜");
song.setName(track.get("name").getAsString());
// 歌手
if (track.has("artists")) {
JsonArray artists = track.getAsJsonArray("artists");
StringBuilder sb = new StringBuilder();
for (int j = 0; j < artists.size(); j++) {
sb.append(artists.get(j).getAsJsonObject().get("name").getAsString());
if (j < artists.size() - 1) sb.append("/");
}
song.setArtist(sb.toString());
} else {
song.setArtist("未知歌手");
}
// 专辑
if (track.has("album")) {
JsonObject album = track.getAsJsonObject("album");
song.setAlbum(album.has("name") ? album.get("name").getAsString() : "未知专辑");
} else {
song.setAlbum("未知专辑");
}
// 时长(毫秒转秒)
song.setDuration(track.get("duration").getAsInt() / 1000);
songs.add(song);
count++;
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
}
}
private String fetchJsonFromUrl() throws Exception {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(chain -> {
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.header("Cookie", "os=pc; appver=2.0.2;")
.header("Referer", "https://music.163.com/")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
})
.build();
// 使用官方热歌榜歌单 ID: 3778678 的详情接口
String url = "https://music.163.com/api/playlist/detail?id=3778678";
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new NetworkException(url, "HTTP " + response.code(), null);
}
return response.body().string();
}
}
}