博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python 从零开始爬虫(七)——实战:网易云音乐评论爬取(附加密算法)
阅读量:6160 次
发布时间:2019-06-21

本文共 7635 字,大约阅读时间需要 25 分钟。

前言

某宝评论区已经成功爬取了,jd的也是差不多的方法,说实话也没什么好玩的,我是看上它们分析简单,又没加密才拿来试手的。如果真的要看些有趣的评论的话,我会选择网易云音乐,里面汇聚了哲学家,小说家,story-teller,皮皮虾等各种人才,某些评论非常值得收藏(甚至开了一个歌单专门收藏它们)。竟然这么好玩,何不尝试把他们爬取下来呢?

图片描述

所以这个(大规模)网易云音乐评论爬取project就成型了

整个过程并不顺利,网上找到的解决方案清一色用的是pycrypto模块(已经没人维护,且还要装一个臃肿的VS14才能安装),非常麻烦。而少数用pycryptodome模块的也出现了报错/不可行的结果。最后是看了很多github大佬的源码,结合网上的思路,才重新写了出来。在此分享出来,提供一个少走弯路的解决方案

这个实战将详细的展示一次手动分析解决动态爬取,同时还会接触加密形post请求,更好的解决一些刁难的动态包

参考文章/代码

前置需求

可选: 捉包工具 (官网下载)

可选:了解一点AES,RSA加密
任一浏览器
pycryptodome模块 (直接pip安装)
base64及binascii模块 (直接导入)
可选是指:如果你要深入了解如何找到加密方法,就选

正文

开始之前感谢网易云给我带来的音乐和欢乐,记住爬取需适度

结构分析

我们要爬的是歌曲的评论,而歌曲的来源有多种,有的来源于专辑,有的来源于歌单,有的来源于歌手页;而歌单和专辑的来源又有多种。所以爬取多个歌曲的评论之前,我们要分析一下信息的结构,最好写下来,这样头脑会更清晰减少代码修改量。这里放出一张我自己整理的结构,并选择一条线路来实现(发现音乐→→歌单→→歌曲→→评论)

图片描述

至于上图所列的其他信息,读者可以过完这个实战后自己动手实现,但是要注意的是:某些信息是无法直接通过网页源码提取出来的,需要通过加密的动态包(其实是API)获得,如果有需要的话我可能会出一篇文章总结网易云音乐的API

收集歌单id

每个歌单都有唯一的id,通过 这个链接就可以找到歌单,所以第一步我们要收集发现音乐下的多个歌单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顺便把歌单名也获取。

歌单链接是,先随便找一个requests一下先,目标没缺失但是requests结果是和F12源码是不同的,筛选时请照着requests结果写(requests结果只有id和歌名,暂时够用那就这样吧)

另一种方法是通过API()获取,包含更全的信息(包括歌手,所属专辑,歌单介绍等),因涉及加密和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]#一次性返回这些信息给评论爬取器

请求动态数据(评论)

进入某首歌,很自然就想到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中的代码。''''''

最终带加密算法的爬评论代码:(代码笨了,应该一次性生成多组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控制台监测输出,打开测试歌曲链接,可以看到有很多组输出,我们可以通过比较评论包的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 jsonfrom Crypto.Cipher import AES  #新的加密模块只接受bytes数据,否者报错,密匙明文什么的要先转码import base64import binasciiimport randomsecret_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时,无疑是非常耗时的,读者可以自行探索。

转载地址:http://xbofa.baihongyu.com/

你可能感兴趣的文章
编译安装mysql-5.6.16.tar.gz
查看>>
活在当下
查看>>
每天进步一点----- MediaPlayer
查看>>
PowerDesigner中CDM和PDM如何定义外键关系
查看>>
跨域-学习笔记
查看>>
the assignment of reading paper
查看>>
android apk 逆向中常用工具一览
查看>>
MyEclipse 报错 Errors running builder 'JavaScript Validator' on project......
查看>>
Skip List——跳表,一个高效的索引技术
查看>>
Yii2单元测试初探
查看>>
五、字典
查看>>
前端js之JavaScript
查看>>
Log4J日志配置详解
查看>>
实验7 BindService模拟通信
查看>>
scanf
查看>>
Socket编程注意接收缓冲区大小
查看>>
SpringMVC初写(五)拦截器
查看>>
检测oracle数据库坏块的方法
查看>>
SQL server 安装教程
查看>>
Linux下ftp和ssh详解
查看>>