溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python如何爬取電影

發(fā)布時(shí)間:2021-11-26 11:39:55 來(lái)源:億速云 閱讀:182 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“Python如何爬取電影”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

實(shí)現(xiàn)功能:

從網(wǎng)站上爬取采用m3u8分段方式的視頻文件,對(duì)加密的 "ts"文件解密,實(shí)現(xiàn)兩種方式合并"ts"文件,為防止IP被封,使用代理,最后刪除臨時(shí)文件。

環(huán)境 &依賴

  • Win10 64bit

  • IDE:Pycharm

  • Python 3.8

  • Python-site-package:requests + BeautifulSoup + lxml + m3u8 + AES

在PyCharm中創(chuàng)建一個(gè)項(xiàng)目會(huì)創(chuàng)建一個(gè)臨時(shí)目錄存放環(huán)境和所需要的package包,所以要在PyCharm 中項(xiàng)目解釋器(Project Interpreter)中添加所有需要的包,這張截圖是本項(xiàng)目的包列表,紅框中是所必須的包,其他有的包我也不知道做什么用的。

[外鏈圖片轉(zhuǎn)存中...(img-kZvgLUlH-1598605086326)]

下面開始我們的正餐,爬取數(shù)據(jù)第一步我們需要解析目標(biāo)網(wǎng)站,找到我們需要爬取視頻的地址,F(xiàn)12打開開發(fā)者工具

[外鏈圖片轉(zhuǎn)存中...(img-yKJgUKe8-1598605086327)]

[外鏈圖片轉(zhuǎn)存中...(img-vXsh0B96-1598605086353)]

很不幸,這個(gè)網(wǎng)站視頻是經(jīng)過包裝采用m3u8視頻分段方式加載

科普一下:m3u8 文件實(shí)質(zhì)是一個(gè)播放列表(playlist),其可能是一個(gè)媒體播放列表(Media Playlist),或者是一個(gè)主列表(Master Playlist)。但無(wú)論是哪種播放列表,其內(nèi)部文字使用的都是 utf-8 編碼。

當(dāng) m3u8 文件作為媒體播放列表(Meida Playlist)時(shí),其內(nèi)部信息記錄的是一系列媒體片段資源,順序播放該片段資源,即可完整展示多媒體資源。

OK,本著“沒有解決不了的困難“的原則我們繼續(xù),依舊在開發(fā)者模式,從Elements模式切換到NetWork模式,去掉不需要的數(shù)據(jù),我們發(fā)現(xiàn)了兩個(gè)m3u8文件一個(gè)key文件和一個(gè)ts文件

[外鏈圖片轉(zhuǎn)存中...(img-fEQVPtfL-1598605086354)]

分別點(diǎn)擊之后我們可以 看到對(duì)應(yīng)的地址

[外鏈圖片轉(zhuǎn)存中...(img-AaVH7Y5i-1598605086355)]

OK,現(xiàn)在地址已經(jīng)拿到了,我們可以開始我們的數(shù)據(jù)下載之路了。

首先進(jìn)行初始化,包括路徑設(shè)置,請(qǐng)求頭的偽裝等,之后我們通過循環(huán)去下載所有ts文件,至于如何定義循環(huán)的次數(shù)我們可以通過將m3u8文件下載之后解析文件得到所有ts的列表,之后拼接地址然后循環(huán)就可以得到所有ts文件了。

第一層

新手學(xué)習(xí),Python 教程/工具/方法/解疑+V:itz992
#EXTM3U

#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=500000,RESOLUTION=720x406

500kb/hls/index.m3u8

觀察數(shù)據(jù),不是真正路徑,第二層路徑在第三行可以看到,結(jié)合我們對(duì)網(wǎng)站源碼分析再次拼接字符串請(qǐng)求:

第二層

#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

之后我們循環(huán)得到的TS列表,通過拼接地址下載視頻片段。但是問題遠(yuǎn)遠(yuǎn)沒有這么簡(jiǎn)單,我們下載的ts文件居然無(wú)法播放,通過對(duì)第二層下載得到的m3u8文件進(jìn)行分析我們可以發(fā)現(xiàn)這一行代碼:

#EXT-X-KEY:METHOD=AES-128,URI="key.key"

此網(wǎng)站采用AES方法對(duì)所有ts文件進(jìn)行了加密,其中

METHOD=ASE-128:說明此視頻采用ASE-128方式進(jìn)行加密,

URI=“key.key”:代表key的地址

綜上所訴,感覺好難啊,好繞了,都拿到了視頻還看不了,但是我們要堅(jiān)持我們的初心不能放棄。Fortunately,我們應(yīng)該慶幸Python強(qiáng)大的模塊功能,這個(gè)問題我們可以通過下載AES模塊解決。

完成之后我們需要將所有ts合并為一個(gè)MP4文件,最簡(jiǎn)單的在CMD命令下我們進(jìn)入到視頻所在路徑然后執(zhí)行:

copy /b *.ts fileName.mp4

需要注意所有TS文件需要按順序排好。在本項(xiàng)目中我們使用os模塊直接進(jìn)行合并和刪除臨時(shí)ts文件操作。

完整代碼:

方法一:

新手學(xué)習(xí),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("正在解析真實(shí)下載地址...")

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])#一定規(guī)律下對(duì)字符串拼接得到第二層地址, 得到真實(shí)m3u8下載地址,

key_url="%s/500kb/hls/key.key"%tempArr[0]#分析規(guī)律對(duì)字符串拼接得到key的地址

key=requests.get(key_url).content

fileName=bsObj.find(class_="video-title w100").contents[0].contents[0]#從源碼中找到視頻名稱的規(guī)律

fileName=re.sub(r'[\s,!]','',fileName) #通過正則表達(dá)式去掉中文名稱中的感嘆號(hào)逗號(hào)和空格等特殊字符串

cryptor=AES.new(key,AES.MODE_CBC,key)#通過AES對(duì)ts進(jìn)行解密

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("下載完成!總共耗時(shí)%d s"%(time.time()-start_time))

print("接下來(lái)進(jìn)行合并......")

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片段到本地之后在進(jìn)行合并,其中有可能順序會(huì)亂,有時(shí)候解密的視頻還是無(wú)法播放合并之后會(huì)導(dǎo)致整個(gè)視頻時(shí)間軸不正確而且視頻根本不能完整播放,在經(jīng)過各種努力,多方查資料之后有的問題還是得不到完美解決,最后突發(fā)奇想,有了一個(gè)新的想法,我們不必把所有ts片段都下載到本地之后進(jìn)行合并,而是采用另一種思維模式,一開始我們只創(chuàng)建一個(gè)ts文件,然后每次循環(huán)的時(shí)候不是去下載ts文件而是將通過地址得到的視頻片段文件流直接添加到我們一開始創(chuàng)建的ts文件中,如果出現(xiàn)錯(cuò)誤跳出當(dāng)前循環(huán)并繼續(xù)下次操作,最后我們直接得到的就是一個(gè)完整的ts文件,還不需要去合并所有片段。具體看代碼如何實(shí)現(xiàn)。

本代碼好多地方和上面都一樣,我們只需要領(lǐng)悟其中的原理和方法就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/'  #獲取免費(fèi)代理的網(wǎng)站,如果網(wǎng)站過期或者失效,自己找代理網(wǎng)站替換

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("正在解析真實(shí)下載地址...")

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): #判斷文件是否存在,不存在則創(chuàng)建

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]#得到其中有用的字符串,這個(gè)針對(duì)不同的網(wǎng)站采用不同的方法自己尋找其中的規(guī)律

realAdr = "%s/%s" % (tempArr[0], all_content)#一定規(guī)律下對(duì)字符串拼接得到第二層地址, 得到真實(shí)m3u8下載地址,

key_url = "%s/%s/hls/key.key" % (tempArr[0], midStr)#分析規(guī)律對(duì)字符串拼接得到key的地址

key_html = requests.head(key_url)#訪問key的地址得到的文本

status = key_html.status_code#是否成功訪問到key的地址

key = ""

if status == 200:

all_content=requests.get(realAdr).text#請(qǐng)求第二層m3u8文件地址得到內(nèi)容

if "#EXT-X-KEY" in all_content:

key = requests.get(key_url).content#如果其中有"#EXT-X-KEY"這個(gè)字段說明視頻被加密

self.fileName = bsObj.find(class_="video-title w100").contents[0].contents[0]#分析網(wǎng)頁(yè)得到視頻的名稱

self.fileName=re.sub(r'[\s,!]','',self.fileName)#因?yàn)槿绻募杏卸禾?hào)感嘆號(hào)或者空格會(huì)導(dǎo)致合并時(shí)出現(xiàn)命令不正確錯(cuò)誤,所以通過正則表達(dá)式直接去掉名稱中這些字符

iv = b'abcdabcdabcdabcd'#AES解密時(shí)候湊位數(shù)的iv

if len(key):#如果key有值說明被加密

cryptor = AES.new(key, AES.MODE_CBC, iv)#通過AES對(duì)ts進(jìn)行解密

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')#初始創(chuàng)建一個(gè)ts文件,之后每次循環(huán)將ts片段的文件流寫入此文件中從而不需要在去合并ts文件

ip_list=self.get_ip_list(self.agency_url,self.headers)#通過網(wǎng)站爬取到免費(fèi)的代理ip集合

for url in urlList:

try:

proxies=self.get_random_ip(ip_list)#從ip集合中隨機(jī)拿到一個(gè)作為此次訪問的代理

resp = requests.get("%s/%s/hls/%s" % (urlRoot, midStr, url.uri), headers=crawler.headers,proxies=proxies)#拼接地址去爬取數(shù)據(jù),通過模擬header和使用代理解決封IP

if len(key):

tempText=cryptor.decrypt(resp.content)#解密爬取到的內(nèi)容

progess=i/len(urlList)#記錄當(dāng)前的爬取進(jìn)度

outputfile.write(tempText)#將爬取到ts片段的文件流寫入剛開始創(chuàng)建的ts文件中

sys.stdout.write('\r正在下載:%s,進(jìn)度:%s %%'%(self.fileName,progess))#通過百分比顯示下載進(jìn)度

sys.stdout.flush()#通過此方法將上一行代碼刷新,控制臺(tái)只保留一行

else:

outputfile.write(resp.content)

except Exception as e:

print("\n出現(xiàn)錯(cuò)誤:%s",e.args)

continue#出現(xiàn)錯(cuò)誤跳出當(dāng)前循環(huán),繼續(xù)下次循環(huán)

i+=1

outputfile.close()

print("下載完成!總共耗時(shí)%d s"%(time.time()-start_time))

self.del_tempfile()#刪除臨時(shí)文件

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('臨時(shí)文件刪除完成')

if __name__=='__main__':

url=input("輸入地址:\n")

crawler=VideoCrawler(url)

crawler.run()

quitClick=input("請(qǐng)按Enter鍵確認(rèn)退出!")

問題與解決:

  1. 一開始以為電腦中Python環(huán)境中有模塊就OK了,最后發(fā)現(xiàn)在Pycharm中自己虛擬的環(huán)境中還需要添加對(duì)應(yīng)模塊,

  2. No module named Crypto.Cipher ,網(wǎng)上看了很多最后通過添加pycryptodome模塊解決,電腦環(huán)境Win10

  3. 文件名不能有感嘆號(hào),逗號(hào)或者空格等這些特殊字符,不然執(zhí)行合并命令的時(shí)候會(huì)提示命令不正確

  4. 在下載中將ts文件流寫入文件時(shí)會(huì)出現(xiàn)這種錯(cuò)誤('Data must be padded to 16 byte boundary in CBC mode',) Data must be padded,我們直接continue跳出當(dāng)前循環(huán)繼續(xù)下次下載。

  5. 有時(shí)出現(xiàn) “Protocol Error, Connection abort, os.error”,應(yīng)該是爬取操作太頻繁ip被封,針對(duì)此問題我們使用免費(fèi)代理。

“Python如何爬取電影”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI