0


spring boot基于selenium批量订阅网易云音乐歌曲

  1. 需求来源 之前用的qq音乐听歌,收藏的歌单只能分享不能导出到其他平台(网易云),如果收藏的歌单歌曲太多就不适用手动复制添加,故本需求因此产生。QQ音乐网页端不能复制歌曲信息,要想拿到收藏夹歌曲信息,本人采用的方式是将歌曲下载下来,通过读取本地文件夹的文件名称获取歌曲信息。本打算用批量提交请求的方式操作,但是一方面由于网易云音乐请求数据都已加密(解密教程),另一方面拿到加密算法后,批量提交不返回状态码,应该是服务器有反爬虫机制,我尝试设置请求头和cookie无果。故最后采用selenium模拟浏览器点击实现。代码测试时间为2024年6月7日,后续网易云页面元素定位可能发生变化。

  2. 主要流程

  3. 引入相关依赖(spring boot 版本2.6.2) <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.141.59</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>

  4. WebDriver驱动下载1. 114及之前的版本2. 116版本3. 117/118/119版本

  5. webdriver配置import org.openqa.selenium.Dimension;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.File;@Configurationpublic class ChromeDriverConfig { /** * 浏览器是否展示 */ @Value("${selenium.ISSHOW}") private Boolean ISSHOW; @Bean public WebDriver getWebDriver(){ WebDriver driver = null; ChromeOptions options = new ChromeOptions(); // 隐藏chrome爬数据 if (!ISSHOW) { options.addArguments("--headless"); } File file = new File("chromedriver.exe"); System.setProperty("webdriver.chrome.driver", file.getAbsolutePath()); driver = new ChromeDriver(options); Dimension dimension = new Dimension(1920, 1080); driver.manage().window().setSize(dimension); return driver; }}

  6. 导入歌单信息(通过读取文件夹的方式)import java.io.IOException;import java.nio.file.*;import java.nio.file.attribute.BasicFileAttributes;import java.util.ArrayList;import java.util.List;public class musicNameRead { static class musicOb { private String name; private String singer; private boolean isVip; public musicOb( String singer, String name,boolean isVip) { this.name = name; this.singer = singer; this.isVip = isVip; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSinger() { return singer; } public void setSinger(String singer) { this.singer = singer; } public boolean isVip() { return isVip; } public void setVip(boolean vip) { isVip = vip; } @Override public String toString() { return "musicOb{" + "name='" + name + '\'' + ", singer='" + singer + '\'' + ", isVip=" + isVip + '}'; } } public static List<musicOb> getMusicInfo(){ Path path = Paths.get("D:\\song"); final int[] i = {0}; List<musicOb> list = new ArrayList<>(); try { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { // 如果你只想遍历到第一层目录,可以在这里返回FileVisitResult.SKIP_SUBTREE return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { String name = file.toFile().getName(); String[] split = name.split(" - "); list.add(new musicOb(split[0].replaceAll(" ",""),split[1].substring(0,split[1].indexOf("[")),true)); i[0]++; return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { // 在访问完一个目录后执行的操作,这里我们什么也不做 return FileVisitResult.CONTINUE; } }); } catch (Exception e){ e.printStackTrace(); } return list; }}

  7. 获取网易云登录Cookie1. chrome浏览器安装EditThisCookie插件,用来将cookie转成json。2. chrome浏览器登录网易云,从插件EditThisCookie中复制Coockie信息。3. selenium设置cookieimport com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import org.openqa.selenium.Cookie;import java.util.ArrayList;import java.util.Date;import java.util.List;public class Music163Cookies { //替换复制的COOKIES public static final String COOKIES = "[\n" + "{\n" + " \"domain\": \".163.com\",\n" + " \"expirationDate\": 1752132253.397857,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"_ntes_nnid\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \",1717572253397\",\n" + " \"id\": 1\n" + "},\n" + "{\n" + " \"domain\": \".163.com\",\n" + " \"expirationDate\": 1752132253.398033,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"_ntes_nuid\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 2\n" + "},\n" + "{\n" + " \"domain\": \".163.com\",\n" + " \"expirationDate\": 1748918432.886641,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"NTES_P_UTID\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"no_restriction\",\n" + " \"secure\": true,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"|1717382433\",\n" + " \"id\": 3\n" + "},\n" + "{\n" + " \"domain\": \".163.com\",\n" + " \"expirationDate\": 1751942432.898526,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"nts_mail_user\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"@163.com:-1:1\",\n" + " \"id\": 4\n" + "},\n" + "{\n" + " \"domain\": \".163.com\",\n" + " \"expirationDate\": 1748918432.886826,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"P_INFO\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 5\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1718874766.040354,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"__csrf\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 6\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752200652.883229,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"_iuqxldmzr_\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"32\",\n" + " \"id\": 7\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752200652.882781,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"JSESSIONID-WYYY\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 8\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1733130756.040268,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": true,\n" + " \"name\": \"MUSIC_U\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 9\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752132252.89175,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"NMTID\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"00O\",\n" + " \"id\": 10\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752201142.078239,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"ntes_kaola_ad\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"1\",\n" + " \"id\": 11\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752132256.818032,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"sDeviceId\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"YD%2Fk6IdX0cERX\",\n" + " \"id\": 12\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752201143.105386,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"WEVNSM\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"strict\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"1.0.0\",\n" + " \"id\": 13\n" + "},\n" + "{\n" + " \"domain\": \".music.163.com\",\n" + " \"expirationDate\": 1752132255.2275,\n" + " \"hostOnly\": false,\n" + " \"httpOnly\": false,\n" + " \"name\": \"WNMCID\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"strict\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"0\",\n" + " \"id\": 14\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1749108256,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"__snaker__id\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 15\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1720175650,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"gdxidpyhxdE\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 16\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1752132286.615827,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"ntes_utid\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"t\",\n" + " \"id\": 17\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1752132256.090159,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"WM_NI\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 18\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1752132256.090443,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"WM_NIKE\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 19\n" + "},\n" + "{\n" + " \"domain\": \"music.163.com\",\n" + " \"expirationDate\": 1752138759.479807,\n" + " \"hostOnly\": true,\n" + " \"httpOnly\": false,\n" + " \"name\": \"WM_TID\",\n" + " \"path\": \"/\",\n" + " \"sameSite\": \"unspecified\",\n" + " \"secure\": false,\n" + " \"session\": false,\n" + " \"storeId\": \"0\",\n" + " \"value\": \"\",\n" + " \"id\": 20\n" + "}\n" + "]"; public static List<Cookie> getCookies(){ List<Cookie> cookies = new ArrayList<>(); JSONArray jsonCookies = JSONObject.parseArray(COOKIES); jsonCookies.forEach(v->{ JSONObject cookie = (JSONObject)v; cookies.add(new Cookie(cookie.getString("name"),cookie.getString("value"),cookie.getString("domain"), cookie.getString("path"), new Date(System.currentTimeMillis() + cookie.getBigDecimal("expirationDate").longValue()), cookie.getBoolean("secure"),cookie.getBoolean("httpOnly"))); }); return cookies; }}

  8. 核心脚本代码import lombok.extern.slf4j.Slf4j;import org.openqa.selenium.*;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.concurrent.TimeUnit;@Component@Slf4jpublic class Music163 { @Autowired private WebDriver driver; /** * 查询后选择第几个搜索结果收藏 */ public static final Integer SEARCH_MUSIC_SELECT_NUMBER = 1; /** * 收藏时添加到第几个收藏夹 */ public static final Integer COLLECT_MUSIC_NUMBER = 2; /** * 获取dom元素失败后,尝试次数 */ public static final Integer GET_DOM_ERROR_TRY_FREQUENCY = 5; /** * 添加收藏 * * @param v 歌曲信息 * @param num 异常后循环尝试次数 */ public void addCollection(musicNameRead.musicOb v, int num) { try { //切换到默认iframes driver.switchTo().defaultContent(); //找到右上角输入框 WebElement inputInfo = driver.findElement(By.id("srch")); //清除里面内容 inputInfo.clear(); //设置搜索的歌曲名称 inputInfo.sendKeys(v.getName()); //回车查询 inputInfo.sendKeys(Keys.ENTER); //切换到表单iframes driver.switchTo().frame("g_iframe"); //加载完成后获取dom元素 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); //获取到隐藏节点的dom元素 WebElement srchrst = driver.findElement(By.className("n-srchrst")); //获取收藏dom元素 WebElement collectBar = srchrst.findElement(By.xpath("div/div[" + SEARCH_MUSIC_SELECT_NUMBER + "]/div[3]/div/span[1]")); //隐藏元素需要使用js点击操作,直接用click()方法不生效 JavascriptExecutor javascriptExecutor = (JavascriptExecutor) driver; javascriptExecutor.executeScript("arguments[0].click()", collectBar); //获取body dom元素 WebElement body = driver.findElement(By.tagName("body")); //获取第几个收藏歌单的dom元素 WebElement addCollection = body.findElement(By.xpath("div[10]/div[2]/div/div[2]/ul/li[" + COLLECT_MUSIC_NUMBER + "]")); //点击添加 addCollection.click(); log.info("添加歌曲完毕,歌曲名称:{}", v.getName()); } catch (Exception e) { e.printStackTrace(); if (num <= GET_DOM_ERROR_TRY_FREQUENCY) { addCollection(v, ++num); } } } public void startEvent() { try { //获取歌单信息 List<musicNameRead.musicOb> musicInfo = musicNameRead.getMusicInfo(); //循环操作 musicInfo.forEach(v -> { addCollection(v, 0); }); } catch (Exception e) { e.printStackTrace(); } } public void collectMusic() { //先访问一次页面,以便于设置Cookie,不然会报错 driver.get("https://music.163.com/"); Music163Cookies.getCookies().forEach(v -> { driver.manage().addCookie(v); }); driver.get("https://music.163.com/"); driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS); startEvent(); //关闭浏览器 driver.quit(); } /** * 流程开始 */ public void start() { collectMusic(); }}

  9. 演示效果

selenium演示效果


本文转载自: https://blog.csdn.net/qq_42702229/article/details/139524307
版权归原作者 Fore_v_er 所有, 如有侵权,请联系我们删除。

“spring boot基于selenium批量订阅网易云音乐歌曲”的评论:

还没有评论