python爬取网易云音乐_Python 从零开始爬虫(七)——实战:网易云音乐评论爬取(附加密算法)...
前言
某宝评论区已经成功爬取了,jd的也是差不多的方法,说实话也没什么好玩的,我是看上它们分析简单,又没加密才拿来试手的。如果真的要看些有趣的评论的话,我会选择网易云音乐,里面汇聚了哲学家,小说家,story-teller,皮皮虾等各种人才,某些评论非常值得收藏(甚至开了一个歌单专门收藏它们)。竟然这么好玩,何不尝试把他们爬取下来呢?
所以这个(大规模)网易云音乐评论爬取project就成型了
整个过程并不顺利,网上找到的解决方案清一色用的是pycrypto模块(已经没人维护,且还要装一个臃肿的VS14才能安装),非常麻烦。而少数用pycryptodome模块的也出现了报错/不可行的结果。最后是看了很多github大佬的源码,结合网上的思路,才重新写了出来。在此分享出来,提供一个少走弯路的解决方案
这个实战将详细的展示一次手动分析解决动态爬取,同时还会接触加密形post请求,更好的解决一些刁难的动态包
参考文章/代码
前置需求
可选:fiddler 捉包工具 (官网下载)
可选:了解一点AES,RSA加密
任一浏览器
pycryptodome模块 (直接pip安装)
base64及binascii模块 (直接导入)
可选是指:如果你要深入了解如何找到加密方法,就选
正文
开始之前感谢网易云给我带来的音乐和欢乐,记住爬取需适度
结构分析
我们要爬的是歌曲的评论,而歌曲的来源有多种,有的来源于专辑,有的来源于歌单,有的来源于歌手页;而歌单和专辑的来源又有多种。所以爬取多个歌曲的评论之前,我们要分析一下信息的结构,最好写下来,这样头脑会更清晰减少代码修改量。这里放出一张我自己整理的结构,并选择一条线路来实现(发现音乐→→歌单→→歌曲→→评论)
至于上图所列的其他信息,读者可以过完这个实战后自己动手实现,但是要注意的是:某些信息是无法直接通过网页源码提取出来的,需要通过加密的动态包(其实是API)获得,如果有需要的话我可能会出一篇文章总结网易云音乐的API
收集歌单id
每个歌单都有唯一的id,通过http://music.163.com/playlist... 这个链接就可以找到歌单,所以第一步我们要收集发现音乐下的多个歌单id
首先进入官网的“发现音乐”的“歌单”一栏,这里可以看到很多高分歌单,先到处点一下,可以发现链接是在改变的,说明部分数据不是动态加载的,可通过网页源码获得。最后发现链接有cat,order,offset,和limit四个对我们有用的参数,cat是分类,order是排序,offset=(页数-1)*35,limit=35。还有注意使用前要把链接的井号和一个斜杠去掉,否者会导致网页源码缺失。
先随便找一条链接requests一下先,可以发现目标信息是完整的,和F12看到的源码一样,那歌单id就可以放心提取了,具体用什么方法取决于读者。参考代码:
def get_playlists(pages,order,cat):#页数(一页获取35个歌单id),排序,分类
playlist_ids = []
for page in range(pages):
url = 'http://music.163.com/discover/playlist/?order={}&cat={}&limit=35&offset={}'.format(order,cat,str(page*35))
print(url)
r = requests.get(url,headers=headers)
playlist_ids.extend(re.findall(r'playlist\?id=(\d+?)" class="msk"',r.text))
return playlist_ids
收集歌单內歌曲id
每个歌单都有多首歌曲,所以第二步我们要获取每个歌单下的所有歌曲id顺便把歌单名也获取。
歌单链接是http://music.163.com/playlist...,先随便找一个requests一下先,目标没缺失但是requests结果是和F12源码是不同的,筛选时请照着requests结果写(requests结果只有id和歌名,暂时够用那就这样吧)
另一种方法是通过API(http://music.163.com/weAPI/v3...)获取,包含更全的信息(包括歌手,所属专辑,歌单介绍等),因涉及加密和js调试较麻烦就不先介绍了(读者可以根据本文的加密算法详解自行调试),以后会写篇文章介绍各种API。
参考代码:
def get_songs(playlist_id='778462085'):
r = requests.get('http://music.163.com/playlist?id={}'.format(playlist_id),headers=headers)
song_ids = re.findall(r'song\?id=(\d+?)".+?',r.text)#歌id列表
song_titles = re.findall(r'song\?id=\d+?">(.+?)',r.text)#歌名列表
list_title = re.search(r'>(.+?) - 歌单 - 网易云音乐',r.text).group(1)#歌单名
list_url = 'http://music.163.com/playlist?id='+playlist_id #歌单链接
return [song_ids, song_titles, list_title, list_url]#一次性返回这些信息给评论爬取器
请求动态数据(评论)
进入某首歌http://music.163.com/song?id=...,很自然就想到requests一下,然而这不会得到任何评论信息,因为评论区是动态加载的(翻页链接不变,动态标志),所以打开F12捉包吧,在xhr中查看response很快找到
捣弄过后发现,请求链接中“R_SO_4_”后接的是歌曲的id,同一首歌下不同页数的动态包的请求链接除csrf_token外是相同的。
请求类型为post,需要两个参数,无论是刷新还是评论翻页这两个参数都会变,应该是加密过的。
先不理加密先,尝试把第一页的两个参数传给请求链接是能获得数据的,对应第一页的评论,尝试把csrf_token参数去除,还是能获取数据,所以csrf_token参数可以不要。我们大胆一点,继续把这对参数传给不同歌曲的请求链接,发现都能获取对应的第一页评论;而把第二页的两个参数传给不同歌曲的请求链接,就会得到对应第二页评论,以此类推。所以得出结论,任一页数的两个参数对不同歌曲是通用的,第n页的参数post过去会得到第n页的评论。这样就成功绕过了加密问题。
然而还是存在缺点的,请看下面对话
A:哈哈哈——这样就不用理会怎样加密了!!!
B:只爬前几页的话确实是的,但是如果你要爬很多页或全部爬取怎么办,那些10W+评论的歌曲难道你要手动复制粘贴5000+对参数吗?
C(对着A):你不知道网易云音乐的API是共用一套加密算法的吗?如果你想爬评论以外的信息怎么办?
所以如果你要大量爬取评论/各种信息时,加密算法就显得很重要。具体怎样加密可以不用了解,直接套用就可(代码在最后),想了解的话继续往下看。
这里简单提供一下获取评论的参考思路,交给读者补全
def get_comments(arg): # 接收get_songs方法返回的数据,爬取页数等
post_urls = [......] # 通过get_songs方法返回的数据构造每首歌的请求链接列表
data = [{}] # 手动写入或加密算法生成
for i in range(len(post_url)): # 爬每首歌评论
#for j in range(pages): # 如果每首歌要爬多页,那要再设一个循环
r = requests.post(post_urls[i],data=data,headers=headers)
print(r.json()) # 剩下解析json数据并写入容器。其中json数据可能会有坑,详看github中的代码。
''''''
最终带加密算法的爬评论代码:github(代码笨了,应该一次性生成多组params和encSecKey再索引使用而不是每首歌都加密一次,懒得改了....)(暂时是单线程,比较慢,有时间加个多线程下去)
(可选)加密算法详解
可以确定params和encSecKey这两个参数是加密过的了,里面包含着页数信息,服务器收到参数,解密后根据内容返回信息。通常这种加密都是通过js加密的,所以首先要找到这个有加密算法的js。
通过F12查看包的initiator可以得知其发起者是core.js,马上去JS包那里找。
其内容是巨量堆砌在一起的,丢去排版一下后拷贝到本地文件中,代码量20000+,先用搜索一下params和encSecKey看看能否定位到加密算法那里。
结果是可行的,看到这个熟悉的data就知道加密函数是window.asrsea(),接收了4个参数!!!又加大了分析难度,根本不知道这些参数是什么。这时就要上fiddler了来调试js了,能实现本地js覆盖原来的js,让浏览器执行本地的js。(使用fiddler前请配置好代理,网上查)
fiddler调试配置
选到autoresponder,把三个选项全勾上,然后按add rule,添加要替换的js,如图添加rule,第一栏是待替换的js(就是那个core.js包的链接),第二栏是替换物的绝对路径(就是拷贝回来修改过的js文件的绝对路径),然后按save
修改js文件,控制台输出关键值
对刚才找到的代码块进行修改,添加5条语句让它分别输出四个参数和params,通过比较包和输出的params确定成组的4个参数。
注意:①拷贝回来的js一定要趁热修改趁热使用,原来的core.js一段时间后会变动(如上面两幅图第一个参数中的j3x变成了j5o),所以不要照抄我的,以你拷贝回来的为准
②如果修改后的js没在浏览器中加载,fiddler也捉不到这个core.js的话,请清空浏览器的缓存再尝试
寻找参数规律
配置好fiddler修改好js后马上运行fiddler,然后马上打开浏览器,开启F12选择console控制台监测输出,打开测试歌曲链接http://music.163.com/#/song?i...,可以看到有很多组输出,我们可以通过比较评论包的params参数和输出的params参数找到评论对应的那组参数(如下图红色圈着的那组)
我们可以看到,不同组的第二第三第四个输出值都是一样的,所以window.asrsea()除第一个参数是会变外,其余三个参数是定值。研究对象一下子减到一个。对评论来说,第一个参数是'{rid: "R_SO_4_411907742", offset: "0", total: "true", limit: "20", csrf_token: "f15b016ca1e43812f78a260998917527"}' ,是json object,为了搞清其变化规律,我们把评论翻到第二页看看会变成怎样。第二页评论得到'{rid: "R_SO_4_411907742", offset: "20", total: "false", limit: "20", csrf_token: "f15b016ca1e43812f78a260998917527"}'......
按多几页,多切几首歌后就会总结出第一个参数的规律,这个object包含了歌曲id,页数等信息,应该是被加密之前的原始数据。
rid——‘R_SO_4_’加上歌曲id(其实rid参数可以不要,刚才说过任一页数的两个参数对不同歌曲是通用的,可以让它为空字符串)
offset——字符化的数字,值等于(页数-1)*20
total——第一页是"true",其余页数是"false"
limit——固定"20"
csrf_token——之前遇到过,无规律字符串(这个可以不要,直接让它为空字符串)
window.asrsea()接收的第一个参数还经过JSON.stringify()处理,让其变成了json数据,这个过程我们可以用python的json.dumps(dict)实现
#window.asrsea()接收参数
'{......}'#第一参数,那个json数据
'010001'#第二参数
'00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'#第三参数
'0CoJUm6Qyw8W8jud'#第四参数
加密算法
参数是搞明白了,但是如何加密还是不清楚,于是回到刚才的js文件中。追踪window.asrsea()函数,发现它指向一个叫d的函数,仔细研究许久后大概知道加密算法
params经两次aes加密获得,模式为CBC,偏移量为b'0102030405060708'。第一次aes加密的明文是处理后的第一参数(具体处理方法看代码),密匙为第四参数;第二次aes加密的明文是第一次加密获得的密文,密匙是那个随机数,之后获得params,一些具体处理看代码
encSecKey经过rsa加密,明文和aes第二次加密同一个随机数,公匙是(第二参数,第三参数)。
参数处理细节处理请看代码
import json
from Crypto.Cipher import AES #新的加密模块只接受bytes数据,否者报错,密匙明文什么的要先转码
import base64
import binascii
import random
secret_key = b'0CoJUm6Qyw8W8jud'#第四参数,aes密匙
pub_key ="010001"#第二参数,rsa公匙组成
modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
#第三参数,rsa公匙组成
#生成随机长度为16的字符串的二进制编码
def random_16():
return bytes(''.join(random.sample('1234567890DeepDarkFantasy',16)),'utf-8')
#aes加密
def aes_encrypt(text,key):
pad = 16 - len(text)%16 #对长度不是16倍数的字符串进行补全,然后在转为bytes数据
try: #如果接到bytes数据(如第一次aes加密得到的密文)要解码再进行补全
text = text.decode()
except:
pass
text = text + pad * chr(pad)
try:
text = text.encode()
except:
pass
encryptor = AES.new(key,AES.MODE_CBC,b'0102030405060708')
ciphertext = encryptor.encrypt(text)
ciphertext = base64.b64encode(ciphertext)#得到的密文还要进行base64编码
return ciphertext
#rsa加密
def rsa_encrypt(ran_16,pub_key,modulus):
text = ran_16[::-1]#明文处理,反序并hex编码
rsa = int(binascii.hexlify(text), 16) ** int(pub_key, 16) % int(modulus, 16)
return format(rsa, 'x').zfill(256)
#返回加密后内容
def encrypt_data(data):#接收第一参数,传个字典进去
ran_16 = random_16()
text = json.dumps(data)
params = aes_encrypt(text,secret_key)#两次aes加密
params = aes_encrypt(params,ran_16)
encSecKey = rsa_encrypt(ran_16,pub_key,modulus)
return {'params':params.decode(),
'encSecKey':encSecKey }
关于API
刚才分析加密算法进行fiddler调试的时候,有没有注意到有很多组输出,本文只选了评论那组进行分析。细心的读者可以发现,不同组的第一个输出都是一个json object,内容具有可读性且输出的params都能找到对应的xhr包。没错,那就是明文,不同的明文对应不同的API,明文通过加密算法得到的params和encSecKey由对应API接收,然后返回对应的信息。
API有很多个,除了获取评论的API外,还有歌词API,歌单API,专辑API,搜索API,mp3API等等,甚至还有签到API(这个要登录先,所以也有登录API)
API的链接很好找,捉下包就可以了;所以要利用这些API,重点是找到对应明文的规律,就像上面分析json object一样,要多次采样多次试验。在面对数十个API时,无疑是非常耗时的,读者可以自行探索。
python爬取网易云音乐_Python 从零开始爬虫(七)——实战:网易云音乐评论爬取(附加密算法)...相关推荐
- python爬取js动态网页_Python 从零开始爬虫(八)——动态爬取解决方案 之 selenium
selenium--自动化测试工具,专门为Web应用程序编写的一个验收测试工具,测试其兼容性,功能什么的.然而让虫师们垂涎的并不是以上的种种,而是其通过驱动浏览器获得的解析JavaScript的能力. ...
- Python的网易云音乐数据分析系统 爬虫 echarts可视化 Flask框架 音乐推荐系统 源码下载
Python的网易云音乐数据分析系统 爬虫 echarts可视化 Flask框架 音乐推荐系统 一.技术说明 网易云音乐数据(歌单.用户.歌词.评论)Python爬取Flask框架搭建ECharts. ...
- Python网络爬虫数据采集实战:同花顺动态网页爬取
前文的爬虫都建立在静态网页基础之上,首先通过请求网站url获取到网页源代码.之后对源代码进行信息提取进而存储即可,本文则针对动态网页进行数据采集,首先介绍Ajax相关理论,之后实战爬取同花顺动态网页, ...
- python网络爬虫应用_python网络爬虫应用实战
原标题:python网络爬虫应用实战 Python这门编程语言包罗万象,可以说掌握了python,除了一些特殊环境和高度的性能要求,你可以用它做任何事. Python作为一门脚本语言,它灵活.易用.易 ...
- python多线程爬虫 爬取多个网页_python多线程爬虫爬取顶点小说内容(BeautifulSoup+urllib)...
思路 之前写过python爬取起点中文网小说,多线程则是先把爬取的章节链接存到一个列表里,然后写一个函数get_text每次调用这个函数就传一个章节链接,那么就需要调用n次该函数来获取n章的内容,所以 ...
- python爬取豆瓣读书简单_Python用16行代码就搞定了爬取豆瓣读书页面
点击蓝字"python教程"关注我们哟! 我们一直说Python比较简单,代码体量没有别的程序那么大,对于初学者,尤其是零编程基础的初学者来说,感触没有那么明显,那么今天就让你见识 ...
- python爬取豆瓣读书简单_Python用16行代码就搞定了爬取豆瓣读书页面!
我们一直说Python比较简单,代码体量没有别的程序那么大,对于初学者,尤其是零编程基础的初学者来说,感触没有那么明显,那么今天就让你见识一下:爬取豆瓣读书页面,Python用16行代码就搞定了! p ...
- python编程理论篇_Python爬虫入门实战之猫眼电影数据抓取(理论篇)
前言 本文可能篇幅较长,但是绝对干货满满,提供了大量的学习资源和途径.达到让读者独立自主的编写基础网络爬虫的目标,这也是本文的主旨,输出有价值能够真正帮助到读者的知识,即授人以鱼不如授人以渔,让我们直 ...
- python爬虫入门实战争胜法_Python爬虫入门实战之猫眼电影数据抓取(理论篇)
前言 本文可能篇幅较长,但是绝对干货满满,提供了大量的学习资源和途径.达到让读者独立自主的编写基础网络爬虫的目标,这也是本文的主旨,输出有价值能够真正帮助到读者的知识,即授人以鱼不如授人以渔,让我们直 ...
最新文章
- JDBC与数据库连接工具对比分析
- python3 限定方法参数 返回值 变量 类型
- pycharm新建文件夹时新建python package和新建directory有什么区别?
- 如何异步提交表单 如何异步跨域提交表单
- 【计算机组成原理】乘法阵列器
- glibc与MSVC CRT(转载)
- Eclipse中部分快捷键
- 请编写一个方法,实现整数的乘法、减法和除法运算(这里的除指整除)。只允许使用加号。
- Spring Cloud Alibaba —— Sentinel 入门
- 【转】90后还过五四吗?这些“脸熟”的过来人送给青年10句忠告
- Fortran入门教程(八)——子例程及函数
- logistic回归分析优点_数据统计的理解和应用(十二)多因素logistic回归案例分析...
- 支付宝登录java和android
- java TIFF转JPG并压缩
- AI算法之Encoder-Decoder 和 Seq2Seq
- 网上插画教学哪家质量好,汇总5大插画培训班
- python excel库pip install_超全整理|Python 操作 Excel 库 xlwings 常用操作详解!
- [CEOI2017]One-Way Streets
- python期末大作业 邮箱小程序
- win10+docker+laradock最新安装
热门文章
- Java排序算法之归并排序
- 【LeetCode】87. Scramble String
- Java 编程下使用 Class.forName() 加载类
- dedecms织梦系统后台验证码图片不显示的解决方法
- 解决Jupyter notebook安装后不自动跳转网页的方法
- Linux permission denied解决方法
- [c#]解决方案:需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。
- 在PL/SQL里直接插入日期时提示 is not a valid date and time的解决方法
- SQL查询四舍五入 解决方法
- 如何复制静态文件以使用Webpack构建目录?