介绍:
在自学爬虫时,一开始就是爬取一些文字(比如小说之类的),再后来变成了图片,再后来变成了视频…
其中最简单的就是对文字的爬取了,从文字过渡到图片也只是多了几行代码而已,但是: 从图片过渡到视频就要 分情况了。
分情况解释:
第一种情况: 链接明确是以 mp4、mkv、rmvb 这类视频格式后缀为结尾的链接,这种下载很简单,和图片下载的方法一样,就是视频文件要比图片大而已。
第二种情况: 另一种,链接是以 m3u8 这类以多个 ts 文件组成的链接。
ts文件 ? 链接: 百度知道。
然而,在进行爬取的过程中,你会发现:第二种情况又有两种呈现方式:
第一种: 网页源码中的链接直接以 m3u8 结尾,这类的链接,推荐使用 FFmpeg 进行下载。(注意该工具下载成功后需要配置环境变量才可以使用。)
使用方法:
import ffmpy3
ffmpy3.FFmpeg(inputs={'http://***.m3u8': None}, outputs={name+'.mp4':None}).run()
FFmpeg 可以帮你下载 m3u8 格式的视频,而且还能帮你自动转换为 mp4 的格式,并且不会出现拼接 ts 文件时乱序的情况。
第二种: 也是我今天着重说的一种情况。网页源码中并没有以 m3u8 格式结尾的链接。而是由在网页中直接请求并解析 ts 文件。
比如以下的视频网站:
分析及爬取:
我们就在当前网站爬取一个前段时间热播的动漫:《天气之子》 来演示用最为基础的 with open() 来爬取 ts 文件并合成为 mp4 格式的方法。
进入 天气之子 的主页,通过控制台我们需要构建一点简单的头文件来防止反爬: (主链接被我修改了,怕被和谐,这里主要分享的是方法。)
header = {
'origin': 'https://www.******.tv',
'referer': 'https://www.******.tv/py/lJWMpVmYqRWb_1.html?158064',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72'
}
由于我曾经被拉进过网站的黑名单: 所以还是默默的写个代理 IP 吧。
不会写 IP代理 ? 链接: Python爬虫,自建IP地址池.
proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999',
'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999',
'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999',
'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80']
proxy = {'HTTP': random.choice(proxies)} #随机选取一个IP
通过点击每一个 ts 文件,我们发现,对于每一个 ts 文件的请求地址都差不多:具体为:
https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400(****).ts
括号中的 * 代表数字,如: 0001、0002、0003…9999
通过拉动进度条可以快速地查看,最大的数字为 1613。
我们来试着请求 一下某段 ts 文件:
import os
import requests
import random
header = {
'origin': 'https://www.pianku.tv',
'referer': 'https://www.pianku.tv/py/lJWMpVmYqRWb_1.html?158064',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72'
}
proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999',
'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999',
'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999',
'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80']
proxy = {'HTTP': random.choice(proxies)}
print(proxy)
path = './Spider/'
if os.path.exists(path + "天气之子/"):
pass
else:
os.makedirs(path + "天气之子/")
def download():
url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804000001.ts'
with open(path + "天气之子/" + str(url).split('/')[-1][-7:], 'wb') as f:
#已知最大的数字为 1613 ,所以以最后4位数字作为 ts 文件的名字。
try:
r = requests.get(url, proxies=proxy, headers=header)
r.raise_for_status()
r.encoding = 'utf-8'
f.write(r.content)
except:
print('请求失败!')
if __name__ == '__main__':
download()
于是我们就得到了第一个 ts 文件:
双击查看,结果是:可以打开播放:
接下来我们试着将所有的 ts 文件下载下来,并且以最后4位数字作为 ts 文件的名字:
首先想到的就是用 for 循环,问题倒是没有,就是太慢了,所以改进为使用线程池来下载:
#
# 前面的部分不变,在此省略
#
def download(num):
# https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400(****).ts
url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num)
#在这里用了 str.format() 中的 {:0>4d} 来控制位数
with open(path + "天气之子/" + str(url).split('/')[-1][-7:], 'wb') as f:
try:
r = requests.get(url, proxies=proxy, headers=header)
r.raise_for_status()
r.encoding = 'utf-8'
f.write(r.content)
print('正在下载第 {} 个片段。'.format(num))
except:
print('请求失败!')
if __name__ == '__main__':
# 开启线程池
start_time = time.time()
pool = ThreadPool(100)
results = pool.map(download, range(1, 1613+1)) # range含左不含右
pool.close()
pool.join()
但是: 一波未平一波又起,下载时,有些片段会秒下载,而有些片段则会下载的特别慢,甚至有些片段直接下载失败!!!!!
如果没有某个片段,剧情不就会跳着走吗?这可咋整,刚进入剧情,正为男女主角捏把汗的时候,一下就给我踢出来了,满头黑人问号,气得我直接口吐芬芳。
解决方案: 我们知道,当我们修改最后的四位数字时,它会直接跳转下载对应的片段,难道要一个一个的去看 ts 文件?不、不、不,当然不需要。
接下来,看看我的简单想法: 创建一个 list 将所有失败的尾数全都追加进去,在第一次程序下载结束时,仅对该 list 中对应的 ts 文件进行下载:
#
# 前面的部分不变,在此省略
#
def download(num, flag=0): #
# https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804001613.ts
url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num)
with open(path + "天气之子/" + str(url).split('/')[-1][-7:], 'wb') as f:
try:
r = requests.get(url, proxies=proxy, headers=header, timeout=5)
#对于下载比较慢的情况,设置 timeout = 5
r.raise_for_status()
r.encoding = 'utf-8'
print('正在下载第 {} 个片段。'.format(num))
f.write(r.content)
if flag == 1: # 对于从 list 中第二次下载的情况,下载成功,则从 list 中删去。
failure_list.remove(num)
except:
print('请求失败!')
if num not in failure_list:
failure_list.append(num) # 如果下载失败或者超时,则添加进 list
def check_ts():
print("开始检查:")
while failure_list: #()、{}、[] 都相当于 false
for num in failure_list:
download(num, 1)
# 传入标识 1
if __name__ == '__main__':
# 开启线程池
start_time = time.time()
pool = ThreadPool(100)
results = pool.map(download, range(1, 1613+1))
pool.close()
pool.join()
check_ts()
结果如下:
ts 文件的合并与转换为 mp4 格式:
经过小小的等待,现在,我们已经下载完成了所有的 ts 文件。
下载完成,有人会说:诶~, ts 文件都是可以播放的,直接用播放器顺序播放不久行了嘛。
但是: 我还没发现有哪款视频播放器能做到完全无卡顿感的切换这么多的视频,每几秒钟就 “ 卡顿 ” 一下,观影效果大打折扣。
所以还是乖乖的把它们都合并了吧。至于怎么合并,且听我慢慢道来:
第一步:我们先定义一个用于合并 ts 文件的函数:def get_video():
第二步:利用 files = os.listdir( path ) 拿到所有的文件名,所有的 ts 文件都是从小到大排列的,所以我们拿到的文件名也是有顺序的。
第三步:遍历每一个文件名, 通过 with open() 以二进制的方式打开并读取该文件(f1),接着再使用一个 with open() 打开目标文件(f2),将从 f1 中读取出来的二进制文件以二进制追加写的方式写入到 f2 中 。
遍历时用 tqdm 库进行显示进度条。结果因为转换的速度太快,根本看不到进度条时怎么走的,几乎是秒转换,所以:我是为了什么加的进度条来着?😱😱😱
def get_video():
files = os.listdir(path + "天气之子/")
for file in tqdm(files, desc="正在转换视频格式:"):
if os.path.exists(path + "天气之子/" + file):
with open(path + "天气之子/" + file, 'rb') as f1:
with open(path + "天气之子.mp4", 'ab') as f2:
f2.write(f1.read())
else:
print("失败")
运行结果:
双击查看,结果是:可以打开播放: (快乐呀!)
快乐的白嫖生活就要开始了!
源码如下:
import os
import requests
import random
from multiprocessing.pool import ThreadPool
from tqdm import tqdm
header = {
'origin': 'https://www.pianku.tv',
'referer': 'https://www.pianku.tv/py/lJWMpVmYqRWb_1.html?158064',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72'
}
proxies = ['HTTP://110.243.30.23:9999', 'HTTP://222.189.191.206:9999', 'HTTP://118.212.104.138:9999',
'HTTP://182.149.83.97:9999', 'HTTP://106.42.163.100:9999', 'HTTP://120.83.107.69:9999',
'HTTP://60.13.42.135:9999', 'HTTP://60.205.188.24:3128', 'HTTP://113.195.232.23:9999',
'HTTP://59.62.36.74:9000', 'HTTP://218.2.226.42:80']
proxy = {'HTTP': random.choice(proxies)}
print(proxy)
path = './Spider/'
if os.path.exists(path + "天气之子/"):
pass
else:
os.makedirs(path + "天气之子/")
failure_list = [] # 保存下载失败的片段
def download(num, flag=0):
# https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb1804001613.ts
url = 'https://*****/20200508/19312_c9d456ff/1000k/hls/d3276cb180400{:0>4d}.ts'.format(num)
with open(path + "天气之子/" + str(url).split('/')[-1][-7:], 'wb') as f:
try:
r = requests.get(url, proxies=proxy, headers=header, timeout=5)
r.raise_for_status()
r.encoding = 'utf-8'
print('正在下载第 {} 个片段。'.format(num))
f.write(r.content)
if flag == 1:
failure_list.remove(num)
except:
print('请求失败!')
if num not in failure_list:
failure_list.append(num)
def get_video():
files = os.listdir(path + "天气之子/")
for file in tqdm(files, desc="正在转换视频格式:"):
if os.path.exists(path + "天气之子/" + file):
with open(path + "天气之子/" + file, 'rb') as f1:
with open(path + "天气之子.mp4", 'ab') as f2:
f2.write(f1.read())
else:
print("失败")
def check_ts():
print("开始检查:")
while failure_list:
for num in failure_list:
download(num, 1)
print("ts 文件下载完成!")
get_video()
if __name__ == '__main__':
# 开启线程池
pool = ThreadPool(100)
results = pool.map(download, range(1, 1613+1))
pool.close()
pool.join()
check_ts()
运行时示例:
正在下载第 396 个片段。
正在下载第 31 个片段。
。。。。。。
正在下载第 257 个片段。
请求失败!
正在下载第 1570 个片段。
正在下载第 1585 个片段。
开始检查:
请求失败!
正在下载第 141 个片段
正在下载第 855 个片
。。。。。。
请求失败!
。。。。。。
正在下载第 945 个片段。
正在下载第 485 个片段。
正在转换视频格式:: 100%|██████████| 1613/1613 [00:03<00:00, 496.47it/s]
共耗时:195.7295339107513
转载:https://blog.csdn.net/qq_44700693/article/details/106189511