【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4

时间:2024-03-31 09:01:11

记录:用Python爬取网页视频

相关:python、requests爬虫、m3u8文件、合成ts

前几天刚好自学了python爬虫,就有一个想法:爬取网页上的视频资源。so说干就干!
但是由于只学python基础语法,对视频格式也不是很了解,所以这一干就是两个晚上(周四、周五下班后),今天刚好是周六,所以记录一下两晚上的成果吧!

一、获取网页的HTML代码

当然,这很简单
requests.get(url).text
将他保存到一个txt文件中,便于操作
我们打开文件,Ctl+f搜索,发现没有所谓的mp4等这种可以直接下载播放的文件链接(根据网站不同,当然如果有mp4链接,情况就简单一万倍了,下面你就不用看下去了)
我们可以找到m3u8的字符串
【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4
这里有五个.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

网上也有在线解码的网站,我们可以测试一下
【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4
是不是一模一样!!!

而且,我们可以利用网上的m3u8在线播放测试网站测试一下,可以直接播放!!这样才能说明得到的链接是正确的
【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4

2.

ok,那接下来我们看看这个链接里面到底是个什么东西呢?直接输入浏览器,会让你下载一个.m3u8的文件,下载下来记事本(notepad++)打开看
【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4

#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的文件,记事本打开可以看到

【python爬虫】爬取网页视频,解析m3u8文件,获取ts并合成mp4
我们看到的
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函数了,我的全部源代码链接如下:

https://download.csdn.net/download/qq_39797956/10990259