网页抓取解密(目标网易云音乐只需要解密params和encSecKey就可以快乐的了)
优采云 发布时间: 2022-03-12 16:03网页抓取解密(目标网易云音乐只需要解密params和encSecKey就可以快乐的了)
目标
网易云音乐只需要解密params和encSecKey就可以愉快的开始爬取了。当然,如果没有足够的代理IP,还是要慢慢爬。废话不多说,直接开始吧。
问
老规矩,先来看看我们要抓取的页面:
查看网络请求:
从名字上可以快速定位到哪个请求是POST请求,然后看它提交了哪些参数,FormData如下:
提交的参数,上面提到的params和encSecKey,都是加密的。我们看看返回内容的格式:
ok,基本的东西我们已经知道了,我们可以进行下一步,找到解密的两个参数。
分析
在调试之前,我们要找到这两个值的位置,然后搜索,先定位js文件再定位代码位置。你应该知道如何搜索。您可以搜索 params 或 encSecKey。如果发现多个结果,不确定是哪个文件,可以点击每个key,搜索key参数,判断是否为目标文件。这里我直接标记了。正确的文件,你可以点击进去。
进入JS文件后,同样搜索key参数params或者encSecKey:
找到encSecKey的位置,拉出这几行代码分析一下:
var bVZ8R = window.asrsea(JSON.stringify(i0x), bqN0x(["流泪", "强"]), bqN0x(Wx5C.md), bqN0x(["爱心", "女孩", "惊恐", "大笑"]));
e0x.data = j0x.cs1x({
params: bVZ8R.encText,
encSecKey: bVZ8R.encSecKey
})
粗略地说,params和encSecKey来自bVZ8R.encText和bVZ8R.encSecKey,bVZ8R是window.asrsea的结果,有四个参数,JSON.stringify(i0x), bqN0x(["tear", "strong"]) , bqN0x(Wx5C.md), bqN0x(["love", "girl", "horrified", "laughing"],先看最后三个参数,从它们的固定值可以大胆推断出这三个值也是Fixed,之所以说是fixed,看一下Wx5C.md:
wx5C.md是一个固定的数组,bqN0x(["tears", "strong"])和bqN0x(["love", "girl", "horrified", "laughing"]肯定不会导致改变,如图下面,测试一下:
这些参数我大概都弄清楚了,剩下的就是弄清楚window.asrsea的具体实现,以及i0x是什么样的,进入调试过程。
调试
Window.asrsea 有一个断点。我的代码位置是第 13133 行。点击粉丝列表的下一页将激活断点。在激活断点的同时,我们也可以看到 i0x 的美妙之处。在控制台输入 i0x:
limit、offset、total、userId其实都是已知的,这里可以看到csrf_token的生成,细心的童鞋应该早就发现了:
}
i0x["csrf_token"] = v0x.gP3x("__csrf"); ## csrf_token在这里产生
X0x = X0x.replace("api", "weapi");
e0x.method = "post";
delete e0x.query;
var bVZ8R = window.asrsea(JSON.stringify(i0x), bqN0x(["流泪", "强"]), bqN0x(Wx5C.md), bqN0x(["爱心", "女孩", "惊恐", "大笑"]));
我点击进入 v0x.gP3x 函数查看:
从代码可以看出csrf_token来自Cookie中的__csrf:
那么这个值就可以在请求网页时从cookie中获取,继续调试window.asrsea。一路点击下一步进入功能。
跳转到ad(d,e,f,g)函数,往下看一点,发现window.asrsea等于这个d函数,哦,好吧,调试一下这个d函数:
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
}
输入一个函数:
可以看到a函数生成随机数,继续运行,进入b函数:
熟悉的AES加密,继续跑进c函数:
就是大家熟悉的RSA加密,网易可真是小心翼翼,各种加密。至此,整体框架已经调试完毕,剩下的无非就是挖JS代码了。
蟒蛇运行
这一次,不仅是运行结果,还包括爬取和入库:
获取参数和 encSecKey
def get_enc(self,a):
with open('..//js//wangyiyun.js', encoding='utf-8') as f:
wangyiyun = f.read()
js = execjs.compile(wangyiyun)
logid = js.call('get_pwd', a)
print(logid)
return logid
抓住
def get_fans(self):
resp = self.get_home_page()
print(resp.cookies)
print(resp.status_code)
time.sleep(6)
limit = 20
for i in range(1,110):
print("第{}页".format(i+1))
offset = limit*i
a = {"userId": "46991111", "offset": str(offset), "total": "false", "limit": str(limit), "csrf_token": ""}
print(a)
logid = self.get_enc(a)
data = {
"params":logid["encText"],
"encSecKey":logid["encSecKey"],
}
print(data)
fans_url = "https://music.163.com/weapi/user/getfolloweds?csrf_token="
resp = self.session.post(url=fans_url,data=data,headers=self.headers)
followed = json.loads(resp.text)
followed_list = []
for foll in followed["followeds"]:
foll_dict = {}
foll_dict["short_name"] = foll.get("py","") #缩写
foll_dict["userId"] = foll.get("userId","") #用户ID
foll_dict["nickname"] = foll.get("nickname","") #昵称
foll_dict["vipType"] = foll.get("vipType","") # vip
foll_dict["eventCount"] = foll.get("eventCount","")#动态
foll_dict["vipRights"] = str(foll.get("vipRights","")) #VIP权益
foll_dict["gender"] = foll.get("gender","") #性别
foll_dict["avatarUrl"] = foll.get("avatarUrl","") #头像
foll_dict["followed"] = foll.get("followed","")
foll_dict["followeds"] = foll.get("followeds","") #粉丝
foll_dict["follows"] = foll.get("follows","") #关注
foll_dict["playlistCount"] = foll.get("playlistCount","") #歌单
foll_dict["mutual"] = foll.get("mutual","") #
foll_dict["expertTags"] = str(foll.get("expertTags",""))
foll_dict["experts"] = str(foll.get("experts",""))
print(foll_dict)
followed_list.append(foll_dict)
self.mysql.insert("music",followed_list)
tm = random.randint(10,30)
time.sleep(tm)
这里需要注意的是,要爬取指定页面,必须先访问这个页面,不能直接请求这个链接,因为它根本没有关于哪个页面的信息。
请求指定网页
def get_home_page(self):
url = "https://music.163.com/#/user/home?id=1737833656"
resp = self.session.get(url)
return resp
表结构
@property
def create_table_sql(self):
create_table = """
CREATE TABLE IF NOT EXISTS music (
short_name varchar(30) ,
userId varchar(100) NOT NULL,
nickname varchar(30),
vipType varchar(30) ,
eventCount varchar(200),
vipRights varchar(900),
gender varchar(900),
avatarUrl varchar(200),
followed varchar(30),
followeds varchar(30),
follows varchar(30),
playlistCount varchar(30),
mutual varchar(30),
expertTags varchar(30),
experts varchar(30),
PRIMARY KEY (userId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"""
return create_table
仓储
def insert(self,table,data_list):
if len(data_list) > 0:
data_list = [{k: v
for k, v in data.items() if v is not None}
for data in data_list]
keys = ", ".join(data_list[0].keys())
values = ", ".join(["%s"] * len(data_list[0]))
sql = """INSERT INTO {table}({keys}) VALUES ({values}) ON
DUPLICATE KEY UPDATE""".format(table=table,
keys=keys,
values=values)
update = ",".join([
" {key} = values({key})".format(key=key)
for key in data_list[0]
])
sql += update
print(sql)
self.connect()
try:
ret = self.cursor.executemany(sql, [tuple(data.values()) for data in data_list])
self.conn.commit()
except Exception as e:
self.conn.rollback()
print("Error: ", e)
traceback.print_exc()
finally:
self.close()
过程
结尾