博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何爬取i春秋网课
阅读量:2061 次
发布时间:2019-04-29

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

单位购买了几个i春秋上的网络课程,用于业务培训,领导希望将这些课程爬取下来,可以离线观看,将这个任务交给我,经过一番努力,摸清了i春秋前端的视频解密的过程,实现了这个爬虫,现将整个过程记录下来。

i春秋(https://www.ichunqiu.com/)是国内一家知名的网络安全类媒体,上面有许多非常好的技术资料和视频课程。
image.png

环境配置

使用的环境

  • windows 7 x64
  • python3.7(Anaconda 3)
  • vscode
  • 火狐开发版
    需要使用的python包有
  • requests 用于模拟http请求
  • bs4 用于解析html文档
  • pycryptodome 用于AES解密
    这些包可以使用清华的pip源进行安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pycryptodome

爬虫原理分析

网站登陆过程

使用火狐开发版,按F12,调出开发者工具,在网络选项卡中可查看网站前后端交互的所有HTTP请求和响应

image.png
登陆操作请求的地址为https://user.ichunqiu.com/login/normal
使用post方法,提交的参数为
image.png
返回的响应为
image.png

视频播放过程

登陆成功后,我们打开购买的视频

分析html结构,发现了页面中含有一个m3u8的地址

访问这个由m3u8地址,里面的内容为

#EXTM3U#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=10000000/57765/57767/57769_d/720/57769.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5000000/57765/57767/57769_d/480/57769.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2500000/57765/57767/57769_d/320/57769.m3u8

这是三种不同清晰度的视频的uri,以720p为例

完整的url为https://mv.ichunqiu.com/57765/57767/57769_d/320/57769.m3u8
查看其内容,发现这是一个标准的m3u8文件

#EXTM3U#EXT-X-VERSION:3#EXT-X-TARGETDURATION:29#EXT-X-MEDIA-SEQUENCE:0#EXT-X-KEY:METHOD=AES-128,URI="http://www.ichunqiu.com/videokey?vid=57769",IV=0x99b74007b6254e4bd1c6e03631cad15b#EXTINF:23.333333,577690.ts#EXTINF:20.833333,577691.ts#EXTINF:20.833333,577692.ts#EXTINF:20.250000,577693.ts#EXTINF:20.833333,577694.ts#EXTINF:20.833333,577695.ts#EXTINF:22.000000,577696.ts#EXTINF:20.833333,577697.ts#EXTINF:10.416667,577698.ts#EXTINF:20.833333,577699.ts#EXTINF:28.583333,5776910.ts#EXTINF:20.000000,5776911.ts#EXTINF:10.416667,5776912.ts#EXTINF:20.833333,5776913.ts#EXTINF:20.833333,5776914.ts#EXTINF:20.833333,5776915.ts#EXTINF:20.833333,5776916.ts#EXTINF:20.833333,5776917.ts#EXTINF:20.833333,5776918.ts#EXTINF:20.833333,5776919.ts#EXTINF:20.833333,5776920.ts#EXTINF:20.833333,5776921.ts#EXTINF:16.333333,5776922.ts#EXTINF:20.833333,5776923.ts#EXTINF:20.833333,5776924.ts#EXTINF:20.833333,5776925.ts#EXTINF:20.833333,5776926.ts#EXTINF:20.833333,5776927.ts#EXTINF:20.833333,5776928.ts#EXTINF:11.708333,5776929.ts#EXTINF:20.833333,5776930.ts#EXTINF:20.833333,5776931.ts#EXTINF:20.833333,5776932.ts#EXTINF:20.833333,5776933.ts#EXTINF:20.833333,5776934.ts#EXTINF:16.583333,5776935.ts#EXTINF:20.833333,5776936.ts#EXTINF:25.000000,5776937.ts#EXTINF:20.833333,5776938.ts#EXTINF:20.833333,5776939.ts#EXTINF:17.500000,5776940.ts#EXTINF:20.833333,5776941.ts#EXTINF:20.833333,5776942.ts#EXTINF:20.833333,5776943.ts#EXTINF:20.833333,5776944.ts#EXTINF:10.416667,5776945.ts#EXTINF:20.833333,5776946.ts#EXTINF:28.125000,5776947.ts#EXTINF:20.833333,5776948.ts#EXTINF:10.416667,5776949.ts#EXTINF:20.833333,5776950.ts#EXTINF:13.125000,5776951.ts#EXT-X-ENDLIST

视频的内容使用AES128进行了加密,按照常理解密的密钥可通过请求

http://www.ichunqiu.com/videokey?vid=57769而得到,iv为解密的另一个参数,这里用16进制的字符串表示。
但是访问http://www.ichunqiu.com/videokey?vid=57769,发现什么响应也没有。
我们播放一个视频,看一下前端是如何获取解密密钥的,在网络请求中搜索"key"发现了一个HTTP请求很可疑
image.png
这个GET请求的url为https://www.ichunqiu.com/video/key/57769,后面这个数字与前面vid一致,应该是视频的id,响应的内容为

{
"code":1, "message":"ok", "data":"UQIFAlwHWwFVXFAGVQMHBV5VAQABW1MMDAoBAQldN2VXBA==", "key":"U18IBQNRWVQPXAZUWFZTDFEFXVdWUAMEBwEABw5UN2VWUw==", "t":"12-43-09"}

AES的解析密钥应该是一个16字节的字符串,在这里我猜测前端根据这些参数来生成了解密密钥,下面需要对前端加载的js进行分析,这个HTTP请求肯定是某个js发出的。

通过检索"video/key"关键字,找到了一个js文件
image.png
在10979行找到一个名为handleKeyResponse的函数
在11068行找到生成密钥的代码
image.png
代码为

var m = new DataView(new Int8Array(hexAesStr(e(Base64.decode(response.data), c))).buffer);

下面通过单步调试,看一下这个密钥长啥样,在11067,11068,11073行设置断点,刷新页面,页面在断点处停下了

image.png
执行一步跳到11068行
在控制点打印出局部变量看一下
image.png
这是一串16进制字符串,有可能是我们苦苦寻找的密钥,但是还需要进一步印证一下
再执行一步,代码跳到11073行
打印segment打一下(注:在mu38格式中的segment代表一个ts片断)
image.png
可以看到segment中含有密钥和iv,将其转化为16进制看一下

In [1]: key_bytes = ['2907804363','3384624288','3384624288','2294155734']In [2]: a = map(lambda x: hex(int(x))[2:],key_bytes)In [3]: aOut[3]: In [4]: ''.join(a)Out[4]: 'ad5192cbc9bd44a0c9bd44a088be09d6'In [5]: iv_bytes = ['2578923527','3055898187','3519471670','835375451']In [6]: ''.join(map(lambda x: hex(int(x))[2:],iv_bytes))Out[6]: '99b74007b6254e4bd1c6e03631cad15b'

这个密钥与上面的e(Base64.decode(response.data), c)返回的结果是一致的,iv也与m3u8文本中的一致,证实了我的判断

下面来分析一下这个e函数是怎么实现的
将相关代码单独复制出来,得到下面的代码

function base64ToString() {
function e(e, n) {
var a = i(), o = sprintf('%02x', a), s = e.substr(a % 32, 2); if (o != s) return $.fn_ajax({
url: base_url + 'Common/ajaxErrorUpload', options: {
os: 'Invalid HLS key', method: 'video/key', description: JSON.stringify({
date: new Date, str: JSON.stringify(response) }) }, callBack: function (e) {
} }), ''; var u = e.replace('', e.substr(a % 32, 2)), l = e.substr(0, a % 32), c = e.substr(a % 32, e.length); c = c.replace(c.substr(0, 2), ''), u = l + c; for (var d = '', h = '', p = 0; p < u.length; p += 2) {
var f = u.substr(p, 2); h = '' == h ? r(o, n) : r(h, n), d += t(f, h) } return d } function t(e, t) {
for (var i = e.length, r = t.length, a = '', o = 0; o < i; o++) a += n(e[o], t[o % r]); return a } function n(e, t) {
for (var n, i = '', r = t.length, a = 0; a < e.length; a++) n = a % r, i += String.fromCharCode(e.charCodeAt(a) ^ t.charCodeAt(n)); return i } function i() {
var e = 180, t = parseInt(response.t.split('-')[0]), n = parseInt(response.t.split('-')[1]), i = parseInt(response.t.split('-')[2]), r = 3600 * t + 60 * n + i, a = r % e; return (r - a) / e % 128 } function r(e, t) {
for (var n, i = parseInt(e, 16), r = - 1, a = - 1, o = 0; o < 36; o++) {
for (var s = 0; s < 32; s++) if (i == t[o][s]) {
r = o, a = s; break } if (r >= 0 && a >= 0) break } return n = sprintf('%02x', (7 * r + a) % 255) } function a(e) {
var t = e, n = t[0], i = t[1], r = t[2]; return parseInt(((1 << 24) + (n << 16) + (i << 8) + r).toString(16).slice(1), 16) } var o = document.createElement('canvas'); if (o.getContext) {
var s, u, l = document.getElementById('c579ad6c4f173b8a74d4cc5611dcc230'), c = new Array; o.width = l.width, o.height = l.height; var d = o.getContext('2d'); d.drawImage(l, 0, 0); for (var h = 0; h < l.width; h++) {
c[h] = new Array; for (var p = 0; p < l.height; p++) {
s = d.getImageData(h, p, 1, 1), u = s.data; var f = a(u); c[h][p] = f } } var m = new DataView(new Int8Array(hexAesStr(e(Base64.decode(response.data), c))).buffer); return segment.key.bytes = new Uint32Array([m.getUint32(0), m.getUint32(4), m.getUint32(8), m.getUint32(12)]), _l = m, finishProcessingFn(null, segment) } alert('您的浏览器版本暂不支持此功能')}

这段代码先找到页面中一个id为c579ad6c4f173b8a74d4cc5611dcc230的图片,将其中的每个像素读取出来,经过a函数一系列的变换,赋值给一个二维数组c,再调用函数e将https://www.ichunqiu.com/video/key/57769返回的response中的data和c进行处理,得到了解密的密钥

在页面找到这个图片,发现这个图片是用base64编码的

代码实现

要想使用脚本爬取所有视频,就必须实现这个生成密钥的过程。

我决定使用python来重写上面这些生成密钥的js代码,这是整个过程中最辛苦的部分,很繁锁,主要是代码中的变换我看不懂,依葫芦画瓢,运行出现了一个小bug,不过通过调试比对也轻松解决了。
get_aes_key.py

#-*- coding:utf-8 -*-import base64from PIL import Image#全局变量response = {
}def n(e,t): #print(e,type(e)) #print(t,type(t)) i = '' r = len(t) for a in range(len(e)): n = a % r i += chr(ord(e[a]) ^ ord(t[n])) return idef t(e, t): i = len(e) r = len(t) a = '' for o in range(i): a += n(chr(e[o]), t[o % r]) return adef r(e, t): i = int(e,16) r = -1 a = - 1 for o in range(36): for s in range(32): if i == t[o][s]: r = o a = s break if (r >= 0 and a >= 0): break return hex((7 * r + a) % 255)[2:]def i(): e=180 t,n,i = map(lambda x: int(x),response['t'].split('-')) r = 3600 * t + 60 * n + i a = r % e return int((r - a) / e % 128)def e(e, n): a = i() o = hex(a)[2:].encode('utf-8') s = e[a%32:a%32+2] #print(a,o,s) u = e.replace(b'',s) l = e[:a % 32] c = e[a%32:a%32+len(e)] c = c.replace(c[:2], b'') u = l + c d = '' h = '' p = 0 for p in range(0,len(u),2): f = u[p:p+2] h = r(o, n) if '' == h else r(h, n) d += t(f, h) return ddef a(e): t = e n = t[0] i = t[1] r = t[2] return int(hex((1 << 24) + (n << 16) + (i << 8) + r)[2:][1:],16) #将base64编码的图片,保存为1.pngdef save2png(): png_bs64_string = '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' # 将 base64 字符串解码成图片字节码 image_data = base64.b64decode(png_bs64_string) # 将字节码以二进制形式存入图片文件中,注意 'wb' with open('1.png', 'wb') as f: f.write(image_data)#根据response的结果和1.png生成密钥def get_aes_key(res): global response response = res img=Image.open("1.png") img_array=img.load() width,height = img.size data = [] for i in range(width): i_v = [] for j in range(height): i_v.append(a(img_array[i,j])) data.append(i_v) return (e(base64.b64decode(response['data']), data))

获取了密钥,下载视频就很简单了,其它部分的代码如下。

爬虫的代码如下

ichunqiu_spider.py

#-*- coding:utf-8 -*- #ichunqiu_spider.py#使用方法 python ichunqiu_spider.py [网课的地址]#如python ichunqiu_spider.py https://www.ichunqiu.com/course/57769import requestsfrom requests.packages import urllib3from Crypto.Cipher import AESimport refrom bs4 import BeautifulSoupfrom get_aes_key import get_aes_keyimport os,sysurllib3.disable_warnings()#全局变量,requests请求所使用headers字段 headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0'}#全局变量,保存requests会话s = requests.Session()#用来扩展AES密钥,如果value的长度不足16个字节,在其后面填充0def fill_character(value): if len(value) < 16: value = value.ljust(16, '\000') elif len(value) > 16: value = value[:16] return value#获取视频加密所使用的AES密钥def get_key(video_id): key_url= 'https://www.ichunqiu.com/video/key/%s' % video_id res1 = s.get(key_url,headers=headers,verify=False) if res1.status_code == 200: response = res1.json() #根据response中的值来生成密钥 return get_aes_key(response) return ''#根据iv和key对ts视频内容进行解密def decrypt_single_ts(ts,iv_str,key_str): #将iv由十六进制字符串转化为byte iv = bytes.fromhex(iv_str) #将密钥由十六进制字符串转化为byte key = bytes.fromhex(key_str) #计算需要填充的字节长度 pad_len = AES.block_size - len(ts) % AES.block_size #若ts长度不是的AES要求的分组长度的整数倍,对其填充0 if pad_len != AES.block_size: ts = ts[:-pad_len] + bytes([0] * pad_len) #解密操作 cipher = AES.new(key, AES.MODE_CBC, iv=iv) out_data = cipher.decrypt(ts) #从解密结果中去掉填充部分,得到ts的解密内容 if pad_len != AES.block_size: out_data = out_data[:-pad_len] return out_data #根据m3u8视频流的url,爬取视频内容#m3u8_url为m3u8的地址,title为视频保存的文件名def handle_m3u8_data(m3u8_url,title): res = s.get(m3u8_url,headers=headers,verify=False) if res.status_code == 200: #这是一个文本文件 data = res.text.strip() #print data #使用正则表达式,提取出加密方法,视频id,和iv aes_method,video_id,iv_str = re.findall(r'#EXT-X-KEY:METHOD=(.*?),URI="http:\/\/www.ichunqiu.com\/videokey\?vid=(\d+)",IV=0x(.*?)\n',data)[0] #使用正则表达式提取来ts的uri ts_uri_list = re.findall(r'(\d+.ts)\n',data) #根据video_id获取解密密钥 key_str = get_key(video_id) print(key_str) print(iv_str) content=b'' #下载所有的ts文件 for ts_url in ts_uri_list: #构造完成的url url_base = m3u8_url[:m3u8_url.rfind('/')+1] res1 = s.get(url_base+ts_url,headers=headers,verify=False) if res1.status_code == 200: #对ts的内容进行解密,并依次拼接成一个完整的文件 content += decrypt_single_ts(res1.content,iv_str,key_str) #保存输出文件 open('%s' % title,'wb').write(content) #爬虫主函数def spider(): global s #登陆url url = 'https://user.ichunqiu.com/login/normal' #登陆信息 data = {
'redirect_url':"https://www.ichunqiu.com/", 'appid':'5af018bda55004e1', 'account':'xxxxxxxxxxxxxxxxxxxxxxxxxxx', 'password':'xxxxxxxxxxxxxxxxxxxxxxxxxxx', 'captcha':'', 'mt':'1577261775197', 'rs':'e8c48853ab79882bc54ef7f6b5452c98' } res = s.post(url=url,data=data,headers=headers,verify=False) if res.status_code == 200: #若登陆成功 if res.json()['code'] == 0: #m3u8_url = 'https://mv.ichunqiu.com/144/279/147_d/147.m3u8' #m3u8_url = 'https://mv.ichunqiu.com//57765/57767/57769_d/320/57769.m3u8' #course_url = 'https://www.ichunqiu.com/course/57765' #课程主页的url course_url = sys.argv[1] res1 = s.get(course_url,headers=headers) if res1.status_code == 200: #使用bs4库来解析html文档 soup = BeautifulSoup(res1.text,'lxml') #课程的名字 course_name = soup.title.text.strip().split('_')[0] print(course_name) for video_catalog_list in soup.find('div',class_='video_catalog').find_all('div',class_= 'video_catalog_list'): #课程中章的名字 zhang_title = video_catalog_list.find('div',class_='v_catalog_zhang listName')['title'].strip() print(zhang_title) #构造存储路径 path = 'data\\%s\\%s'%(course_name,zhang_title) #若该目录不存在,创建该目录 if not os.path.isdir(path): #os.makedirs会递归创建目录,os.mkdir只会在当前目录下创建目录 os.makedirs(path) #找到当前章下面的课时 for a in video_catalog_list.find_all('a',class_='_courseList'): #课时的title keshi_title = a['title'] #课时页面的url keshi_url = a['href'] print(keshi_title) #请求课时页面 res2 = s.get(keshi_url,headers=headers,verify=False) if res2.status_code == 200: #利用正则表达式找到m3u8地址 m3u8_url1 = re.findall(r'data-video-url="(https.*?m3u8)',res2.text)[0] index = m3u8_url1.rfind('/') #一个视频有多个清晰度选项,选择720P,构造url m3u8_url = m3u8_url1[:index+1]+'720/'+m3u8_url1[index+1:] print(m3u8_url) #保存的视频文件名 filename = '%s\\%s.mp4' % (path,keshi_title) #若该文件不存在 if not os.path.isfile(filename): try: #下载该视频 handle_m3u8_data(m3u8_url,filename) except Exception as e: #出错的话不处理 print(e) print('下载完毕') spider()

参考资料

  • m3u8解密
    https://blog.csdn.net/weixin_42572656/article/details/96981292
    https://www.zuowting.top/2018/Python%E8%A7%A3%E5%AF%86m3u8%E8%A7%86%E9%A2%91.html
  • AES加解密
    https://blog.csdn.net/Vieri_32/article/details/48345023
  • 火狐如何调试js
    https://developer.mozilla.org/zh-CN/docs/Tools/Debugger
  • 如何调试python
    https://www.cnblogs.com/jing1617/p/9396617.html
你可能感兴趣的文章
PL/SQL学习笔记
查看>>
如何分析SQL语句
查看>>
结构化查询语言(SQL)原理
查看>>
SQL教程之嵌套SELECT语句
查看>>
几个简单的SQL例子
查看>>
日本語の記号の読み方
查看>>
计算机英语编程中一些单词
查看>>
JavaScript 经典例子
查看>>
判断数据的JS代码
查看>>
js按键事件说明
查看>>
AJAX 初次体验!推荐刚学看这个满好的!
查看>>
AJAX 设计制作 在公司弄的 非得要做出这个养的 真晕!
查看>>
AJAX 自己研究玩的
查看>>
javascript(js)数组操作
查看>>
用JavaScript脚本实现Web页面信息交互
查看>>
window 窗口对象操作
查看>>
公司一位老员工愤然离去的留信!崩溃!
查看>>
C#技巧:网页表单自动填写技术(以gmail为例)
查看>>
C#基础概念二十五问
查看>>
C#在Excel中将连续多列相同数据项合并
查看>>