python 下载拉钩教育AES加密视频
說在前面:
下面我們要爬取的是拉鉤教育課程上面的視頻,課程已經購買過了。但是由于沒有提供緩沖和下載視頻的功能,所以就打算把視頻通過python給下載下來,以下的文章都是參考博友的,自己總結下并學習學習。
正式爬取:
1.拉鉤教育的網址:https://kaiwu.lagou.com/,輸入自己的賬號和密碼 ,然后登陸進去,找到自己已經購買的課程(這里我就舉這個例子)。
2.通過F12 抓包的方式,進入到控制臺的network,最后找到的這個視頻的請求地址,同時也找到視頻的名字和地址(注意:cookies 和referer 字段 請求的時候一定要帶上,因為電腦的原因截圖沒有顯示出來)。
下面是請求頭的代碼,注意一定要帶上cookie 和referer 字段,代碼里面的cookie要修改成自己的,要不然會報錯。
3.分析請求地址和請求后的響應內容,不難發現 返回的數據是json格式,所以只需要使用json()模塊解析數據就可以了。
4.現在已經知道請求的地址,然后我們就可以發送請求,再解析數據了。現在有許多的視頻網站,都是通過m3u8的方式把視頻分割成ts視頻流的方式,所以本次我們要得到的就是m3u8文件地址,再通過m3u8文件找到每一個單獨的ts視頻的地址以及AES128加密的key以及iv偏移量。
具體代碼如下:
通過parse_one方法得到了所有課程的m3u8以及課程名,通過字典的形式進行存儲,然后把得到的字典傳遞給下一個方法。
def parse_one(self):""":return:獲得所有的課程url和課程名 返回一個隊列(請求一次)"""html = requests.get(url=self.url, headers=self.headers).textdit_message = json.loads(html)courseSectionList = dit_message['content']['courseSectionList']for course in courseSectionList:for i in course['courseLessons']:if i['videoMediaDTO'] == None:passelse:fileUrl = i['videoMediaDTO']['fileUrl']theme = i['theme']m3u8_dict = {fileUrl: theme} # fileUrl就是m3u8視頻的url,theme為視頻的名字if os.path.exists(os.path.join(self.save_folder, "{}.mp4".format(theme))):print("{}已經存在".format(theme))passelse:self.queue.put(m3u8_dict) # 將每個本地不存在的視頻url(m3u8)和name加入到隊列中return self.queue5.同樣的道理,我們還需要向m3u8文件地址發送請求,可以從此請求中獲取ts 以及key相關的信息。
然后,從下面這些信息中拿到key(key的url已經找到了,通過requests發送請求即可得到真正的二進制的key )以及所有ts的url(這里ts的url只給出了部分需要自己拼接,用發送請求的url進行拼接)。
這是m3u8文件的內容:
下面是每一節課程下面視頻的m3u8地址 和ts地址,可以找一下規律,然后自己拼接下地址:
這部分代碼如下:
代碼解讀:(對響應回來的數據進行各種切割拼接的操作以得到真正有用的內容)
- 1.遍歷上一個請求穿過來的字典,通過這個m3u8_url可以拼接出ts真正的url
- 2.獲取加密的秘鑰key
- 3.將key,ts的url以及課程的名字傳遞給下一個方法
- 4.發送請求寫入數據
6.破解AES128加密后視頻,然后保存到本地,這部分代碼如下:
def write(self, key, ts_url, name, m3u8_dict):save_wav = os.path.join(self.save_folder, '{}.mp4'.format(name))cryptor = AES.new(key, AES.MODE_CBC, iv=key)with open(save_wav, 'ab')as f:try:html = requests.get(url=ts_url, headers=self.headers).contentf.write(cryptor.decrypt(html))print('{},{}寫入成功'.format(ts_url, name))except Exception as e:print('{}爬取出錯'.format(name))while True:if f.close(): # 檢查這個出問題的文件是否關閉 閉關則刪除然后重新爬取,沒關閉則等待10s,直到該文件被刪除并重新爬取為止os.remove('{}.mp4'.format(name))print('{}刪除成功'.format(name))thread = self.thread_method(self.get_key, m3u8_dict)print("開啟線程{},{}重新爬取".format(thread.getName(), name))thread.start()thread.join()breakelse:time.sleep(10)完整代碼如下:
使用多線程threading 和 Queue來爬取,速度提升很多。
import threading from queue import Queue import re import requests import json from Crypto.Cipher import AES import time import osclass LaGou_spider():def __init__(self):self.url = 'https://gate.lagou.com/v1/neirong/kaiwu/getCourseLessons?courseId=131'self.headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36','Cookie': 'LG_LOGI-9841821f-45eb-4135-9698-8d6b8280d898; LGUID=20200704113210-6a6a8c61','Referer': 'https://kaiwu.lagou.com/course/courseInfo.htm?courseId=131','Origin': 'https://kaiwu.lagou.com','Sec-fetch-dest': 'empty','Sec-fetch-mode': 'cors','Sec-fetch-site': 'same-site','x-l-req-header': '{deviceType:1}'}self.queue = Queue() # 初始化一個隊列self.error_queue = Queue()self.save_folder = r"/Users/拉鉤視頻下載"def parse_one(self):""":return:獲得所有的課程url和課程名 返回一個隊列(請求一次)"""html = requests.get(url=self.url, headers=self.headers).textdit_message = json.loads(html)courseSectionList = dit_message['content']['courseSectionList']for course in courseSectionList:for i in course['courseLessons']:if i['videoMediaDTO'] == None:passelse:fileUrl = i['videoMediaDTO']['fileUrl']theme = i['theme']m3u8_dict = {fileUrl: theme} # fileUrl就是m3u8視頻的url,theme為視頻的名字if os.path.exists(os.path.join(self.save_folder, "{}.mp4".format(theme))):print("{}已經存在".format(theme))passelse:self.queue.put(m3u8_dict) # 將每個本地不存在的視頻url(m3u8)和name加入到隊列中return self.queuedef get_key(self, **kwargs):m3u8_dict = kwargsfor m3u8_url in m3u8_dict: # 獲取某個視頻的urlurl_split = m3u8_url.split('.')[0:-2]str_url = '.'.join(url_split)true_url = str_url.split('/')[0:-1]t_url = '/'.join(true_url) # 拼接ts的url前面部分html = requests.get(url=m3u8_url, headers=self.headers).text # 請求返回包含ts以及key數據message = html.split('\n') # 獲取key以及ts的url# 通過正則表達式獲取key的列表以及ts的列表key_parse = re.compile('URI="(.*?)"')key_list = key_parse.findall(html)key = requests.get(url=key_list[0], headers=self.headers).content # 一個m3u8文件中的所有ts對應的key是同一個 發一次請求獲得m3u8文件的keyvideo_name = m3u8_dict[m3u8_url] # 視頻的名字for i in message:if 'v.f240.ts?' in i:ts_url = t_url + '/' + i # ts_url 就是拼接后每一個ts 視頻流地址self.write(key, ts_url, video_name, m3u8_dict)def write(self, key, ts_url, name, m3u8_dict):save_wav = os.path.join(self.save_folder, '{}.mp4'.format(name))cryptor = AES.new(key, AES.MODE_CBC, iv=key)with open(save_wav, 'ab')as f:try:html = requests.get(url=ts_url, headers=self.headers).contentf.write(cryptor.decrypt(html))print('{},{}寫入成功'.format(ts_url, name))except Exception as e:print('{}爬取出錯'.format(name))while True:if f.close(): # 檢查這個出問題的文件是否關閉 閉關則刪除然后重新爬取,沒關閉則等待10s,直到該文件被刪除并重新爬取為止os.remove('{}.mp4'.format(name))print('{}刪除成功'.format(name))thread = self.thread_method(self.get_key, m3u8_dict)print("開啟線程{},{}重新爬取".format(thread.getName(), name))thread.start()thread.join()breakelse:time.sleep(10)# 創建多線程方法def thread_method(self, method, value):thread = threading.Thread(target=method, kwargs=value)return threaddef main(self):global m3u8thread_list = []m3u8_dict = self.parse_one()while not m3u8_dict.empty():for i in range(10): # 創建10個線程并啟動m3u8 = m3u8_dict.get() # 每次從隊列取出一個對象 ,啟動一個線程實例化對象,然后再放到線程列表中thread = self.thread_method(self.get_key, m3u8)thread.start()print(thread.getName() + '啟動成功,{}'.format(m3u8))time.sleep(1)thread_list.append(thread)for k in thread_list:k.join() # 回收線程if __name__ == '__main__':run = LaGou_spider()run.main()其實,我在這里有一個疑問 ,就是那個破解AES加密的時候,為什么iv也是key?
不應該是m3u8文件里面的那個iv偏移量嗎?
參考博客:Python爬蟲實現全自動爬取拉鉤教育視頻
總結
以上是生活随笔為你收集整理的python 下载拉钩教育AES加密视频的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 的AES加密与解密
- 下一篇: Python 中的url,Base64和