您好,登錄后才能下訂單哦!
本篇內容介紹了“Python如何爬取電影”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
從網站上爬取采用m3u8分段方式的視頻文件,對加密的 "ts"文件解密,實現兩種方式合并"ts"文件,為防止IP被封,使用代理,最后刪除臨時文件。
Win10 64bit
IDE:Pycharm
Python 3.8
Python-site-package:requests + BeautifulSoup + lxml + m3u8 + AES
在PyCharm中創建一個項目會創建一個臨時目錄存放環境和所需要的package包,所以要在PyCharm 中項目解釋器(Project Interpreter)中添加所有需要的包,這張截圖是本項目的包列表,紅框中是所必須的包,其他有的包我也不知道做什么用的。
[外鏈圖片轉存中...(img-kZvgLUlH-1598605086326)]
下面開始我們的正餐,爬取數據第一步我們需要解析目標網站,找到我們需要爬取視頻的地址,F12打開開發者工具
[外鏈圖片轉存中...(img-yKJgUKe8-1598605086327)]
[外鏈圖片轉存中...(img-vXsh0B96-1598605086353)]
很不幸,這個網站視頻是經過包裝采用m3u8視頻分段方式加載
科普一下:m3u8 文件實質是一個播放列表(playlist),其可能是一個媒體播放列表(Media Playlist),或者是一個主列表(Master Playlist)。但無論是哪種播放列表,其內部文字使用的都是 utf-8 編碼。
當 m3u8 文件作為媒體播放列表(Meida Playlist)時,其內部信息記錄的是一系列媒體片段資源,順序播放該片段資源,即可完整展示多媒體資源。
OK,本著“沒有解決不了的困難“的原則我們繼續,依舊在開發者模式,從Elements模式切換到NetWork模式,去掉不需要的數據,我們發現了兩個m3u8文件一個key文件和一個ts文件
[外鏈圖片轉存中...(img-fEQVPtfL-1598605086354)]
分別點擊之后我們可以 看到對應的地址
[外鏈圖片轉存中...(img-AaVH7Y5i-1598605086355)]
OK,現在地址已經拿到了,我們可以開始我們的數據下載之路了。
首先進行初始化,包括路徑設置,請求頭的偽裝等,之后我們通過循環去下載所有ts文件,至于如何定義循環的次數我們可以通過將m3u8文件下載之后解析文件得到所有ts的列表,之后拼接地址然后循環就可以得到所有ts文件了。
新手學習,Python 教程/工具/方法/解疑+V:itz992 #EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=500000,RESOLUTION=720x406 500kb/hls/index.m3u8
觀察數據,不是真正路徑,第二層路徑在第三行可以看到,結合我們對網站源碼分析再次拼接字符串請求:
#EXT-X-VERSION:3 #EXT-X-TARGETDURATION:2 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHOD=AES-128,URI="key.key" #EXTINF:2.000000, IsZhMS5924000.ts #EXTINF:2.000000, IsZhMS5924001.ts #EXT-X-ENDLIST
之后我們循環得到的TS列表,通過拼接地址下載視頻片段。但是問題遠遠沒有這么簡單,我們下載的ts文件居然無法播放,通過對第二層下載得到的m3u8文件進行分析我們可以發現這一行代碼:
#EXT-X-KEY:METHOD=AES-128,URI="key.key"
此網站采用AES方法對所有ts文件進行了加密,其中
METHOD=ASE-128:說明此視頻采用ASE-128方式進行加密,
URI=“key.key”:代表key的地址
綜上所訴,感覺好難啊,好繞了,都拿到了視頻還看不了,但是我們要堅持我們的初心不能放棄。Fortunately,我們應該慶幸Python強大的模塊功能,這個問題我們可以通過下載AES模塊解決。
完成之后我們需要將所有ts合并為一個MP4文件,最簡單的在CMD命令下我們進入到視頻所在路徑然后執行:
copy /b *.ts fileName.mp4
需要注意所有TS文件需要按順序排好。在本項目中我們使用os模塊直接進行合并和刪除臨時ts文件操作。
方法一:
新手學習,Python 教程/工具/方法/解疑+V:itz992 import re import requests import m3u8 import time import os from bs4 import BeautifulSoup import json from Crypto.Cipher import AES class VideoCrawler(): def __init__(self,url): super(VideoCrawler, self).__init__() self.url=url self.down_path=r"F:\Media\Film\Temp" self.final_path=r"F:\Media\Film\Final" self.headers={ 'Connection':'Keep-Alive', 'Accept':'text/html,application/xhtml+xml,*/*', 'User-Agent':'Mozilla/5.0 (Linux; U; Android 6.0; zh-CN; MZ-m2 note Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 MZBrowser/6.5.506 UWS/2.10.1.22 Mobile Safari/537.36' } def get_url_from_m3u8(self,readAdr): print("正在解析真實下載地址...") with open('temp.m3u8','wb') as file: file.write(requests.get(readAdr).content) m3u8Obj=m3u8.load('temp.m3u8') print("解析完成") return m3u8Obj.segments def run(self): print("Start!") start_time=time.time() os.chdir(self.down_path) html=requests.get(self.url).text bsObj=BeautifulSoup(html,'lxml') tempStr = bsObj.find(class_="iplays").contents[3].string#通過class查找存放m3u8地址的組件 firstM3u8Adr=json.loads(tempStr.strip('var player_data='))["url"]#得到第一層m3u8地址 tempArr=firstM3u8Adr.rpartition('/') realAdr="%s/500kb/hls/%s"%(tempArr[0],tempArr[2])#一定規律下對字符串拼接得到第二層地址, 得到真實m3u8下載地址, key_url="%s/500kb/hls/key.key"%tempArr[0]#分析規律對字符串拼接得到key的地址 key=requests.get(key_url).content fileName=bsObj.find(class_="video-title w100").contents[0].contents[0]#從源碼中找到視頻名稱的規律 fileName=re.sub(r'[\s,!]','',fileName) #通過正則表達式去掉中文名稱中的感嘆號逗號和空格等特殊字符串 cryptor=AES.new(key,AES.MODE_CBC,key)#通過AES對ts進行解密 urlList=self.get_url_from_m3u8(realAdr) urlRoot=tempArr[0] i=1 for url in urlList: resp=requests.get("%s/500kb/hls/%s"%(urlRoot,url.uri),headers=crawler.headers) if len(key): with open('clip%s.ts' % i, 'wb') as f: f.write(cryptor.decrypt(resp.content)) print("正在下載clip%d" % i) else: with open('clip%s.ts'%i,'wb') as f: f.write(resp.content) print("正在下載clip%d"%i) i+=1 print("下載完成!總共耗時%d s"%(time.time()-start_time)) print("接下來進行合并......") os.system('copy/b %s\\*.ts %s\\%s.ts'%(self.down_path,self.final_path,fileName)) print("刪除碎片源文件......") files=os.listdir(self.down_path) for filena in files: del_file=self.down_path+'\\'+filena os.remove(del_file) print("碎片文件刪除完成") if __name__=='__main__': crawler=VideoCrawler("地址大家自己找哦") crawler.start() crawler2=VideoCrawler("地址大家自己找哦") crawler2.start()
方法二在方法一中我們是下載所有ts片段到本地之后在進行合并,其中有可能順序會亂,有時候解密的視頻還是無法播放合并之后會導致整個視頻時間軸不正確而且視頻根本不能完整播放,在經過各種努力,多方查資料之后有的問題還是得不到完美解決,最后突發奇想,有了一個新的想法,我們不必把所有ts片段都下載到本地之后進行合并,而是采用另一種思維模式,一開始我們只創建一個ts文件,然后每次循環的時候不是去下載ts文件而是將通過地址得到的視頻片段文件流直接添加到我們一開始創建的ts文件中,如果出現錯誤跳出當前循環并繼續下次操作,最后我們直接得到的就是一個完整的ts文件,還不需要去合并所有片段。具體看代碼如何實現。
本代碼好多地方和上面都一樣,我們只需要領悟其中的原理和方法就OK了
import re import requests import m3u8 import time import os from bs4 import BeautifulSoup import json from Crypto.Cipher import AES import sys import random class VideoCrawler(): def __init__(self,url): super(VideoCrawler, self).__init__() self.url=url self.down_path=r"F:\Media\Film\Temp" self.agency_url='https://www.kuaidaili.com/free/' #獲取免費代理的網站,如果網站過期或者失效,自己找代理網站替換 self.final_path=r"F:\Media\Film\Final" self.headers={ 'Connection':'Keep-Alive', 'Accept':'text/html,application/xhtml+xml,*/*', 'User-Agent':'Mozilla/5.0 (Linux; U; Android 6.0; zh-CN; MZ-m2 note Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 MZBrowser/6.5.506 UWS/2.10.1.22 Mobile Safari/537.36' } def get_url_from_m3u8(self,readAdr): print("正在解析真實下載地址...") with open('temp.m3u8','wb') as file: file.write(requests.get(readAdr).content) m3u8Obj=m3u8.load('temp.m3u8') print("解析完成") return m3u8Obj.segments def get_ip_list(self,url, headers): web_data = requests.get(url, headers=headers).text soup = BeautifulSoup(web_data, 'lxml') ips = soup.find_all('tr') ip_list = [] for i in range(1, len(ips)): ip_info = ips[i] tds = ip_info.find_all('td') ip_list.append(tds[0].text + ':' + tds[1].text) return ip_list def get_random_ip(self,ip_list): proxy_list = [] for ip in ip_list: proxy_list.append('http://' + ip) proxy_ip = random.choice(proxy_list) proxies = {'http': proxy_ip} return proxies def run(self): print("Start!") start_time=time.time() self.down_path = r"%s\%s" % (self.down_path, uuid.uuid1())#拼接新的下載地址 if not os.path.exists(self.down_path): #判斷文件是否存在,不存在則創建 os.mkdir(self.down_path) html=requests.get(self.url).text bsObj=BeautifulSoup(html,'lxml') tempStr = bsObj.find(class_="iplays").contents[3].string#通過class查找存放m3u8地址的組件 firstM3u8Adr=json.loads(tempStr.strip('var player_data='))["url"]#得到第一層m3u8地址 tempArr=firstM3u8Adr.rpartition('/') all_content = (requests.get(firstM3u8Adr).text).split('\n')[2]#從第一層m3u8文件中中找出第二層文件的的地址 midStr = all_content.split('/')[0]#得到其中有用的字符串,這個針對不同的網站采用不同的方法自己尋找其中的規律 realAdr = "%s/%s" % (tempArr[0], all_content)#一定規律下對字符串拼接得到第二層地址, 得到真實m3u8下載地址, key_url = "%s/%s/hls/key.key" % (tempArr[0], midStr)#分析規律對字符串拼接得到key的地址 key_html = requests.head(key_url)#訪問key的地址得到的文本 status = key_html.status_code#是否成功訪問到key的地址 key = "" if status == 200: all_content=requests.get(realAdr).text#請求第二層m3u8文件地址得到內容 if "#EXT-X-KEY" in all_content: key = requests.get(key_url).content#如果其中有"#EXT-X-KEY"這個字段說明視頻被加密 self.fileName = bsObj.find(class_="video-title w100").contents[0].contents[0]#分析網頁得到視頻的名稱 self.fileName=re.sub(r'[\s,!]','',self.fileName)#因為如果文件名中有逗號感嘆號或者空格會導致合并時出現命令不正確錯誤,所以通過正則表達式直接去掉名稱中這些字符 iv = b'abcdabcdabcdabcd'#AES解密時候湊位數的iv if len(key):#如果key有值說明被加密 cryptor = AES.new(key, AES.MODE_CBC, iv)#通過AES對ts進行解密 urlList=self.get_url_from_m3u8(realAdr) urlRoot=tempArr[0] i=1 outputfile=open(os.path.join(self.final_path,'%s.ts'%self.fileName),'wb')#初始創建一個ts文件,之后每次循環將ts片段的文件流寫入此文件中從而不需要在去合并ts文件 ip_list=self.get_ip_list(self.agency_url,self.headers)#通過網站爬取到免費的代理ip集合 for url in urlList: try: proxies=self.get_random_ip(ip_list)#從ip集合中隨機拿到一個作為此次訪問的代理 resp = requests.get("%s/%s/hls/%s" % (urlRoot, midStr, url.uri), headers=crawler.headers,proxies=proxies)#拼接地址去爬取數據,通過模擬header和使用代理解決封IP if len(key): tempText=cryptor.decrypt(resp.content)#解密爬取到的內容 progess=i/len(urlList)#記錄當前的爬取進度 outputfile.write(tempText)#將爬取到ts片段的文件流寫入剛開始創建的ts文件中 sys.stdout.write('\r正在下載:%s,進度:%s %%'%(self.fileName,progess))#通過百分比顯示下載進度 sys.stdout.flush()#通過此方法將上一行代碼刷新,控制臺只保留一行 else: outputfile.write(resp.content) except Exception as e: print("\n出現錯誤:%s",e.args) continue#出現錯誤跳出當前循環,繼續下次循環 i+=1 outputfile.close() print("下載完成!總共耗時%d s"%(time.time()-start_time)) self.del_tempfile()#刪除臨時文件 def del_tempfile(self): file_list=os.listdir(self.down_path) for i in file_list: tempPath=os.path.join(self.down_path,i) os.remove(tempPath) os.rmdir(self.down_path) print('臨時文件刪除完成') if __name__=='__main__': url=input("輸入地址:\n") crawler=VideoCrawler(url) crawler.run() quitClick=input("請按Enter鍵確認退出!")
問題與解決:
一開始以為電腦中Python環境中有模塊就OK了,最后發現在Pycharm中自己虛擬的環境中還需要添加對應模塊,
No module named Crypto.Cipher ,網上看了很多最后通過添加pycryptodome模塊解決,電腦環境Win10
文件名不能有感嘆號,逗號或者空格等這些特殊字符,不然執行合并命令的時候會提示命令不正確
在下載中將ts文件流寫入文件時會出現這種錯誤('Data must be padded to 16 byte boundary in CBC mode',) Data must be padded,我們直接continue跳出當前循環繼續下次下載。
有時出現 “Protocol Error, Connection abort, os.error”,應該是爬取操作太頻繁ip被封,針對此問題我們使用免費代理。
“Python如何爬取電影”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。