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;
    
    @Configuration
    public 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.  获取网易云登录Cookie

    1. chrome浏览器安装EditThisCookie插件,用来将cookie转成json。

    2. chrome浏览器登录网易云,从插件EditThisCookie中复制Coockie信息。

    3. selenium设置cookie

      import 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
    @Slf4j
    public 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演示效果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值