Python网络爬虫 第五章 抓取视频
之前在第三章的例子中爬取了梨视频的视频,那么那种方式是否也适合爬取电视剧或者电影呢?其实不是这样的。
我们想要抓取⽹上的视频资源就必须要了解我们的视频⽹站是如何⼯作的,这⾥我⽤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合并速度超快。