?Python爬蟲:使用協(xié)程下載m3u8視頻
前言
學(xué)了一段時(shí)間爬蟲,終于把進(jìn)程、線程、協(xié)程給搞明白了,還沒搞明白的強(qiáng)烈推薦看一下螞蟻老師B站的視頻:https://www.bilibili.com/video/BV1bK411A7tV?p=1,思路很清晰。爬蟲屬于IO密集型任務(wù),且資源開銷少,對(duì)于爬蟲來說很有優(yōu)勢(shì),對(duì)于使用爬蟲下載視頻,那效率就更高了。我們以人人影視最新一集的國(guó)王排名為例:https://www.rryy123.com/play/40479-1-18.html
一,頁(yè)面分析

通過抓包我們很容易發(fā)現(xiàn)index.m3u8這個(gè)包,但是響應(yīng)內(nèi)容卻沒顯示可用的數(shù)據(jù)。為了驗(yàn)證一下我們對(duì)這個(gè)網(wǎng)址https://c2.monidai.com/20220225/yOm07IlJ/index.m3u8發(fā)起請(qǐng)求,并保存為m3u8格式,打開發(fā)現(xiàn)得到的結(jié)果如下:

但是發(fā)現(xiàn)里面的網(wǎng)址是以png結(jié)尾?于是我們對(duì)上面的一個(gè)網(wǎng)址發(fā)起請(qǐng)求進(jìn)行驗(yàn)證,并以二進(jìn)制寫入文件,文件名后綴以.ts結(jié)尾。下載完成后打開正是我們想要的視頻文件!
那如何通過視頻的URL定位到index.m3u8地址呢?通過檢查網(wǎng)頁(yè)源代碼,搜索關(guān)鍵字m3u8很容易就定位到,如下圖:

至此,第一步我們已經(jīng)完成,通過正則匹配可以獲得對(duì)應(yīng)地址,代碼如下:
????def?get_m3u8_url(self):
????????#?根據(jù)劇集網(wǎng)址,獲取m3u8下載索引
????????res?=requests.get(url=self.url,headers=self.headers).text
????????first_m3u8_url?=?re.findall(';var?now="(.*?)"',res,re.S)[0]?????#?獲取m3u8下載地址
????????self.name?=?re.findall("正在播放:(.*?) ",res,re.S)[0]???#?獲取下載片名
????????print(self.name+"準(zhǔn)備開始下載")
????????res_second?=?requests.get(url=first_m3u8_url,headers=self.headers).text
????????with?open(self.name+".m3u8",'w',encoding='utf-8')?as?f:
????????????f.write(res_second)
二,獲取m3u8播放列表
根據(jù)上一步下載的m3u8文件,提取下載地址,代碼如下:
????def?get_ts_url(self):
????????#?根據(jù)m3u8下載索引,獲取各個(gè)下載地址
????????ts_url_list?=?[]
????????with?open(self.name+".m3u8",'r',encoding='utf-8')?as?f:
????????????for?line?in?f:
????????????????if?line.startswith('#'):
????????????????????continue
????????????????line?=?line.strip()
????????????????ts_url_list.append(line)
????????return?ts_url_list
三,編碼實(shí)現(xiàn)
然后下一步我們就開始下載視頻了,完整代碼如下:
#?-*-?coding:utf-8?-*-
import?time
import?re
import?os
import?requests
import?asyncio
import?aiohttp
import?aiofiles
import?nest_asyncio
nest_asyncio.apply()
class?RRyy123:
????def?__init__(self):
????????self.url?=?None
????????self.name?=?None
????????self.headers?=?{'User-Agent':'Mozilla?/?5.0(Windows?NT?10.0;Win64;x64)?AppleWebKit?/?537.36(KHTML,?likeGecko)?Chrome?/?95.0.4638.69Safari?/?537.36'}
????def?get_m3u8_url(self):
????????#?根據(jù)劇集網(wǎng)址,獲取m3u8下載索引
????????res?=requests.get(url=self.url,headers=self.headers).text
????????first_m3u8_url?=?re.findall(';var?now="(.*?)"',res,re.S)[0]?????#?獲取m3u8下載地址
????????self.name?=?re.findall("正在播放:(.*?) ",res,re.S)[0]???#?獲取下載片名
????????print(self.name+"準(zhǔn)備開始下載")
????????res_second?=?requests.get(url=first_m3u8_url,headers=self.headers).text
????????with?open(self.name+".m3u8",'w',encoding='utf-8')?as?f:
????????????f.write(res_second)
????def?get_ts_url(self):
????????#?根據(jù)m3u8下載索引,獲取各個(gè)ts文件地址
????????ts_url_list?=?[]
????????with?open(self.name+".m3u8",'r',encoding='utf-8')?as?f:
????????????for?line?in?f:
????????????????if?line.startswith('#'):
????????????????????continue
????????????????line?=?line.strip()
????????????????ts_url_list.append(line)
????????return?ts_url_list
????async?def?download_ts(self,url,session,sem):
????????#?使用協(xié)程下載單個(gè)ts文件
????????ts_name?=?os.path.splitext(url)[0].split("/")[-1]
????????async?with?sem:?????#?控制協(xié)程信號(hào)量
????????????async?with?session.get(url?=url)?as?res:
????????????????if?not?os.path.exists(f"./temp_{self.name}"):
????????????????????os.makedirs(f"./temp_{self.name}/")
????????????????async?with?aiofiles.open(f"./temp_{self.name}/{ts_name}.ts",'wb')?as?f:
????????????????????await?f.write(await?res.read())
????????????????????print(f"{ts_name}.ts下載完成")
????async?def?main(self):
????????#?根據(jù)m3u8文件中的ts網(wǎng)址,下載ts視頻
????????tasks?=?[]
????????self.get_m3u8_url()
????????ts_url_list?=?self.get_ts_url()
????????sem?=?asyncio.Semaphore(5)??#?設(shè)置信號(hào)量,控制異步數(shù)量
????????async?with?aiohttp.ClientSession(headers=self.headers)?as?session:
????????????for?ts_url?in?ts_url_list:
????????????????tasks.append(asyncio.create_task(self.download_ts(url=ts_url,session=session,sem=sem)))
????????????await?asyncio.wait(tasks)
if?__name__?==?'__main__':
????t?=?time.time()
????f?=?RRyy123()
????f.url?=?"https://www.rryy123.com/video/?40479-1-18.html"
????asyncio.run(f.main())
????print(f"{f.name}下載完成,總耗時(shí){int(time.time()-t)}s.")
我這是測(cè)了一下,下載一個(gè)128M的文件只要15s!!!

四,ts文件合并
下載完成后我們要對(duì)ts文件進(jìn)行合并了,這里推薦使用ffmpeg,windows下載地址:http://ffmpeg.org/download.html#build-windows。這里要注意的是 將解壓后的文件目錄中 bin 目錄(包含 ffmpeg.exe )添加進(jìn) path 環(huán)境變量中。使用 ffmpeg 合并ts 文件方法非常簡(jiǎn)單,只需要在終端輸入一行命令:
ffmpeg -f concat -i filename.txt -c copy output.mp4
首先在ts文件相同目錄中,把所有要合并的ts文件名保存在filename.txt:如下圖:
然后使用命令行進(jìn)行合并,這里要注意文件路徑,合并完成后刪除ts文件,這里為了防止誤刪使用send2trash庫(kù),刪除內(nèi)容可以放進(jìn)回收站,代碼如下:
import?send2trash
import?os
def?merge_video(name):
????#?合并視頻
????ts_url_list=[]
????with?open(name?+?".m3u8",?'r',?encoding='utf-8')?as?f:
????????for?line?in?f:
????????????if?line.startswith('#'):
????????????????continue
????????????line?=?line.strip()
????????????ts_url_list.append(line)
????f?=?open(f"./temp_{name}/filename.txt"?,'w'?,encoding="utf-8")
????for?ts_url?in?ts_url_list:
????????ts_name?=?os.path.splitext(ts_url)[0].split("/")[-1]
????????f.write("file??"+?ts_name?+".ts\n")
????f.close()
????print(f"{name}開始合并。。。。")
????os.system(f"ffmpeg?-f?concat?-i?./temp_{name}/filename.txt?-c?copy?{name}.mp4")
????print(f"{name}視頻合成成功")
def?remove_temp(name):
????#?刪除ts文件,放入回收站
????send2trash.send2trash(f"./temp_{name}")
????send2trash.send2trash(f"{name}.m3u8")
????print("臨時(shí)文件已刪除。")
if?__name__?==?'__main__':
????name?=?"國(guó)王排名-第19集"
????merge_video(name)
????remove_temp(name)
最后,推薦螞蟻老師的Python課程:
如果購(gòu)買課程,加微信:ant_learn_python
找螞蟻老師,進(jìn)群、領(lǐng)資料
