xuanyu-10-18

写在最前:互联网并非法外之地,爬虫仅供技术交流

运行环境

  • python 3.7.4
  • requests 2.10.0

爬取目标

  • EDA技术与应用(2020秋)1.1.2 EDA技术概述 教学视频

分析视频字幕接口

找接口就只能凭借经验去network里面翻找,或者借助于浏览器调试,没有过多的技巧。

一、从资源回溯寻找接口

  • 带有视频接口的json文件URL分析

    https://www.xuetangx.com/api/v1/lms/service/playurl/7ED5FE6BE6C6DAC39C33DC5901307461/?appid=10000

    跟其他视频比较,可以得出:

    有一个请求参数,这个参数似乎是固定的,所以不用管。

    7ED5FE6BE6C6DAC39C33DC5901307461是一个路径变量,不同视频有着不同的该参数。

  • 带有字幕接口的json文件URL分析

    https://www.xuetangx.com/api/v1/lms/service/s_t_g_p/

    这个数据是通过POST请求的,数据查看后发现需要一个json对象

    {"c_d":"7ED5FE6BE6C6DAC39C33DC5901307461"}
    

    而且这个c_d与上文视频的路径变量一致。

    所以最后得出请求方案:

    c_d = val
    # method: GET
    # 视频动态URL
    url_video = "https://www.xuetangx.com/api/v1/lms/service/playurl/{}/?appid=10000".format(c_d)
    # method: POST
    # 字幕URL
    url_subtitle = "https://www.xuetangx.com/api/v1/lms/service/s_t_g_p/"
    data = {"c_d":"7ED5FE6BE6C6DAC39C33DC5901307461"}
    
  • 视频接口和字幕接口URL分析

    上面的两个json文件直接提供的视频和字幕的接口URL,所以即使他们的URL还带了其它的参数,我们也不再需要关心这些。

    可能会担心的就是鉴权问题,但是我已经尝试过了,字幕和视频的接口以及这两个json文件都不需要专门的头部信息进行鉴权。

    我们只要找到上面的两个文件,就可进行视频字幕的下载。

    所以我们现在需要找到c_d

二、从未知变量回溯寻找接口

  • 带有c_d(ccid)的json文件

    我们可以在这个文件下的data中的content_info中的media下找到一个ccidc_d相同,所以我们可以把这里获得的ccid当成变量给下游的URL。

    同时我们还在这个文件下找到了视频相关附件的链接,https://qn-next.xuetangx.com/15679498483925.pptx,同样不需要鉴权就可以下载。

  • 带有c_d(ccid)的json文件URL分析

    https://www.xuetangx.com/api/v1/lms/learn/leaf_info/4227236/6195112/?sign=NCIAE08091001906

    我们可以看到这个新的路径给爬取增加了不少难度,它多出了两个路径变量(4227236/6195112)和一个请求参数(sign=NCIAE08091001906`)。

    而且从这里开始就已经需要鉴权了,头文件得带上相应的参数才可以进行访问。

  • 带有id的json文件

    https://www.xuetangx.com/api/v1/lms/learn/leaf_info/4227236/6195112/?sign=NCIAE08091001906中的6195112是这个文件的leaf_list中每一个json对象的id。我们成功的解决了下游URL的一个变量。

  • 带有c_d(ccid)的json文件URL分析

    https://www.xuetangx.com/api/v1/lms/learn/course/chapter?cid=4227236&sign=NCIAE08091001906

    很幸运的是,下游URL的两个未解决变量在这里出现了,经过这个URL,总体的未知变量没有增多。

    经过两个路由后,我们最后可以得出这样的请求方案:

    cid = val1
    sign = val2
    # method: GET
    # 章节动态URL
    url_chapter = "https://www.xuetangx.com/api/v1/lms/learn/course/chapter?cid={}&sign={}".format(cid, sign)
    leaf_id = response(url_chapter)
    # method: GET
    # 小节动态URL
    url_leaf = "https://www.xuetangx.com/api/v1/lms/learn/leaf_info/{}/{}/?sign={}".format(cid, vid, sign)
    

三、回溯到头再顺流而下

  • 未解决的问题

    1. "NCIAE08091001906"到底是什么?cid是课程id吗?

      我们可以通过退出再登录,使用其它账户来判断它们是否与用户身份相关;通过等待一段时间看它们是否改变,判断是否与时间有关。我们会发现它们既与用户身份无关也与时间无关

      我们还可以通过浏览器的调试模式去判断这一点。

      最后我们可以得出sign和cid都是课程识别码。

      虽然你可以在进入这门课程学习后,在顶上的URL找到这两个参数。但我依旧想更清楚的解释它们是什么,sign(course_sign)是一门课程的标识,而cid(classroom_id)是一门课程每个学期的标识。这些信息都可以在更高的源头追溯到。

      但这次我们就先追溯到这里。

    2. 关于鉴权的问题。

      我们在爬虫的时候需要考虑清楚地告诉对方服务器我们是什么?

      所以我们需要去看浏览器为我们生成的请求头和其它请求条件呢,这我们可以自己搭一个本地服务,去看requests的请求头和浏览器的有什么区别。

      再通过不断试错,找到当前请求需要的请求头和其它请求条件。

      很庆幸的是,学堂在线我们需要补充修改的请求头参数非常简单。示例如下:

      • 方式一

        # 这里的代码请不要尝试,sessionid我已经安全退出,失去效力。
        # 没有安全退出的话可以保存两周,在此期间可以任意爬取。当然这也跟浏览器的设置有关。
        headers = { "xtbz": "xt" }
        cookies = { "sessionid": "z3rvy7fpp4tqbc4opmzkq1amlvmqde7d" }
        requests.get("https://www.xuetangx.com/api/v1/lms/learn/leaf_info/4227236/6195112/?sign=NCIAE08091001906",headers=headers,cookies=cookies)
        
      • 方式二

        # 方式一直接带上cookie是更好的选择,至少在学堂在线是这样的。
        headers = { "xtbz": "xt", "cookies": "sessionid=z3rvy7fpp4tqbc4opmzkq1amlvmqde7d" }
        requests.get("https://www.xuetangx.com/api/v1/lms/learn/leaf_info/4227236/6195112/?sign=NCIAE08091001906",headers=headers,cookies=cookies)
        
  • 正式顺流而下

    1. 找到sign和cid

      img

    2. 找到cookies

      img

      只需要sessionid就好,其它浏览器找cookies自行百度。

    3. 根据sign和cid请求数据

      import json,requests,time
      
      cid = "4227236"
      sign = "NCIAE08091001906"
      
      # 请求头 仅供参考
      headers = { "xtbz": "xt" }
      cookies = { "sessionid": "z3rvy7fpp4tqbc4opmzkq1amlvmqde7d" }
      
      # 章节信息
      url_chapter = "https://www.xuetangx.com/api/v1/lms/learn/course/chapter?cid={}&sign={}".format(cid, sign)
      time.sleep(0.2)
      chapter = json.loads(requests.get(url_chapter,headers=headers,cookies=cookies).content)
      ## 第一章的第一节的所有小节
      leaf_list = chapter[\'data\'][\'course_chapter\'][0][\'section_leaf_list\'][0][\'leaf_list\']
      ## 第一章的第一节的所有视频小节
      video_leaf_list = list(filter(lambda item:item[\'leaf_type\']==0, leaf_list))
      ## 第一章的第一节的第一个视频小节的id
      vid = video_leaf_list[0][\'id\']
      
      # 视频小节信息
      url_leaf = "https://www.xuetangx.com/api/v1/lms/learn/leaf_info/{}/{}/?sign={}".format(cid, vid, sign)
      time.sleep(0.2)
      video = json.loads(requests.get(url_leaf,headers=headers,cookies=cookies).content)
      
      ## ppt等附件
      url_file = video[\'data\'][\'content_info\'][\'download\'][0][\'file_url\']
      time.sleep(0.2)
      file = requests.get(url_file).content
      with open(\'1.pptx\',\'wb\') as f:
        f.write(file)
      
      ccid = video[\'data\'][\'content_info\'][\'media\'][\'ccid\']
      ## 视频
      time.sleep(0.2)
      url_video = json.loads(requests.get("https://www.xuetangx.com/api/v1/lms/service/playurl/{}/?appid=10000".format(ccid)).content)[\'data\'][\'sources\'][\'quality10\'][0]
      time.sleep(0.2)
      content_video = requests.get(url_video).content
      with open(\'1.mp4\',\'wb\') as f:
        f.write(content_video)
      ## 字幕
      time.sleep(0.2)
      url_subtitle = json.loads(requests.post("https://www.xuetangx.com/api/v1/lms/service/s_t_g_p/",data={"c_d": ccid},headers=headers).content)[\'data\'][0][\'data\']
      time.sleep(0.2)
      content_subtitle = requests.get(url_subtitle).text
      with open(\'1.txt\',\'w\') as f:
        f.write(content_subtitle)
      

写在最后

上面的代码主要是提供一个思路,实际只用于抓取EDA技术与应用(2020秋)第一章的第一节的第一个视频小节,因为我们不可以保证每一门课的第一章的第一节都有视频小节,也不能保证每一个小节都有附件,每一个视频都有字幕,爬取其它视频还要做容错处理。

如果想一次爬所有视频也可以实现,用for循环就可以。请记得不要过度频繁地发送请求,会给服务器造成巨大的压力,服务器针对此也有很多的反爬手段。

爬取的字幕是json数据,想要变成字幕文件还得做相应处理。

这篇文章还有后续,会继续完善相应功能。

分类:

技术点:

相关文章: