记录:用Python爬取网页视频
相关:python、requests爬虫、m3u8文件、合成ts
前几天刚好自学了python爬虫,就有一个想法:爬取网页上的视频资源。so说干就干!
但是由于只学python基础语法,对视频格式也不是很了解,所以这一干就是两个晚上(周四、周五下班后),今天刚好是周六,所以记录一下两晚上的成果吧!
一、获取网页的HTML代码
当然,这很简单
requests.get(url).text
将他保存到一个txt文件中,便于操作
我们打开文件,Ctl+f搜索,发现没有所谓的mp4等这种可以直接下载播放的文件链接(根据网站不同,当然如果有mp4链接,情况就简单一万倍了,下面你就不用看下去了)
我们可以找到m3u8的字符串
这里有五个.m3u8链接,我猜是因为该网站有5个播放线路,那我们自然选择第一条线路(HD)去爬取视频资源喽!
二、处理m3u8文件
关于m3u8
m3u8是苹果公司推出一种视频播放标准,是m3u的一种,不过 编码方式是utf-8,是一种文件检索格式,将视频切割成一小段一小段的ts格式的视频文件,然后存在服务器中(现在为了减少I/o访问次数,一般存在服务器的内存中),通过m3u8解析出来路径,然后去请求。
1.
HD%u9ad8%u6e05%24https%3A%2F%2Fyouku.com-l-youku.com%2F20190207%2F20335_d1f19bfb%2Findex.m3u8
这个链接根本打不开 ,进过我研究发现,链接前面有 mac_url=unescape( 字样,那就说明后面的链接需要解码才能使用。这个解码方法我查了一下:
s2 = unescape (s1)
规则:
所有以 %xx 十六进制形式编码的字符都用 ASCII 字符集中等价的字符代替。
以 %uxxxx 格式(Unicode 字符)编码的字符用十六进制编码 xxxx 的 Unicode 字符代替。
由此,我们可以对这串字符进行解码:
前面“HD%u9ad8%u6e05%24”代表“HD高清$”,不是链接的一部分,因此我们忽略即可。
# 字符(十六进制)转ASCII码
def hexToAscii(h):
d = int(h,16) # 转成十进制
return chr(d) # 转成ASCII码
# 从得到的html代码中获取m3u8链接(不同网站有区别)
def getM3u8(http_s):
ret1 = http_s.find("unescape")
ret2 = http_s.find(".m3u8")
ret3 = http_s.find("http", ret1, ret2) # "unescape"和".m3u8"之间找"http"
m3u8_url_1 = http_s[ret3: ret2 + 5] # 未解码的m3u8链接
# 下面对链接进行解码
while True:
idx = m3u8_url_1.find('%')
if idx != -1:
m3u8_url_1 = m3u8_url_1.replace(m3u8_url_1[idx:idx+3], \
hexToAscii(m3u8_url_1[idx+1:idx+3]))
else:
break
return m3u8_url_1
NICE
这样我们就得到了m3u8的链接
https://youku.com-l-youku.com/20190207/20335_d1f19bfb/index.m3u8
网上也有在线解码的网站,我们可以测试一下
是不是一模一样!!!
而且,我们可以利用网上的m3u8在线播放测试网站测试一下,可以直接播放!!这样才能说明得到的链接是正确的
2.
ok,那接下来我们看看这个链接里面到底是个什么东西呢?直接输入浏览器,会让你下载一个.m3u8的文件,下载下来记事本(notepad++)打开看
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608
1000k/hls/index.m3u8
那么,这个又是什么鬼呢???
原来,
m3u8链接分层的(我是这么理解的),所以还需要解析,最后一行的 1000k/hls/index.m3u8 其实是获取第二层KEY。即要用它替换掉之前解析出来的链接的最后的"index.m3u8",构成新的链接。
https://youku.com-l-youku.com/20190207/20335_d1f19bfb/1000k/hls/index.m3u8
# 寻找字符串s中最后出现字符c的index
def findLastchr(s, c):
ls = []
sum = 0
while True:
i = s.find(c)
if i != -1:
s = s[i+1:]
ls.append(i)
else:
break
for i in range(len(ls)):
sum += (ls[i] + 1)
return sum - 1
def getM3u8_2(m3u8_url_1):
r1 = requests.get(m3u8_url_1)
r1.raise_for_status()
text = r1.text
idx = findLastchr(text, '\n')
key = text[idx + 1:] # 得到第一层m3u8中的key
idx = findLastchr(m3u8_url_1, '/')
m3u8_url_2 = m3u8_url_1[:idx + 1] + key # 组成第二层的m3u8链接
return m3u8_url_2
好,接下来我们浏览器打开这个链接,又会下载一个.m3u8的文件,记事本打开可以看到
我们看到的
284e2012ca2000000.ts 等等… …
当然要替换掉第二层m3u8链接后面的index.m3u8就是ts链接了。
例如,
https://youku.com-l-youku.com/20190207/20335_d1f19bfb/1000k/hls/284e2012ca2000000.ts
ts文件会按照顺序编号的
比如我这里从 284e2012ca2000000.ts 到 284e2012ca2001806.ts
上面是直接用浏览器下载,我们这里用python程序自动做这个事情(会调用到前面定义过的函数)
# 从最原始的url-->生成一个ts列表的文件
def getTsFile(url, filename):
try:
r = requests.get(url)
r.encoding = r.apparent_encoding
r.raise_for_status()
http_s = r.text
m3u8_url_1 = getM3u8(http_s)
print("第一层m3u8链接" + m3u8_url_1)
m3u8_url_2 = getM3u8_2(m3u8_url_1)
print("第二层m3u8链接" + m3u8_url_2)
# 通过新的m3u8链接,获取真正的ts播放列表
# 由于列表比较长,为他创建一个txt文件
r2 = requests.get(m3u8_url_2)
f = open(filename, "w", encoding="utf-8") # 这里要改成utf-8编码,不然默认gbk
f.write(r2.text)
f.close()
print("创建ts列表文件成功")
return "success"
except:
print("爬取失败")
return "failed"
ts文件处理
1.
毕竟这些个链接太多了,用链表管理起来比较方便,一会批量下载的话可以使用索引进行循环
分两个步骤:
1.提取ts列表文件的内容,逐个拼接ts的url,形成list
2.批量下载ts文件
# 提取ts列表文件的内容,逐个拼接ts的url,形成list
def getPlayList(filename, m3u8_url_2):
ls = []
f = open(filename, "r")
line = " " # line不能为空,不然进不去下面的循环
idx = findLastchr(m3u8_url_2, '/')
while line:
line = f.readline()
if line != '' and line[0] != '#':
line = m3u8_url_2[:idx+1] + line
ls.append(line[:-1]) # 去掉'\n'
return ls
# 批量下载ts文件
def loadTs(ls):
root = "D://mp4//"
length = len(ls)
try:
if not os.path.exists(root):
os.mkdir(root)
for i in range(length):
path = root + ls[i][-7:]
r = requests.get(ls[i])
with open(path, 'wb') as f:
f.write(r.content)
f.close()
print(path + " --> OK ( {} / {} ){:.2f}%".format(i , length, i*100/length))
print("全部ts下载完毕")
except:
print("批量下载失败")
我这里用的是D:\mp4路径,当然也可以改
注意:下载生成ts文件时,命名一定要规范,
如:0000.ts 0001.ts 0002.ts … … 1806.ts
不能 1.ts 2.ts 3.ts … … 1806.ts
因为下面合并的时候是按照字符匹配的
2.
下面我们进行ts合并成mp4
其实就是调用Windows的命令copy /b *.ts new.mp4
直接放代码
# 整合所有ts文件,保存为mp4格式
def tsToMp4():
print("开始合并...")
root = "D://mp4//"
outdir = "output"
os.chdir(root)
if not os.path.exists(outdir):
os.mkdir(outdir)
os.system("copy /b *.ts new.mp4")
os.system("move new.mp4 {}".format(outdir))
print("结束合并...")
OK
这样就大功告成了!!
这里我就不给出main函数了,我的全部源代码链接如下: