解密网易云音乐评论js加密参数,实现分词处理制作词云图

前言:

很多人都说网易云音乐的一条评论是一个故事, 细细一看的话, 你会发现有些评论确实很真, 很性情。
抓取网易云音乐评论主要涉及了参数的加密问题,这篇博客主要是剖析一下如何进行参数的加密以及解密剖析
这里使用的是java进行加密实现数据的抓取。

爬虫有三难:

  • 登录难 如: 淘宝, 12306
  • 参数加密 如: 网易云音乐评论, 企查查, 天眼查,
  • 响应(内容)加密 如: 企查查, 天眼查

确立需求

获取“电灯胆”歌词评论, 然后对歌词进行分词处理,获取高频词,制作词云图等

这里主要涉及的的技术点有:

  1. F12的抓包以及快速定位链接
  2. 网页debug js在爬虫中的使用
  3. 爬虫参数加密的剖析过程
  4. java分词工具word的使用
  5. mysql 统计相同

先来看一下整体效果

图一 图二使用的是 图悦 进行分词统计并且生成词云图
http://www.picdata.cn/picdata/index.php#
在这里插入图片描述

在这里插入图片描述
图三使用的是java word 分词库进行分词, 结合数据库进行统计词频等生成的词云图
在这里插入图片描述


具体步骤如下:

步骤01: 我们随机选择一首歌曲进入页面, 拖到底部查看评论页面

然后点击下一页, 网页会请求后台获取数据, 我们通过F12抓包可以知道链接地址以及参数等, 如下图:
在这里插入图片描述

步骤02: 参看传递的参数, 如下图

我们可以了解到
url:https://music.163.com/weapi/v1/resource/comments/R_SO_4_1379057027?csrf_token=
method: post
content-type: application/x-www-form-urlencoded

params:必要参数
encSecKey: 必要参数
在这里插入图片描述

步骤03: 解析参数来源

从图一中可以知道发起请求的js是core_97f1bfe….js?97f1bfe
那么进入这个js查看其源码, 可以发现这个core_xxxx.js的东西竟然是一个4万5千多行的文件
不过不要慌, 如果真的去看完这个多代码都可以前端 js了, 我想这篇博客也是不知道猴年马月才可以写了
在这里插入图片描述

步骤04: 快速定位加密的js位置

ctrl + f 搜索关键字 “encSecKey”
可以知道了生成参数params encSecKey的参数位置了
在这里插入图片描述
接着往上看两行代码

var bUP7I = window.asrsea(JSON.stringify(i8a), brF9w(["流泪", "强"]), brF9w(WZ4d.md), brF9w(["爱心", "女孩", "惊恐", "大笑"]));

发现是一个 window.asrsea的方法产生的一个对象bUP7I
在这里留一个心眼, brF9w 这是一个方法, 传递了一些乱七八糟的参数, 但是我在断点了好多次都发现是这几个奇奇怪怪的参数, 非常有意思的网易云音乐呀…
因此我们在这里可以理解为调用的window.asrsea()方法,
第一个参数是一个字符串,=>因为看到了JSON.stringify()这个方法
第二个参数是一个常量
第三个参数是一个常量
第四个参数是一个常量

步骤05: 找到五个核心方法(a b c d e)

接着我们ctrl + f 搜索关键字 “asrsea”
如下图, 我们这个五个方法进行了深入的了解以及剖析, 就是这几个方法花了我一个星期的空闲时间, 主要是不理解机密, 捣腾了
window.asrsea = d,
window.ecnonasr = e
可以了解到d方法赋值给window.asrsea, e赋值给window.ecnonasr

因此图四调用的window.asrsea, 实际上就是d方法

在这里插入图片描述

步骤06: debug核心方法d

在12896行打一个断点, 我们查看一下四个参数究竟是什么东东
在这里插入图片描述
参数d:
{“rid”:“R_SO_4_1379057027”,“offset”:“100”,“total”:“false”,“limit”:“20”,“csrf_token”:""}

参数e:
010001

参数f:
00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7

参数g:
0CoJUm6Qyw8W8jud

关键的源码

 function a(a) {
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {
        var h = {}
          , i = a(16);
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }
    function e(a, b, d, e) {
        var f = {};
        return f.encText = c(a + e, b, d),
        f
    }
    window.asrsea = d,
    window.ecnonasr = e

结合步骤05的图解, 可以知道方法b是一个加密方法,方法d是一个参数组装方法,

细看方法d, 会发现调用了两次方法b, 也就是加密两次了, 第一次是对参数d, g加密, g是一个常量, 上面以及解析了, 第二次是对第一次机密的结果再次进行加密, 参数i是一个调用了a方法的16为随机数, 因此i也是可以理解为一个常量值, 通过debug, 就可以查到这个常量是什么了

接着再看 h.encSecKey = c(i, e, f),这行代码
由于 i 可以理解为常量, e 传入d方法的时候也是常量, f 传入d方法的时候也是常量,
因此可以理解为h.encSecKey也是一个常量, 我们只需要debug, 获取到一个h.encSecKey即可( 但是这个encSecKey和参数 i 需要 同一次debug出来的才可以, 否则有问题的…)

步骤07: java实现前端的加密算法(js方法b)

由于这里找了很多资料, 直接提供这个方法出来
https://blog.csdn.net/bicheng4769/article/details/80811676
https://zhidao.baidu.com/question/1819427615658816228.html

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.util.Base64Utils;

/**
 * @description AESEncrypt加密工具
 * @date 2019-08-24 23:51:19
 * @author houyu for.houyu@foxmail.com
 */
public class AESEncrypt {

    public static String encrypt(String content, String sKey) {
        /*
         function b(a, b) {
             var c = CryptoJS.enc.Utf8.parse(b)
               , d = CryptoJS.enc.Utf8.parse("0102030405060708")
               , e = CryptoJS.enc.Utf8.parse(a)
               , f = CryptoJS.AES.encrypt(e, c, {
                         iv: d,
                         mode: CryptoJS.mode.CBC
                     });
             return f.toString()
         }
        // 说明: 前段js没有使用填充模式, 默认使用了PKCS7Padding
        //
        // https://zhidao.baidu.com/question/1819427615658816228.html
        // CryptoJS.enc.Utf8.parse方法才可以将key转为128bit的。好吧,既然说了是多次尝试,那么就不知道原因了,后期再对其进行更深入的研究。
        // 字符串类型的key用之前需要用uft8先parse一下才能用
        //
        // 后端使用的是PKCS5Padding,但是在使用CryptoJS的时候发现根本没有这个偏移,查询后发现PKCS5Padding和PKCS7Padding是一样的东东,使用时默认就是按照PKCS7Padding进行偏移的
        //
        // CryptoJS.AES         >> 算法
        // CBC                  >> 模式
        // 0102030405060708     >> 偏移量
        //
        // 由于CryptoJS生成的密文是一个对象,如果直接将其转为字符串是一个Base64编码过的,在encryptedData.ciphertext上的属性转为字符串才是后端需要的格式。
         */
        try {
            byte[] encryptedBytes;
            byte[] byteContent = content.getBytes("UTF-8");
            // 获取cipher对象,getInstance("算法/工作模式/填充模式")
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // 采用AES方式将密码转化成密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(sKey.getBytes(), "AES");
            // 初始化偏移量
            IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes("UTF-8"));
            //cipher对象初始化 init(“加密/解密,密钥,偏移量”)
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
            //按照上面定义的方式对数据进行处理。
            encryptedBytes = cipher.doFinal(byteContent);
            return new String(Base64Utils.encode(encryptedBytes), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

步骤08: 验证js加密之后的内容与java加密之后的内容是否一致(方法b)

首先控制相同变量
参数d:
{“rid”:“R_SO_4_1379057027”,“offset”:“100”,“total”:“false”,“limit”:“20”,“csrf_token”:""}
参数g:
0CoJUm6Qyw8W8jud

浏览器控制台输入
b(’{“rid”:“R_SO_4_1379057027”,“offset”:“100”,“total”:“false”,“limit”:“20”,“csrf_token”:""}’, ‘0CoJUm6Qyw8W8jud’)
结果会报如下图错误在这里插入图片描述
因此我们需要先定义方法b, 直接把方法b的源码丢到控制台即可
在这里插入图片描述

后端java加密结果
在这里插入图片描述

至此 以及实现了java后端加密和js加密一致的内容, 接下来就是比较简单的了,看一下封装对应前段的几个方法即可

import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.util.Base64Utils;

/**
 * @description AESEncrypt加密工具
 * @date 2019-08-24 23:51:19
 * @author houyu for.houyu@foxmail.com
 */
public class AESEncrypt {

    /**
     * @description 对应网易云音乐的方法b
     * @date 2019-08-25 00:05:05
     * @author houyu for.houyu@foxmail.com
     */
    public static String encrypt(String content, String sKey) {
        /*
         function b(a, b) {
             var c = CryptoJS.enc.Utf8.parse(b)
               , d = CryptoJS.enc.Utf8.parse("0102030405060708")
               , e = CryptoJS.enc.Utf8.parse(a)
               , f = CryptoJS.AES.encrypt(e, c, {
                         iv: d,
                         mode: CryptoJS.mode.CBC
                     });
             return f.toString()
         }
        // 说明: 前段js没有使用填充模式, 默认使用了PKCS7Padding
        //
        // https://zhidao.baidu.com/question/1819427615658816228.html
        // CryptoJS.enc.Utf8.parse方法才可以将key转为128bit的。好吧,既然说了是多次尝试,那么就不知道原因了,后期再对其进行更深入的研究。
        // 字符串类型的key用之前需要用uft8先parse一下才能用
        //
        // 后端使用的是PKCS5Padding,但是在使用CryptoJS的时候发现根本没有这个偏移,查询后发现PKCS5Padding和PKCS7Padding是一样的东东,使用时默认就是按照PKCS7Padding进行偏移的
        //
        // CryptoJS.AES         >> 算法
        // CBC                  >> 模式
        // 0102030405060708     >> 偏移量
        //
        // 由于CryptoJS生成的密文是一个对象,如果直接将其转为字符串是一个Base64编码过的,在encryptedData.ciphertext上的属性转为字符串才是后端需要的格式。
         */
        try {
            byte[] encryptedBytes;
            byte[] byteContent = content.getBytes("UTF-8");
            // 获取cipher对象,getInstance("算法/工作模式/填充模式")
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // 采用AES方式将密码转化成密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(sKey.getBytes(), "AES");
            // 初始化偏移量
            IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes("UTF-8"));
            //cipher对象初始化 init(“加密/解密,密钥,偏移量”)
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
            //按照上面定义的方式对数据进行处理。
            encryptedBytes = cipher.doFinal(byteContent);
            return new String(Base64Utils.encode(encryptedBytes), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /** 以下参数是js debug获取到的 */
    public static final String i = "C3Ba8sQUIHbPHC1Z";
    public static final String e = "";
    public static final String f = "";
    public static final String g = "0CoJUm6Qyw8W8jud";
    public static final String encSecKey = "7c23b7a80684cee814ecdc6252cc66e53a4df5890b1299783e5c575c709e7c8c22d98edc4074fc31b9bf7458e1ab6452c42fde55fcbd9b765f049da3809703686fc86b43ff757a2fa9eb77c0b04a51f02efb3e0ade116454561a6f2aefe89f6d611343383eaf643dce13b4ad1709ea2f8215f922c1a014d7fd79adcd0fa107a4";

    /**
     * @description 对应网易云音乐方法d, 其中参数d f g在外边定义好
     * @date 2019-08-25 00:06:51
     * @author houyu for.houyu@foxmail.com
     */
    public static Map<String, Object> methodD(String pageObject) {
        String encText = encrypt(pageObject, g);
        encText = encrypt(encText, i);// C3Ba8sQUIHbPHC1Z

        // 这里就不必要调用方法c了,因为这里是一个固定值, 直接前段js debug出来即可
        Map<String, Object> paramMap = new HashMap<>(2);
        paramMap.put("params", encText);
        paramMap.put("encSecKey", encSecKey);
        return paramMap;
    }

    public static Map<String, Object> getParamMap(String rid, int pageIndex, int pageSize){
        String s = "{\"rid\":\"%s\",\"offset\":\"%s\",\"total\":\"false\",\"limit\":\"%s\",\"csrf_token\":\"\"}";
        s = String.format(s, rid, (pageIndex - 1) * pageSize, pageSize);
        return methodD(s);
    }

}

具体源码在上面的git

https://github.com/HouYuSource/spider/blob/master/src/main/java/cn/shaines/spider/module/music/Music163Spider.java

  • 代理池抓取
  • 多线程抓取

待完善的地方是:重试机制的建立, 也就是说某一页失败了,加入重新抓取队列中。

最后的话

  • 单个IP抓取一定数量之后就会被封IP,建议加上IP池(下面是封IP之后, 网页的评论列表都打不开了)
    在这里插入图片描述
  • 网易后端限制了中间页数的获取, 也就是说大概有2/5获取不到的.
    在这里插入图片描述

在这里插入图片描述

我的CSDN:https://blog.csdn.net/JinglongSource
我的博客:https://shaines.cn

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值