首先放网易云音乐的网址:
https://music.163.com/
通过Network我们可以找到我们的音乐url存放的位置
那么我们就简单啦,知道Ajax的请求页面,我们当然就可以直接爬取了,但是:
这个FormData好像不简单,那我怎么请求呢?
第一直觉,就感觉是被加密了,不愧是网易云,有一套呢
那么肯定就是和JS脱离不了关系了,找到JS,然后保存到本地进行分析一下
把代码保存到本地,进行一些操作
加密用到了四个参数,那么我们可以打印一下这四个参数
那么问题来了,这JS文件在我的本地,我怎么让网站进行加载呢?
对的,这里要运用到一个工具,Fiddler4
这是个什么东西呢
它能够记录并检查所有你的电脑和互联网之间的Http通讯,设置断点,查看所有的进出Fiddler的数据(cookie,html,css,js)
大概就是,在客户端和服务器之间创建一个代理服务器来对之间进行交互通讯信息进行监控
下载安装完成之后还要对Fiddler4进行配置:
Tools---》Options
更详细的介绍,这里就不多说了
那么大概界面是这样的
我们打开官网
把core.js拖拉都右边,钩上相应的选项,在最下面找到要替换的JS,最后点击一下save
就能发现上面的路径变了,上图是我已经替换好的了
接下来:在网易云上搜索一首歌,打开控制台
你会发现,居然这样子了:
打印成功了!
再看XHR里面
我们的重点是url这个,所以我们只用关注第一个打印的,显然ids就是歌曲的序号
多试几组可以看出来一个问题就是:
后三个参数是不用管的
那么歌曲的序号又要怎么获取呢?最终找到的结果是在
然而这个页面也是加密的,很强,没事
我们再看看后台打印的东西
span class = "s-fc7"又是什么东西呢?
经过测试,发现这是固定的值
整个Json不同的地方在于s,传入歌名就可以了
呼
offset是偏移量,与翻页数有关系
好了,接下来又得去看我们的JS代码,去分析加密过程了
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
首先看函数d
函数d首先有个i,这个i是一个随机的十六位字符串,
然后进行了两次加密,第一次是第一个参数和第四个参数进行加密,把结果返回出来后与i字符串进行第二次加密,这个encText就是我们的params
而通过我们的刚才打印结果来看,后三个参数是固定的,然而i是随机的,也就是我们可以固定一个参数(一个最不可能的坑你,就是每次随机刚好就是随机到我的字符串),也就是说h.encSecKey=c(i,e,f)也是固定的,那也没什么好看的了
最主要的还是我们的params参数的第一次加密,因为第二次加密是在第一次加密的结果和一个固定的字符串,所以也没有讨论的必要了
首先看看我们的加密算法:
#AES加密算法
def AES_encrypt(text, key, iv):
pad = 16 - len(text) % 16
if type(text)==type(b''):
text = str(text, encoding='utf-8')
text = text + pad * chr(pad)
encryptor = AES.new(key, AES.MODE_CBC, iv)
encrypt_text = encryptor.encrypt(text)
encrypt_text = base64.b64encode(encrypt_text)
return encrypt_text
这里,要安装一下Crypto模块,不然会报错,模块找不到
接下来就是代码了
from Crypto.Cipher import AES
import requests
import base64
import os
import codecs
import json
from pypinyin import lazy_pinyin
from urllib.request import urlretrieve
# 后三个参数和i的值(随机的十六位字符串)
b = '010001'
c = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
d = '0CoJUm6Qyw8W8jud'
#随机的十六位字符串
def createSecretKey(size):
return (''.join(map(lambda xx: (hex(ord(xx))[2:]), str(os.urandom(size)))))[0:16]
#AES加密算法
def AES_encrypt(text, key, iv):
pad = 16 - len(text) % 16
if type(text)==type(b''):
text = str(text, encoding='utf-8')
text = text + str(pad * chr(pad))
encryptor = AES.new(key, AES.MODE_CBC, iv)
encrypt_text = encryptor.encrypt(text)
encrypt_text = base64.b64encode(encrypt_text)
return encrypt_text
#得到第一个加密参数
def Getparams(a,SecretKey):
#0102030405060708是偏移量,固定值
iv = '0102030405060708'
h_encText = AES_encrypt(a,d,iv)
h_encText = AES_encrypt(h_encText,SecretKey,iv)
return h_encText
#得到第二个加密参数
def GetSecKey(text, pubKey, modulus):
text = text[::-1]
rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(pubKey, 16) % int(modulus, 16)
return format(rs, 'x').zfill(256)
#得到表单的两个参数
def GetFormData(a):
SecretKey = createSecretKey(16)
params = Getparams(a,SecretKey)
enSecKey = GetSecKey(SecretKey,b,c)
data = {
"params":str(params,encoding='utf-8'),
"encSecKey":enSecKey
}
return data
def getOnePatam():
# 查询id的url
url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
#伪装头部
head = {
'Host': 'music.163.com',
'Origin':'https://music.163.com',
'Referer':'https://music.163.com/search/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
print("输入你想要下载的歌手")
song_name = input()
#第一个参数
song_name = ''.join(lazy_pinyin(song_name))
key = '{hlpretag:"",hlposttag:"</span>",s:"'+song_name+'",type:"1",csrf_token:"",limit:"30",total:"true",offset:"0"}'
FormData = GetFormData(key)
html = requests.post(url,headers=head,data=FormData)
result = json.loads(html.text)
return result['result']['songs']
#下载器:
def download(name,id):
# 获取歌曲的url的路径
song_url = "https://music.163.com/weapi/song/enhance/player/url?csrf_token="
# 伪装头部
headers = {
'Host': 'music.163.com',
'Origin': 'https://music.163.com',
'Referer': 'https://music.163.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
# 把上个页面查询到的id放到第二个页面的第一个参数上
a =str({'ids': "["+str(id)+"]", 'br': 320000, 'csrf_token': ""})
FormData = GetFormData(a)
response = requests.post(song_url,data = FormData,headers=headers)
json_dict = json.loads(response.content)
song_url=json_dict['data'][0]['url']
print(song_url)
folder = os.path.exists('songs')
if not folder:
os.makedirs('songs')
path = os.path.join('songs',name+".mp3")
urlretrieve(song_url,filename=path)
if __name__ == '__main__':
song_list = getOnePatam()
for i in song_list:
name = i['name']
id = i['id']
download(name,id)
以下是效果图
这次程序重点的是对加密的网页,学会如何去处理
有一点要强调一下,因为如果是中文,会导致加密的时候字符串长度不匹配的问题,所以只能用拼音,所以这里加了一个中文转拼音的库,pypinyin的lazy_pinyin()的方法