文章目录
- 前言
- 一、请求分析
- 二、逆向思路
- 三、全部代码
- 总结
前言
声明:本文章只是用于学习逆向知识,仅供学习,未经作者同意禁止转载
对于爬虫而言,不管是什么类型的都会遵循这几个步骤
- 获取目标url
- 分析请求数据
- 逆向解密数据
- 伪造请求
- 清洗数据
- 保存数据
这是对于逆向爬虫中的步骤。
本文会使用谷歌浏览器自带的开发者工具
,分析网页端的酷狗音乐的请求进行逆向。
当然对于手机端也是可以进行爬虫和逆向的,不过会比网页端复杂多,需要一些工具以及反编译手段,需要java部分基础,这个我们将留到后面讲解学习。
一、请求分析
首先我们先进入酷狗音乐的搜索页面:
https://www.kugou.com/yy/html/search.html#searchType=song&searchKeyWord=快乐崇拜
点击第一首歌进去听一下,然后我们看一下当前页面的url如下:
https://www.kugou.com/mixsong/1rkjpr6a.html?fromsearch=快乐崇拜#hash=CA937F42CE9AACC3CDDB2516DAEE55D4&album_id=8437324&album_audio_id=106774479
我们点击f12打开开发者工具
进行抓包找到如图:
发现了这里请求是GET
并且有我们想要的结果,所以我们可以去分析第一张图的参数
。
二、逆向思路
经过测试发现该接口只需要mid
和album_audio_id
这两个参数
就可以获取到接口了。
那么这个album_audio_id好猜,顾名思义就是这个音乐的id嘛,那么这个mid是个什么呢?
我们可以发现mid是32位的,非常的像md5加密,所以我们去看源代码如图:
通过搜索mid发现了mid调用了一个getBaseInfo函数,那么我们可以往上看找打在哪里引入(
)
可以抓包找到也可以往上看引用地址
如下:
可以看到mid参数确实是个md5,所以我们可以把这两个代码单独拿出来
getBaseInfo.min.js如下:
const Guid = function () {
function e() {
return (65536 * (1 + Math.random()) | 0).toString(16).substring(1)
}
return e() + e() + "-" + e() + "-" + e() + "-" + e() + "-" + e() + e() + e()
}
const MD5 = function (e) {
function n(e, n) {
e[n >> 5] |= 128 << n % 32,
e[14 + (n + 64 >>> 9 << 4)] = n;
for (var t = 1732584193, s = -271733879, c = -1732584194, u = 271733878, d = 0; d < e.length; d += 16) {
var f = t
, m = s
, g = c
, p = u;
s = a(s = a(s = a(s = a(s = o(s = o(s = o(s = o(s = r(s = r(s = r(s = r(s = i(s = i(s = i(s = i(s, c = i(c, u = i(u, t = i(t, s, c, u, e[d + 0], 7, -680876936), s, c, e[d + 1], 12, -389564586), t, s, e[d + 2], 17, 606105819), u, t, e[d + 3], 22, -1044525330), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 4], 7, -176418897), s, c, e[d + 5], 12, 1200080426), t, s, e[d + 6], 17, -1473231341), u, t, e[d + 7], 22, -45705983), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 8], 7, 1770035416), s, c, e[d + 9], 12, -1958414417), t, s, e[d + 10], 17, -42063), u, t, e[d + 11], 22, -1990404162), c = i(c, u = i(u, t = i(t, s, c, u, e[d + 12], 7, 1804603682), s, c, e[d + 13], 12, -40341101), t, s, e[d + 14], 17, -1502002290), u, t, e[d + 15], 22, 1236535329), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 1], 5, -165796510), s, c, e[d + 6], 9, -1069501632), t, s, e[d + 11], 14, 643717713), u, t, e[d + 0], 20, -373897302), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 5], 5, -701558691), s, c, e[d + 10], 9, 38016083), t, s, e[d + 15], 14, -660478335), u, t, e[d + 4], 20, -405537848), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 9], 5, 568446438), s, c, e[d + 14], 9, -1019803690), t, s, e[d + 3], 14, -187363961), u, t, e[d + 8], 20, 1163531501), c = r(c, u = r(u, t = r(t, s, c, u, e[d + 13], 5, -1444681467), s, c, e[d + 2], 9, -51403784), t, s, e[d + 7], 14, 1735328473), u, t, e[d + 12], 20, -1926607734), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 5], 4, -378558), s, c, e[d + 8], 11, -2022574463), t, s, e[d + 11], 16, 1839030562), u, t, e[d + 14], 23, -35309556), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 1], 4, -1530992060), s, c, e[d + 4], 11, 1272893353), t, s, e[d + 7], 16, -155497632), u, t, e[d + 10], 23, -1094730640), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 13], 4, 681279174), s, c, e[d + 0], 11, -358537222), t, s, e[d + 3], 16, -722521979), u, t, e[d + 6], 23, 76029189), c = o(c, u = o(u, t = o(t, s, c, u, e[d + 9], 4, -640364487), s, c, e[d + 12], 11, -421815835), t, s, e[d + 15], 16, 530742520), u, t, e[d + 2], 23, -995338651), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 0], 6, -198630844), s, c, e[d + 7], 10, 1126891415), t, s, e[d + 14], 15, -1416354905), u, t, e[d + 5], 21, -57434055), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 12], 6, 1700485571), s, c, e[d + 3], 10, -1894986606), t, s, e[d + 10], 15, -1051523), u, t, e[d + 1], 21, -2054922799), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 8], 6, 1873313359), s, c, e[d + 15], 10, -30611744), t, s, e[d + 6], 15, -1560198380), u, t, e[d + 13], 21, 1309151649), c = a(c, u = a(u, t = a(t, s, c, u, e[d + 4], 6, -145523070), s, c, e[d + 11], 10, -1120210379), t, s, e[d + 2], 15, 718787259), u, t, e[d + 9], 21, -343485551),
t = l(t, f),
s = l(s, m),
c = l(c, g),
u = l(u, p)
}
return Array(t, s, c, u)
}
function t(e, n, t, i, r, o) {
return l(s(l(l(n, e), l(i, o)), r), t)
}
function i(e, n, i, r, o, a, l) {
return t(n & i | ~n & r, e, n, o, a, l)
}
function r(e, n, i, r, o, a, l) {
return t(n & r | i & ~r, e, n, o, a, l)
}
function o(e, n, i, r, o, a, l) {
return t(n ^ i ^ r, e, n, o, a, l)
}
function a(e, n, i, r, o, a, l) {
return t(i ^ (n | ~r), e, n, o, a, l)
}
function l(e, n) {
var t = (65535 & e) + (65535 & n);
return (e >> 16) + (n >> 16) + (t >> 16) << 16 | 65535 & t
}
function s(e, n) {
return e << n | e >>> 32 - n
}
function c(e) {
for (var n = Array(), t = (1 << d) - 1, i = 0; i < e.length * d; i += d)
n[i >> 5] |= (e.charCodeAt(i / d) & t) << i % 32;
return n
}
function u(e) {
for (var n = "", t = 0; t < 4 * e.length; t++)
n += "0123456789abcdef".charAt(e[t >> 2] >> t % 4 * 8 + 4 & 15) + "0123456789abcdef".charAt(e[t >> 2] >> t % 4 * 8 & 15);
return n
}
var d = 8;
return e = e ? function (e) {
return u(n(c(e), e.length * d))
}(e) : ""
}
function get_mid() {
const uid = Guid()
return MD5(uid)
}
自此mid就完成了,那么现在我们就需要去找album_audio_id了,对于逆向我们有时候也需要正向的去分析,对于这种音乐id,一般应该是其他页面传过来给我们,我们才能去获取的,所以回到一开始的搜索页面,打开f12开发者工具抓包。
如下:
可以发现在搜索页面抓到了这个请求,还是一样我们开始分析这个参数。
我们可以观察参数cienttime=1670213288633
很明显是一个时间戳
,而mid和uuid是相同的
,并且我们前面已经解析过了不需要分析,经过测试发现dfid是不变参数
,keyword是我们搜索的名字
,其他参数基本是不变的。
不过我们可以发现,此时还有一个参数signature=b1e228e8bbc9175422a38d2b70986d28
,这很明显也是md5加密
的,不过我们并不知道是怎么加密的,所以需要继续打断点进行解析测试。
我们使用全局搜索signature关键字
如图:
这个infSign.min.js看名字很像,我们点进去看看。
代码是丑化的,我们格式化一下。
我们可以发现这里很可以,明显这个s参数就是md5的盐,我们打开端点进行调试:
可以看到这就是s参数的全部,而我们就只需要把这22个参数使用join函数合并起来再进行md5加密,就完成了。
那么我们可以将现在我们所有的思路转化成python代码进行实现。
三、全部代码
main.py如下:
import json
import asyncio
import requests
import execjs
from hashlib import md5
import time
import threading
# 酷狗音乐主函数
class KuGoMusic():
def __init__(self, input):
self.new_md5 = md5()
self.url = "https://complexsearch.kugou.com/v2/search/song"
self.headers = {
'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
}
mid = self.get_mid()
self.child_url = "https://wwwapi.kugou.com/yy/index.php?r=play/getdata&mid={}".format(mid)
self.query = ""
self.params = ["NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt", "appid=1014", "bitrate=0", "callback=callback123",
"clienttime={}".format(int(time.time() * 1000)), "clientver=1000",
"dfid=4XSQkz1mmSGI2XV1Ud1xgR9V", "filter=10", "inputtype=0", "iscorrection=1", "isfuzzy=0",
"keyword={}".format(input), "mid={}".format(mid), "page=1", "pagesize=30", "platform=WebFilter",
"privilege_filter=0",
"srcappid=2919", "token=", "userid=0", "uuid={}".format(mid),
"NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"]
self.info = "".join(self.params)
self.new_md5.update(self.info.encode(encoding='utf-8'))
self.signature = self.new_md5.hexdigest()
del self.params[0]
del self.params[-1]
self.params.append("signature={}".format(self.signature))
def get_mid(self):
with open('getBaseInfo.min.js', 'r', encoding='utf-8') as f:
text = f.read()
js_data = execjs.compile(text)
mid = js_data.call('get_mid')
return mid
# 保存音频给你们当练习写(我没写 = =)
def with_open(self,url):
pass
def get_query(self):
# self.params.__iter__()
for index, item in enumerate(self.params):
if len(self.params) - 1 <= index:
self.query += item
break
self.query += "{}&".format(item)
def getData(self, url):
res = requests.get(url, headers=self.headers).json()
print(res['data']['audio_name'], res['data']['play_url'], res['data']['album_name'])
async def getData2(self, url):
loop = asyncio.get_event_loop()
# requests模块默认不支持异步操作,所以就使用线程池来配合实现了。
future = await loop.run_in_executor(None, requests.get, url, self.headers)
res = future.json()
print(res['data']['audio_name'], res['data']['play_url'], res['data']['album_name'])
def getData_Threads(self, lists):
threads = []
for row in lists:
url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
threads.append(threading.Thread(target=self.getData, args=(url_child,)))
for thread in threads:
# 执行
thread.start()
for thread in threads:
# 结束
thread.join()
def getData_test(self, lists):
for row in lists:
url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
self.getData(url_child)
def getData_async(self, lists):
tasks = []
for row in lists:
url_child = self.child_url + "&album_audio_id={}".format(row["ID"])
tasks.append(self.getData2(url_child))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
def parse_text(self, text):
lists = json.loads(text)['data']['lists']
# 多线程
# self.getData_Threads(lists)
# 普通
# self.getData_test(lists)
# 协程
self.getData_async(lists)
def run(self):
self.get_query()
url = "{0}?{1}".format(self.url, self.query)
res = requests.get(url, headers=self.headers)
self.parse_text(res.text[12:-2])
if __name__ == '__main__':
input = input("请输入歌名或歌手: ")
kugo = KuGoMusic(input)
kugo.run()
总结
不管是逆向还是非逆向,所有爬虫基本上都遵循着这几个步骤,路还漫长还需不断学习,多加练习。
最后