之前在第三章的例子中爬取了梨視頻的視頻,那么那種方式是否也適合爬取電視劇或者電影呢?其實不是這樣的。
我們想要抓取⽹上的視頻資源就必須要了解我們的視頻⽹站是如何⼯作的,這⾥我⽤91看劇來做舉例.,其他⽹站的原理是⼀樣的。
一、視頻⽹站是如何⼯作的
假設, 你現在想要做⼀個視頻⽹站. 也有很多的UP主幫你上傳視頻, 作為服務器作者的你. 只需要把視頻保存起來. 然后給出⼀個視頻的鏈接即可. 然后在你的HTML代碼中通過 video 標簽引⼊即可.
<video src="爬⾍概述.mp4"></video>
就可以了. 但是, 如果你這么做. 你的⽤戶和⽼板⼀定會把你罵的狗⾎臨頭. 為什么呢?
假設你的視頻是10個G的⾼清⽆碼⼤資源. 那么此時, 你的⽤戶和你⽼板將⾯臨如下困境
- 1. ⽤戶: 這個視頻怎么加載的這么慢. 點擊快進也快進不了. 太慢了
- 2. ⽼板: 怎么這個⽉的流量費⼜這么⾼啊. 要死的拉好不~
為什么會這樣? 聰明的我告訴你答案. 你的視頻那么⼤. 每次⽤戶打開的時候. 可能只是差了最后⼏分鍾沒看呢. 那此時它必須把整個視頻都傳輸完畢. 才能看到他想看的那⾥. 等待時間肯定超⻓的好不. ⽽每次都要把10G的⽂件進⾏⽹絡傳輸. 流量費~你懂的. 三⼤運營商最喜歡的就是你這種朴實⽆華的送錢⾏為.
OK~ 不扯了. 但凡有點⼉經驗的程序員肯定會想辦法把⽤戶上傳好的視頻進⾏轉碼(不同清晰度)做切⽚(ts)處理. 這樣既⽅便⽤戶進⾏⼤跨度的調整進度條(最⼩延遲). 也能為公司節省⼤量的流量費.既然要把視頻切成⾮常多個⼩碎⽚. 那就需要有個⽂件來記錄這些⼩碎⽚的路徑. 該⽂件⼀般為M3U⽂件. M3U⽂件中的內容經過UTF-8的編碼后, 就是M3U8⽂件. 今天, 我們看到的各⼤視頻⽹站平台使⽤的⼏乎都是M3U8⽂件。
如何解讀M3U8⽂件?
二、爬取91電影
知道了這些,我們就開始簡單練手,目標電視劇是最近很火的《小舍得》
https://www.91kanju.com/vod-play/58988-1-1.html
利用F12抓包,很輕松就獲得了M3U8文件的鏈接
https://m3api.awenhao.com/index.php?note=kkRhwgadty8k3zr5c9n7b&raw=1&n.m3u8
但是似乎打不開?怎么回事呢?
查看一下網頁源代碼,里面的viedo鏈接地址
https://m3api.awenhao.com/index.php?note=kkRp5ws8htmgcbn69e1a7&raw=1&n.m3u8
二者看似相同,但note后半部分內容卻不太一樣,這其實是一種反爬蟲機制。
訪問過程如圖所示,這種反爬蟲機制就是要求訪問必須是從
https://www.91kanju.com/vod-play/58988-1-1.html
頁面發出的,因為這里才有note=kkRp5ws8htmgcbn69e1a7信息。
下面就可以編寫爬蟲代碼 了,代碼流程是:
- 1. 拿到548121-1-1.html的頁面源代碼
- 2. 從源代碼中提取到m3u8的url
- 3. 下載m3u8
- 4. 讀取m3u8文件, 下載視頻
- 5. 合並視頻
import requests import re headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36" } obj = re.compile(r"url: '(?P<url>.*?)',", re.S) # 用來提取m3u8的url地址 url = "https://www.91kanju.com/vod-play/58988-1-1.html" resp = requests.get(url, headers=headers) m3u8_url = obj.search(resp.text).group("url") # 拿到m3u8的地址 print(m3u8_url) resp.close() # 下載m3u8文件 resp2 = requests.get(m3u8_url, headers=headers) with open("小舍得.m3u8", mode="wb") as f: f.write(resp2.content) resp2.close() print("下載完畢") # 解析m3u8文件 n = 1 with open("小舍得.m3u8", mode="r", encoding="utf-8") as f: for line in f: line = line.strip() # 先去掉空格, 空白, 換行符 if line.startswith("#"): # 如果以#開頭. 我不要 continue # 下載視頻片段 resp3 = requests.get(line) f = open(f"video/{n}.ts", mode="wb") f.write(resp3.content) f.close() resp3.close() n += 1 print("完成了1個")
雖然最后下載下來了,但是,不得不說下載速度真的很慢,下面使用多線程和異步協程改進一下。
import aiofiles import aiohttp import requests import asyncio import re import os # 獲取m3u8的url def get_m3u8_url(url, headers): resp = requests.get(url, headers=headers) obj = re.compile(r"url: '(?P<url>.*?)',", re.S) # 用來提取m3u8的url地址 m3u8_url = obj.search(resp.text).group("url") # 拿到m3u8的地址 resp.close() return m3u8_url # 下載m3u8文件 def download_m3u8_file(url, headers): resp = requests.get(url, headers=headers) with open("小舍得.m3u8", mode="wb") as f: f.write(resp.content) resp.close() print("下載完畢") async def download_ts(url, name, session): async with session.get(url) as resp: async with aiofiles.open(f"video/{name}.ts", mode="wb") as f: await f.write(await resp.content.read()) # 把下載到的內容寫入到文件中 print(f"{name}下載完畢") # 解析m3u8文件 異步下載 async def aio_download(): tasks = [] n = 1 async with aiohttp.ClientSession() as session: # 提前准備好session async with aiofiles.open("小舍得.m3u8", mode="r", encoding='utf-8') as f: async for line in f: line = line.strip() # 去掉沒用的空格和換行 # line就是xxxxx.ts if line.startswith("#"): continue task = asyncio.create_task(download_ts(line, n, session)) # 創建任務 tasks.append(task) n = n + 1 await asyncio.wait(tasks) # 等待任務結束 def merge_ts(): # mac: cat 1.ts 2.ts 3.ts > xxx.mp4 # windows: copy /b 1.ts+2.ts+3.ts xxx.mp4 os.system(f"copy /b *.ts> movie.mp4") print("搞定!") def main(url, headers): m3u8_url = get_m3u8_url(url, headers) print(m3u8_url) download_m3u8_file(m3u8_url, headers) # 異步協程 asyncio.run(aio_download()) # 測試的使用可以注釋掉 # merge_ts() if __name__ == '__main__': headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36" } url = "https://www.91kanju.com/vod-play/58988-1-1.html" main(url, headers)
關於合並視頻
打開cmd,切進目錄,執行copy /b *.ts video.ts合並速度超快。