本次爬取《若月亮没来live》这首歌的评论信息,网页源代码和F12的代码并不一样,所以我们尝试在XHR中找到评论信息。
评论信息的文件是get,在get文件里面找到url,可以看到url后面有个“csrf_token=”,这个是我们没有登录,如果登录的话“=”后面就会有东西。请求方式是post。
1.找到encSecKey和params的加密过程
网页往服务器发送的参数params是加密了的。服务器收到请求以后会根据encSecKey将params里的参数还原出来,然后再去做其他操作。
请求到get的时候initiator里面是执行哪些js脚本的过程,Request call stack下面的脚本是从下往上执行,最上面的是最后执行的脚本。
点开最上面的js脚本以后,找到发送数据的那一行代码,然后打上断点找commen的url
程序在这个函数里面被加密了。然后在这个函数里面打个断点,看是在哪里进行加密的。
此时的data还没有问题。
然后点击这个键。
、
就会进入到该界面,可以发现加密的数据是在这几行中进行的。
所以加密过程就是该行代码进行加密的。通过window.asrsea(参数, xxx, xxx, xxx)进行加密的
即params -> encText encSecKey -> encSecKey
真实的参数就是下图这里:
网易音乐加密的网页源代码如下所示:
# 处理加密过程, 网易网页上的代码
"""
function a(a = 16) { # 返回随机的16位字符串
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1) # 循环16次
e = Math.random() * b.length, # 随机数
e = Math.floor(e), # 取整
c += b.charAt(e); # 取字符串中的xxx位置
return c
}
function b(a, b) { # a是要加密的内容
var c = CryptoJS.enc.Utf8.parse(b) # b是秘钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) # e是数据
, f = CryptoJS.AES.encrypt(e, c, { # c:加密的秘钥
iv: d, # 搜一下AES加密算法可知该变量是偏移量
mode: CryptoJS.mode.CBC # 模式:CBC
});
return f.toString()
}
function c(a, b, c) { # c里面不产生随机数
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) { d:数据(data ) e:010001 f:很长 g:0CoJUm6Qyw8W8jud
var h = {} # 空对象
, i = a(16); # 进入到a函数中 参数为16
h.encText = b(d, g), # g是秘钥 # params的第一次加密
h.encText = b(h.encText, i), # 返回的就是params i也是秘钥 # params的第二次加密
h.encSecKey = c(i, e, f), # 返回的就是encSecKey,e和f是定死的,把i固定以后,得到的encSecKey也是固定的
return h
}
encText两次加密:
数据+g => b => 第一次加密的结果+i => b = params
"""
2.将encSecKey写死
通过搜索“window.asrsea”搜索加密过程,加密过程就是下图圈起来的那一坨代码。 “ window.asrsea = d”,故该加密过程入口是d函数
d函数的第一个参数d就是刚才拿到的数据,保存到data变量中
data = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_2158973221",
"threadId": "R_SO_4_2158973221",
}
第二个参数e就是复制第二个参数打开console,往里面一运行就发现参数是'010001',第三个和第四个参数同第二个参数。
第三个参数是 f : "'00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'"
第四个参数是 g:'0CoJUm6Qyw8W8jud'
打个断点找到i的值写死,就可以使encSecKey固定不变。i: "jytyq7y8SwRmHgD7"
现在的encSecKey是
encSecKey: "7b043865d78705e97b83d04eecc0990ce8cfd4c1e61fc7196a7c7da7b020ed283c0bc163952fe2a35a821b8087819f768d3fbc240f6f424c118aa6a2caaae85e7d28faae0b8db1d7a121ff90cb71abad26d5f0646f63c8643e72ffde3fa065e9e7ef6c1970697e90fb4b051201ce86a5907dbde5e682524836e94068cecdff2a"
此时encSecKey是i为"jytyq7y8SwRmHgD7"的时候的值,
3.将params写死
# 将加密的结果处理成16的倍数, 为下面的加密算法服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
# 加密过程(算法)
def enc_params(data, key):
iv = "0102030405060708" # 下面b函数中给了iv=d 而d="0102030405060708"
data = to_16(data)
# key: 秘钥 IV: 偏移量 mode: 模式
aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC) # new一个能进行AES加密的工具
bs = aes.encrypt(data.encode("utf-8")) # 加密, 加密的内容的长度必须是16的倍数 需将data转换为字节
# 加密的结果不能直接被decode("utf-8")识别,所以不能直接decode,需要导入b64decode
return str(b64encode(bs), "utf-8") # 结果转化成字符串返回
4.发送请求,得到歌曲评论数据
整个源代码如下所示:
爬取网易相关的信息都是这个流程,例如网易新闻、网易云音乐等。
# 1.找到未加密的参数 # window.asrsea(参数, xxx, xxx, xxx)
# 2.想办法把参数进行加密(params,encSecKey),必须参考网易加密的逻辑 # params -> encText encSecKey -> encSecKey
# 3.请求到网易,拿到评论信息
# AES加密,需要安装pycrypto: pip install pycrypto
# 安装时电脑上需要有vs
# 安装时候会可能会出现一些错误,可以网上寻找解决办法
from Crypto.Cipher import AES
from base64 import b64encode # 加密结果进行字节
import requests
import json
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
# 请求方式是post,以下是真实的参数
# window.asrsea(参数, xxx, xxx, xxx)函数的四个参数d,e,f,g
data = { # 现在是字典
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_2158973221",
"threadId": "R_SO_4_2158973221",
}
# 服务于d的
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
i = "jytyq7y8SwRmHgD7" # 手动固定的 -> 网易音乐评论页面函数中是随机的
def get_encSecKey(): # 由于i被我们固定了,所以encSecText就是固定的,c()函数的结果就是固定的
# 返回encSecKey
return "7b043865d78705e97b83d04eecc0990ce8cfd4c1e61fc7196a7c7da7b020ed283c0bc163952fe2a35a821b8087819f768d3fbc240f6f424c118aa6a2caaae85e7d28faae0b8db1d7a121ff90cb71abad26d5f0646f63c8643e72ffde3fa065e9e7ef6c1970697e90fb4b051201ce86a5907dbde5e682524836e94068cecdff2a"
# 把参数params进行加密
# 需要将函数中参数data转换为字符串,所以需要json包
def get_params(data): # 需要四个参数,e、f、g、i等都是固定的,故只需要将data这个变化的参数写死, 默认data是字符串
first = enc_params(data, g) # 第一次加密
second = enc_params(first, i)
return second # 加密的结果,返回的就是params
# 将加密的结果处理成16的倍数, 为下面的加密算法服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
# 加密过程(算法)
def enc_params(data, key):
iv = "0102030405060708" # 下面b函数中给了iv=d 而d="0102030405060708"
data = to_16(data)
# key: 秘钥 IV: 偏移量 mode: 模式
aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC) # new一个能进行AES加密的工具
bs = aes.encrypt(data.encode("utf-8")) # 加密, 加密的内容的长度必须是16的倍数 需将data转换为字节
# 加密的结果不能直接被decode("utf-8")识别,所以不能直接decode,需要导入b64decode
return str(b64encode(bs), "utf-8") # 结果转化成字符串返回
# 处理加密过程, 网易网页上的代码
"""
function a(a = 16) { # 返回随机的16位字符串
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1) # 循环16次
e = Math.random() * b.length, # 随机数
e = Math.floor(e), # 取整
c += b.charAt(e); # 取字符串中的xxx位置
return c
}
function b(a, b) { # a是要加密的内容
var c = CryptoJS.enc.Utf8.parse(b) # b是秘钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) # e是数据
, f = CryptoJS.AES.encrypt(e, c, { # c:加密的秘钥
iv: d, # 搜一下AES加密算法可知该变量是偏移量
mode: CryptoJS.mode.CBC # 模式:CBC
});
return f.toString()
}
function c(a, b, c) { # c里面不产生随机数
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) { d:数据(data ) e:010001 f:很长 g:0CoJUm6Qyw8W8jud
var h = {} # 空对象
, i = a(16); # 进入到a函数中 参数为16
h.encText = b(d, g), # g是秘钥 # params的第一次加密
h.encText = b(h.encText, i), # 返回的就是params i也是秘钥 # params的第二次加密
h.encSecKey = c(i, e, f), # 返回的就是encSecKey,e和f是定死的,把i固定以后,得到的encSecKey也是固定的
return h
}
encText两次加密:
数据+g => b => 第一次加密的结果+i => b = params
"""
# 发送请求,得到歌曲评论结果
resp = requests.post(url, data={
"params": get_params(json.dumps(data)), # 需要将data转换为json的字符串形式然后作为参数进入该函数中
"encSecKey": get_encSecKey()
})
print(resp.text)