Python爬虫 爬取视频
现在很多视频网站采用流媒体技术进行播放视频,一种常见的方案是m3u8文件+ts文件 。 m3u8是苹果公司推出一种视频播放标准,是m3u的一种,不过编码方式是utf-8,是一种文件检索格式,将视频切割成一小段一小段的ts格式的视频文件,然后存在服务器中(现在为了减少I/o访问次数,一般存在服务器的内存中),通过m3u8解析出来路径,然后去请求。
本来是打算按照教程爬取一个解析VIP视频的网站上的视频,但是原网站已经找不到了。经过一番努力,找到了另一个vip解析网站:http://www.qmaile.com/。
我们以爱奇艺视频的加勒比海盗5为例:http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1。这个视频必须要有VIP才能看完整版,但我们可以通过上述VIP视频解析网站来在线看这些VIP视频,但是这个网站只提供了在线解析视频的功能,没有提供下载接口,如果想把视频下载下来,我们就可以利用网络爬虫进行抓包,将视频下载下来。
一、
经过测试发现不同的视频有不同的加载方式,例如神奇动物:格林德沃之罪(https://www.iqiyi.com/v_19rr7p5sag.html?vfrm=pcw_playpage&vfrmblk=F&vfrmrst=80521_correlation_star_image2),通过抓包解析VIP的这个动作,我们可以直接得到这个视频在服务器上的缓存地址如下所示,根据这个地址,我们就可以轻松下载视频了。
进入这个url后,可以直接转存视频,如下所示:
也可以利用爬虫下载这个视频,下载速度很快,代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import requests,re, json, sys
from urllib import request
def _progress(block_num, block_size, total_size):
'''回调函数
@block_num: 已经下载的数据块
@block_size: 数据块的大小
@total_size: 远程文件的大小
'''
sys.stdout.write('\r>> Downloading %s %.1f%%' % (filename,
float(block_num * block_size) / float(total_size) * 100.0))
def video_download(url, filename):
request.urlretrieve(url=url,filename=filename,reporthook=_progress)
if __name__ == '__main__':
url="http://vwecam.tc.qq.com/1006_46214f775a20481da80278d41621dbbb.f0.mp4?vkey=12A318FDC135A83A15C7FD5051B126034A0381F693160CC0A5467B4F101D15844FA7F0437AD984902A90E95552A9876BB8BDB5B43ED9F703&rf=mobile.qzone.qq.com"
filename = '神奇动物'
print('%s下载中:' % filename)
video_download(url, filename+'.mp4')
print('\n下载完成!')
|
要注意爬取的时效性比较低,其url每次解析都在变化,所以当长时间为爬取需要在抓包分析其变化后的url。另外如果遇到<403>Forbidden的情况,需要加入headers,headers的内容也可以通过抓包提取。
二、
接下来爬取通过另一种方式加载的视频,即m3u8文件+ts文件 ,以加勒比海盗5为例:http://www.iqiyi.com/v_19rr7qhfg0.html#vfrm=19-9-0-1。在抓包时发现它是以流媒体的形式加载的,当视频解析成功开始播放时,抓包如下:
我们获取一下改url的内容:
可以看到有一系列的4WudJ1vi1366000.ts等,将其替换掉m3u8链接后面的index.m3u8就是ts文件的链接了,同时 ts文件会按照顺序编号 。爬取ts文件的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import requests
# from contextlib import closing
def getPlayList(url):
html=requests.get(url).text
ts=html.split()
ts_1ist=ts[5:][::2]
return ts_1ist
# def dl_ts(ts_url, i):
# with closing(requests.get(ts_url, stream=True, verify = False)) as r:
# with open('%d.ts' % i, 'ab+') as f:
# for chunk in r.iter_content(chunk_size = 1024):#当流下载时,这是优先推荐的获取内容方式。
# if chunk:
# f.write(chunk)
# f.flush()#清空缓冲区
def dl_ts(ts_url, i):
r = requests.get(ts_url)
with open(str(i+1)+".ts", 'wb') as f:
f.write(r.content)
f.close()
if __name__ == '__main__':
url="https://sohu.com-v-sohu.com/20170915/PehRQmVm/1000kb/hls/index.m3u8"
ts_list=getPlayList(url)
length=len(ts_list)
# for i in range(length):
for i in range(100):
ts_url = "https://sohu.com-v-sohu.com/20170915/PehRQmVm/1000kb/hls/{}" .format(ts_list[i])
print("下载第{}个".format(i+1))
dl_ts(ts_url, i)
|
下载比较慢,在下载完成后,将所有的ts文件合并到一起即可,合并代码如下所示:
1
2
3
4
5
6
7
8
9
10
|
import os
def tsToMp4():
print("开始合并...")
root = "D://python code//数据爬取//"
outdir = "output"
os.chdir(root)
# copy /b F:\f\*.ts E:\f\new.ts
os.system("copy /b *.ts new.mp4")
# os.system("move new.mp4 {}".format(outdir))
print("结束合并...")
|
大功告成!(ps:下载速度太慢了)
后又尝试多进程爬取,代码如下,后面再继续改进:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
from time import sleep
import socket
import requests
from contextlib import closing
from multiprocessing import Process
class CatchVideo(object):
def __init__(self):
self.url="https://sohu.com-v-sohu.com/20170915/PehRQmVm/1000kb/hls/index.m3u8"
self.ts_list=[]
def getPlayList(self):
html=requests.get(self.url).text
ts=html.split()
self.ts_1ist=ts[5:][::2]
def set_url(self,i):
tsurl = "https://sohu.com-v-sohu.com/20170915/PehRQmVm/1000kb/hls/{}" .format(self.ts_list[i])
return tsurl
def dl_ts(self,url_1, i):
with closing(requests.get(url_1, stream=True, verify = False)) as r:
with open('%d.ts' % i, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):#当流下载时,这是优先推荐的获取内容方式。
if chunk:
f.write(chunk)
f.flush()#清空缓冲区
def start_work(self,i):
url_1=set_url(i)
try:
dl_ts(url_1,i)
print(str(i) + ".ts success")
sleep(1)
except socket.timeout as e:
print(e.reason)
dl_ts(url_1,i)
if __name__ == '__main__':
catch_video = CatchVideo()
socket.setdefaulttimeout(20)# 设置socket层超时时间20秒
I = 0
while I < 7747+1:
#7747为总的长度
# 5个进程并发运行
p_l = [Process(target=catch_video.start_work, args=(i,)) for i in range(I, I+3)]
for p in p_l:
p.start()
for p in p_l:
p.join()
I = I + 5
|