中国大学 MOOC 免费课程及课件爬虫
本帖最后由 729 于 2020-11-23 19:10 编辑该源码 获取的是下载地址(免费的课程视频以及课件付费课程的PDF文件 视频是经过加密的) 没有进行下载 需要下载的可以调用返回的下载地址即可
老实说 我不会排版 也是第一次写分析过程 所以有啥问题就不要见怪了哈
# 分析过程
## PDF链接分析
> 进入课程主页并全程使用fiddler进行监视
---
> 随便点击文档 在网页右下角中有对应的文档下载 获取下载链接
---
> 使用fiddler 进行搜索查找链接的源头
---
>找到下载链接的源头 获取其头部信息 进行分析
---
>查找两个数据的来源 分析信息
---
>1.分析信息得知 返回的数据是分段的 并且当contentType=3的时候 对应的是PDF文件 其中数据中有许多参数各有其他使用(name之类的) 可以自行判断
>2.分析头部信息
---
>获取termID
---
>PDF:
>1.在访问课程主页的时候获取termID
>2.使用termID获取所用的课程PDF文件对应的contentID已经s.x.id POST访问(https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLastLearnedMocTermDto.dwr)
>3.获取对应的contentID以及s.x.id后POST访问('https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr')即可获得对应的下载链接
---
#视频链接获取
>随便点击一个视频进行观看 之前的抓包可以不用清理那么快 因为也需要
>获取对应的视频下载URL(使用的是IDM下载器 所以我这直接出现 )
---
>如果没有IDM直接获取到的也可以进行分析
>当视频播放的时候fidell可以抓包到连续的访问 类型相同的
---
>分析他们的链接 看着相似 找出不同部分进行搜索,发现全部来自同一个访问的返回值
---
>对访问的URL进行查找(实际上这个URL与IDM下载器抓取到的URL是一致的 )里面有好几个URL 对应的是不同的视频清晰度以及格式 可以全部下载试一下
---
>发现其中的URL是简单的GET访问 分析其数据来源 VIDEOID signature
>发现VideoID也属于上面PDF之前的全部课程ID里面 对应contentId
---
>进行sigature头部数据分析
>进行搜索csrfkey来自访问官网时候得到的cookies里面的NTESSTUDYSI值
>bizId是contentI对应的s.x.id
>视频URL获取:
>1.进行官网访问获取csrfkey值
>2获取所有课程contentId 以及对应的s.x.id(文件ID)
>3.根据s.x.id获取对应的signature签名
>4.根据获取到的signature获取所有清晰度以及格式的视频下载链接 打开即可下载
---
# 获取课程主ID##课程主页termId
>key_id=re.search(r'\d+',re.search(r'id : ".+"',requests.get('课程主页URL').text).group()).group()`
>
---
# 获取视频目录以及ID
>payloaddata={'callCount':1,
> 'scriptSessionId':'${scriptSessionId}190',
> 'c0-scriptName':'CourseBean',
> 'c0-methodName':'getMocTermDto',
> 'c0-id':'a',
> 'c0-param0':1207358202,##课程目录ID
> 'c0-param1':0,
> 'c0-param2':'true',
> 'batchId':'1605715313492'}
> r=requests.post('https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLastLearnedMocTermDto.dwr',data=payloaddata)
> r.text#这是获得课程的所有视频的ID以及所有文档的ID
>contentType=null##一个章节
>contentType=3##获得的是文档
>contentType=2##单元测试或其他(反正不是文件)
>contentType=1##获取视频
---
# 文档下载可直接获得
>data={
> 'callCount':1,
> 'scriptSessionId':'${scriptSessionId}190',
> 'c0-scriptName':'CourseBean',
> 'c0-methodName':'getLessonUnitLearnVo',
> 'c0-id':0,
> 'c0-param0':'1214812409',##contentId
> 'c0-param1':3,
> 'c0-param2':0,
> 'c0-param3':'1216200842',##s.x.id
> 'batchId':'1605720752677',}
> url='https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr'
> r=requests.post('url',data=data)##取得其中的textOrigUrl即是下载链接
---
# 视频地址获取步骤:
1.进入官网获取cookie 其中cookie中的NTESSTUDYSI值是csrfkey值 这是官网防止跨域访问(https://www.icourse163.org)
2.获取对应的signature ('https://www.icourse163.org/web/j/resourceRpcBean.getResourceToken.rpc?csrfKey='+csrfkey)##POST访问
data={'bizId':1260493290,#s.x.id
'bizType':1,
'contentType':1}
3.获取对应的视频m3u8的URL ('https://vod.study.163.com/eds/api/v1/vod/video?videoId='+contentId+'&signature='+signature+'&clientType=1')#GET获取
clienType=1 #按照上面的获取来说应该是一个视频的标志
返回值需要的有:videoUrl以及ak 里面有好几个videoUrl对应不同的清晰度以及格式
# 源码
#encoding:utf-8
import requests
import re
from requests import Session
import json
class Mooc:
def __init__(self,url):
self.url=url
self.termId=re.search(r'\d+',re.search(r'termId : ".+"',requests.get(url).text).group()).group()
self.__GetAllId()
def __GetAllId(self):
self.dic={}##总字典
dic_con={}##每个小章节字典
file_number=0##每个小章节的文件数
AllId_url='https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLastLearnedMocTermDto.dwr'##返回课程文件ID以及内容ID
payloaddata={'callCount':1,
'scriptSessionId':'${scriptSessionId}190',
'c0-scriptName':'CourseBean',
'c0-methodName':'getMocTermDto',
'c0-id':'a',
'c0-param0':self.termId,##课程目录ID
'c0-param1':0,
'c0-param2':'true',
'batchId':'1605715313492'}##随便啦长度一样的数字吧大概
r=requests.post(AllId_url,data=payloaddata)##
list_re=re.compile(r'contentId=.+').findall(r.text)##将返回的数据进行处理分段
for d in list_re:##遍历每段数据 获取参数值
contentId=re.search(r'contentId=\d+',d)
contentType=re.search(r'\d+',re.search(r'contentType=\d',d).group()).group()##文件格式
file_id=re.search(r'\d+',re.search(r'\.id=\d+',d).group()).group()##文件ID
name=re.search(r'".+"',re.search(r'name=.+"',d).group()).group().encode('utf8').decode('unicode_escape')
if contentId and not re.search(r'isTestChecked',d):##确认是否有内容ID 以及是否是课件或者视频
file_number+=1
contentId=re.search(r'\d+',contentId.group()).group()
dic_con={'contentId':contentId,'contentType':contentType,'file_id':file_id,'name':name}
self.dic=dic_con
else:
if re.search(r'published=true',d):##大章节的确认
name_all=name##大章节名字
self.dic={}
else:
if re.search(r'isTestChecked=false',d):##小章节的确认
name_content=name##小章节名字
file_number=0##小章节文件数量
self.dic={}
#dic格式
#{'第一大章节名':{'第一小章节名':{1(文件数):{'contentId': '课程ID', 'contentType': '文件格式', 'file_id': '文件ID','name':'文件名'},...},
# '第二小章节名':...},
# '第二章大章节名':...}
def GetPdf(self):
pdf_dic={}
Pdf_url='https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr'
data={
'callCount':1,
'scriptSessionId':'${scriptSessionId}190',
'c0-scriptName':'CourseBean',
'c0-methodName':'getLessonUnitLearnVo',
'c0-id':0,
'c0-param0':'contentId',##contentId
'c0-param1':3,
'c0-param2':0,
'c0-param3':'文件ID',##文件ID
'batchId':'1606049296413',}
for p in self.dic.items():
for d in p.items():
for f in d.items():
if f['contentType']=='3':
data['c0-param0']=f['contentId']
data['c0-param3']=f['file_id']
r=requests.post(Pdf_url,data=data)##取得其中的textOrigUrl即是下载链接
pdf_dic]=re.search(r'textUrl:".+"',r.text).group()
return pdf_dic
## pdf_dic格式
## pdf_dic={'PDF文件名':下载地址,...}返回的是下载地址 没有进行下载 需要的可以自己写一个用来下载的即可
def GetTs(self):
ts_dic={}
s=Session()
s.get('https://www.icourse163.org')
csrfkey=s.cookies['NTESSTUDYSI']
data={'bizId':1260493290,#contentId
'bizType':1,
'contentType':1}
for v in self.dic.items():#dic.keys()每个大章节的名字 v.keys()每个小章节的名字
for i in v.items():
for d in i.items():
if d['contentType']=='1':
ts_list=[]
data['bizId']=d['file_id']
ts_url=s.post('https://www.icourse163.org/web/j/resourceRpcBean.getResourceToken.rpc?csrfKey='+csrfkey,data=data)
signature=json.loads(ts_url.text)['result']['videoSignDto']['signature']
vide_urls=json.loads(s.get('https://vod.study.163.com/eds/api/v1/vod/video?videoId='+d['contentId']+'&signature='+signature+'&clientType=1').text)
videos=vide_urls['result']['videos']
for url in videos:
ts_list.append(url['videoUrl'])
ts_dic['name']]=ts_list
return ts_dic
## ts_dic格式
## ts_dic={'视频文件名':[下载地址1,下载地址2,下载地址3],...}返回的是下载地址 没有进行下载 需要的可以自己写一个用来下载的即可
##使用方法:1.复制代码 创建一个类然后使用其中的方法即可 MOOC.GetPdf()和MOOC.GetTs()返回的是字典调用接口就可以啦 Hangjau 发表于 2020-11-24 20:13
老哥贴个链接呢 我也是在论坛下的一个 有点不稳定 有时直接就卡了。
https://www.52pojie.cn/forum.php?mod=viewthread&tid=1027221&highlight=fiddler当时我大概是下载了这个 我看版本信息是一样的 我没更新过 应该是这个了 tianyagd 发表于 2020-11-23 15:32
论坛 也有专门的批量下载工具。
嗯嗯确实有了 也用过 不过好像论坛的那个是要登录的 现在的代码只是用PYTHON实现了一次且不需要登录 批量下载的话懒得写上 不过已经有链接就好下载了 楼主可以讲一下分析过程么? yzqhj 发表于 2020-11-23 14:37
楼主可以讲一下分析过程么?
需要分析过程吗?我等等编辑上去吧 729 发表于 2020-11-23 14:40
需要分析过程吗?我等等编辑上去吧
嗯嗯,感谢,最近也在学,想学习一下,分析的过程! 收藏准备学习下 收藏学习 谢谢分享 谢谢分享 谢谢楼主,准备学习下 学习了,谢谢楼主!