大家好,我是小小明。

今天我将带大家一步步来研究如何下载B站直播视频。

获取直播间下载地址

首先获取指定分区直播间id列表:

import requests
from lxml import etreedef get_room_ids(room_type="学习"):urls = {"学习": "https://live.bilibili.com/p/eden/area-tags?visit_id=9ynmsmaiie80&areaId=377&parentAreaId=11","颜值领域": "https://live.bilibili.com/show/yzly?visit_id=3g19a7bxnb60"}res = requests.get(urls[room_type])res.encoding = res.apparent_encodinghtml = etree.HTML(res.text)room_ids = {}for a in html.xpath("//ul/li/a"):url = a.xpath("./@href")[0]tags = a.xpath(".//text()")room_ids[tags[1]] = url[1:url.find("?")]return room_idsroom_ids = get_room_ids()
room_ids
{'公考课堂——刘文超': '21283497','工业算法选型及调优策略': '21689802','是个废狮': '22029374','进行一个模型的做': '1070197','Lumist Navi——简历面试指南': '22518797','教建模的大叔': '22590752','随便剪剪': '36431','blender,干空间站': '4874724','【若鹭姬工作室】游戏制作日常': '5688995','【建模】nomad 建模': '1261365','戳戳戳': '969007','全能音乐人入门课第二期-第一节直播': '93751','PS学习分享间': '14352877','马士兵老师两天带你彻底征服多线程!': '21365314','十道真题搞定SQL': '11209769','超nice的扁平风IP形象插画来了!': '1327479','直播作曲&编曲~': '71040','<python副业>月入2万+直播分享': '23202081','前端编程小姐姐在线写游戏网页': '22273117','零基础学Python/前端': '14584642'}

获取了当前时间学习分区中最热门的前N个直播间id,下面我计划获取PS学习分享间的直播间。

获取指定直播间m3u8视频流地址:

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}def get_live_url(cid, platform='h5'):playUrl = 'https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl'params = {'cid': cid,  # cid序列号'qn': 150,  # 播放的视频质量'platform': platform,  # 视频的播放形式'ptype': 16}response = requests.get(playUrl, headers=headers, params=params).json()text = response['data']['durl']url = text[-1]['url']return urlurl = get_live_url(room_ids['PS学习分享间'])
url
'https://d1--cn-gotcha103.bilivideo.com/live-bvc/779295/live_346651764_2556686_1500.m3u8?cdn=cn-gotcha03&expires=1624891101&len=0&oi=1947748628&pt=h5&qn=150&trid=100375c02ceb030e4ee082e77a292b8bba6e&sigparams=cdn,expires,len,oi,pt,qn,trid&sign=ecd48c9e8c8ea467004377dedd16a384&ptype=0&src=5&sl=1&sk=677197cc63610fc&order=4'

使用m3u8模块解析一下该地址:

import m3u8playlist = m3u8.load(uri=url, headers=headers)
print("segments:", playlist.segments)
print("target_duration:", playlist.target_duration)
print("keys:", playlist.keys)
print("playlists", playlist.playlists)
segments:
target_duration: None
keys: []
playlists #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=10000000
https://236809367.cloudvdn.com/a.m3u8?cdn=cn-gotcha03&domain=d1--cn-gotcha103.bilivideo.com&expires=1624891101&len=0&oi=1947748628&order=4&player=bTMAAGE2uvzMwowW&pt=h5&ptype=0&qn=150&secondToken=secondToken%3AAU83VLPVHkaN2K6QFa3M4hu5i9I&sign=ecd48c9e8c8ea467004377dedd16a384&sigparams=cdn%2Cexpires%2Clen%2Coi%2Cpt%2Cqn%2Ctrid&sk=677197cc63610fc&sl=1&src=5&streamid=live-qn%3Alive-qn%2Flive_346651764_2556686_1500&trid=100375c02ceb030e4ee082e77a292b8bba6e&v3=1

只有playlists有数据,说明该m3u8是个嵌套类型的。

接下来解析出内部的m3u8地址:

def get_real_url(url):playlist = m3u8.load(uri=url, headers=headers)return playlist.playlists[0].absolute_urireal_url = get_real_url(url)
real_url
'https://236809362.cloudvdn.com/a.m3u8?cdn=cn-gotcha03&domain=d1--cn-gotcha103.bilivideo.com&expires=1624891101&len=0&oi=1947748628&order=4&player=BEEAAPDKUQjywowW&pt=h5&ptype=0&qn=150&secondToken=secondToken%3A2KUkkU8RBKYtssdUqEbh-z-vpkQ&sign=ecd48c9e8c8ea467004377dedd16a384&sigparams=cdn%2Cexpires%2Clen%2Coi%2Cpt%2Cqn%2Ctrid&sk=677197cc63610fc&sl=1&src=5&streamid=live-qn%3Alive-qn%2Flive_346651764_2556686_1500&trid=100375c02ceb030e4ee082e77a292b8bba6e&v3=1'

注意:内部m3u8地址,会在指定时间内仍然未连接时自动失效,持续被访问的内部地址则持续有效。失效时,需要重新从外部地址获取(再次调用上述方法即可)。

解析一下内部m3u8地址:

playlist = m3u8.load(uri=real_url, headers=headers)
print("segments:", playlist.segments)
print("target_duration:", playlist.target_duration)
print("keys:", playlist.keys)
print("playlists", playlist.playlists)
segments: #EXTINF:1.034,
/1623780718.ts?domain=d1--cn-gotcha103.bilivideo.com&keyoff=0&player=BEEAAG7TT8s8w4wW&streamid=live-qn%3Alive-qn%2Flive_346651764_2556686_1500&v3=1
#EXTINF:2.485,
/1623780719.ts?domain=d1--cn-gotcha103.bilivideo.com&player=BEEAAG7TT8s8w4wW&streamid=live-qn%3Alive-qn%2Flive_346651764_2556686_1500&v3=1
target_duration: 3.0
keys: [None]
playlists

可以看到,通过segments属性,可以获取ts视频流地址。keys属性为None,说明视频流未加密,这样也保证了我们的下载会非常简单。

可以通过以下代码获取ts文件的时间戳和下载地址:

for seg in playlist.segments:print(int(seg.uri[1:seg.uri.find(".")]))print(seg.absolute_uri)

m3u8介绍

M3U8是Unicode 版本的 M3U,用 UTF-8 编码。"M3U"和"M3U8"文件都是苹果公司使用的HTTP Live Streaming 格式的基础,这种格式可以在 iPhone 和 Macbook 等设备播放。是一种播放多媒体列表的文件格式,文本内容是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式大致如下:

# 未加密
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.000000,
1af12fece7a000000.ts
#EXTINF:4.320000,
1af12fece7a000001.ts
...
#EXTINF:3.800000,
1af12fece7a001155.ts
#EXT-X-ENDLIST# 加密
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/key.key"
#EXTINF:3,
https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/EUtRrqJU.ts
#EXTINF:4.72,
https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/HF90vrrN.ts
...
#EXTINF:0.24,
https://ts1.yuyuangewh.com:9999/20200808/1XdSSbTb/2000kb/hls/b7ZLcRqT.ts
#EXT-X-ENDLIST

m3u8文件中常见的标签:

标签 格式 作用
EXTM3U #EXTM3U 表明该文件是一个m3u8文件,每个m3u8文件必须将该标签放置在第一行
EXT-X-VERSION EXT-X-VERSION:<number> 表明该文件是一个m3u8文件,每个m3u8文件必须将该标签放置在第一行
EXT-X-TARGETDURATION #EXT-X-TARGETDURATION:<s> 表示每个视频分段最大的时长(单位秒)
EXT-X-PLAYLIST-TYPE #EXT-X-PLAYLIST-TYPE:<type-enum> 表明流媒体类型,VOD 表示该视屏流为点播源,因此服务器不能更改该m3u8文件;EVENT表示该视频流为直播源,因此服务器不能更改或删除该文件任意部分内容,但是可以在文件末尾添加新内容
EXT-X-MEDIA-SEQUENCE #EXT-X-MEDIA-SEQUENCE:<number> 表示播放列表第一个URL片段文件的序列号,每个媒体片段URL都拥有一个唯一的整型序列号,每个媒体片段序列号按出现顺序依次加 1,如果该标签未指定,则默认序列号从0开始
EXT-X-KEY #EXT-X-KEY:METHOD=AES-128,URI="http:xxxx",IV="xxxx" 表明视频流文件的加解密方法,METHOD表示加密方式,URI表示密钥路径,该密钥是一个 16 字节的数据,IV是一个128位的十六进制数值
EXTINF #EXTINF:<duration>,[<title>] 表示其后 URL 指定的媒体片段时长(单位为秒),duration可以为十进制的整型或者浮点型,其值必须小于或等于EXT-X-TARGETDURATION指定的值
EXT-X-ENDLIST #EXT-X-ENDLIST 表明m3u8文件的结束

更多可参考:https://www.jianshu.com/p/e97f6555a070

一个专门用于解析m3u8文件库,参阅:https://pypi.org/project/m3u8/

pip install m3u8

下载直播

首先测试访问10次链接并下载:

import timedef download_video(max_count=1000, max_size=120*1024*1024):max_id = Nonesize = 0for i in range(1, max_count+1):playlist = m3u8.load(uri=real_url, headers=headers)for seg in playlist.segments:current_id = int(seg.uri[1:seg.uri.find(".")])if max_id and current_id <= max_id:continuewith open("combine.mp4", "ab" if max_id else "wb") as f:r = requests.get(seg.absolute_uri, headers=headers)data = r.contentsize += len(data)f.write(data)print(f"\r下载次数({i}/{max_count}),已下载:{size/1024/1024:.2f}MB", end="")if size >= max_size:print("\n文件已经超过大小限制,下载结束!")returnmax_id = current_idtime.sleep(2)download_video(10)
下载次数(10/10),已下载:3.88MB                                                                          | 0/1 [00:21<?, ?it/s]s]

经测试下载成功。两个参数分别限制了最大的访问次数,和最大的文件大小。任何一个参数满足条件都会结束下载,当前直播间停播时,程序也会抛出异常自动停止。

完整代码:

import time
import m3u8
import requestsheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}def get_live_url(cid, platform='h5'):playUrl = 'https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl'params = {'cid': cid,  # cid序列号'qn': 150,  # 播放的视频质量'platform': platform,  # 视频的播放形式'ptype': 16}response = requests.get(playUrl, headers=headers, params=params).json()text = response['data']['durl']url = text[-1]['url']return urldef get_real_url(url):playlist = m3u8.load(uri=url, headers=headers)return playlist.playlists[0].absolute_uridef download_video(url, max_count=1000, max_size=120*1024*1024):max_id = Nonesize = 0for i in range(1, max_count+1):playlist = m3u8.load(uri=url, headers=headers)for seg in playlist.segments:current_id = int(seg.uri[1:seg.uri.find(".")])if max_id and current_id <= max_id:continuewith open("combine.mp4", "ab" if max_id else "wb") as f:r = requests.get(seg.absolute_uri, headers=headers)data = r.contentsize += len(data)f.write(data)print(f"\r下载次数({i}/{max_count}),已下载:{size/1024/1024:.2f}MB", end="")if size >= max_size:print("\n文件已经超过大小限制,下载结束!")returnmax_id = current_idtime.sleep(2)url = get_live_url('22273117')
real_url = get_real_url(url)
download_video(real_url)

下载了一个公考的直播,发现就是文件实在是太大了,14分钟的视频居然就占到了100MB。

Python实时下载B站直播间视频(M3U8视频流)相关推荐

  1. 使用树莓派基于FFmpeg推流视频和摄像头到B站直播间

    文章目录 从B站直播间获取rtmp地址和直播码 在终端使用ffpmeg进行视频或摄像头推流 用python实现控制树莓派推流 如何停止树莓派推流 前提条件 1.首先要有一个树莓派,并连接了摄像头,且能 ...

  2. 【爬虫】批量下载B站收藏夹视频 - Python

    批量下载B站收藏夹视频 起因 开发过程 环境需求 B站API介绍 收藏夹基本信息 收藏夹中各个视频的AV号 收藏夹中各个视频的BV号 最终代码 起因 不知道大家在浏览自己的B站收藏夹的时候,有没有遇到 ...

  3. Python 仅下载B站视频的音频

    Python 仅下载B站视频的音频 因为各音源网站都要会员而且资源都不全,但是B站上所有的歌或者郭德纲的相声是都可以分享的,所以想在B站上下载想要的歌或者其他的音频. 首先打开一个连接https:// ...

  4. 【python+pyqt5】B站直播弹幕姬

    文章目录 前言 1.日志对象 2.获取弹幕 3.qt窗口 窗口间传递信号 主窗口 设置窗口 弹幕展示窗口 托盘 4.主函数 5.最终成果及使用方法 6.开源地址 前言 这个软件是基于我半年多前写的一个 ...

  5. 用ffmpeg在Windows11下的命令行模式推流到B站直播间

    0. 通过修改环境变量,实现ffmpeg命令在任意命令行路径可用的方法.不用再使用下面的第1步了!! 注意:一路"确定 " 保存设置后,最好重启一下,这样就可以将ffmpeg变成系 ...

  6. 不用obs不用直播姬,直接ffmpeg命令行推流RTSP到B站直播间

    最近在做公司的直播准备工作,在尝试过程中,发现公司的"海康威视 DS-2CD1021FD-IW1"摄像头输出的是RTSP格式的. 经过各种搜索,尝试了用B站官方直播姬抓VLC窗口, ...

  7. PHP直播爬虫,B 站直播间数据爬虫

    前言 起因 去年在 B 站发现一个后期超强的 UP 主:修仙不倒大小眼,专出 PDD 这样知名主播的吃鸡精彩集锦,涨粉超快.于是想怎么做这样的 UP,遇到的第一个问题便是素材,精彩时刻需要手动从直播录 ...

  8. AioWebSocket实现python异步接收B站直播弹幕

    文章目录 前言 AioWebSocket是什么 AioWebSocket相比于http/https的优势 AioWebSocket实现接收弹幕功能 前言 第一次写文章,若有不对的地方请多多包涵并指正. ...

  9. 关于哔站直播间账号开通的公告

    鉴于很多朋友想转行或者提升在IT行业的地位,想要在一定的时间内学到更多的干货: 同时又有很多人在网上经常搜索段海涛老师.赵星老师的视频学习,而遇到困难时不能及时解决容易泄气: 想要跟老师面对面上课,却 ...

最新文章

  1. 你猜,为什么Google和Facebook不用Docker?
  2. 基于python opencv实现广角相机标定和图像畸变矫正
  3. 关于分页插件PageHelper不起作用的问题
  4. 使用python装饰器计算函数运行时间的实例
  5. sqlserver 的一些好用的插件
  6. 局域主机做服务器,安装DNN,外网访问的解决办法
  7. 数据结构之查找算法:折半查找
  8. Airflow 中文文档:UI /截图
  9. oracle数据库文件dbf复制#ocp试验#
  10. linux运行程序开启了opencl,错误:运行OpenCL代码时,clGetPlatformIDs -1001(Linux)
  11. 为SIT Portal切换SIT所连接的数据库
  12. ICT通信运营企业的重建之服务升级(三)----如何打造ICT服务满意度
  13. 硅谷孵化器的中国江湖
  14. Milking Cows /// 区间计数 离散化排序 oj10105
  15. javafx小球运动
  16. EMC-浪涌防护及退耦设计
  17. FPGA IP核 串口实验 signaltap
  18. LeetCode | 0017. Letter Combinations of a Phone Number电话号码的字母组合【Python】
  19. hjr-MUD游戏(七):挂机功能的实现
  20. 'GridSearchCV' object has no attribute 'best_params_'解决办法

热门文章

  1. 淘宝手机所在地查询接口
  2. BBR学习笔记--什么是BBR、可调整的参数
  3. ofd格式文件怎么转换pdf格式,过来瞧一瞧
  4. c++/MFC CSocket仿QQ聊天软件,实现1对1聊天,群聊
  5. 概述MOS管的结构知识、原理详解-KIA MOS管
  6. python 收发微信之二:获取微信上行信息(利用 flask 框架实现 Web API,获取 WxPusher 上行微信)
  7. MATLAB | 三个趣的圆相关的数理性质可视化
  8. 命令行用impdp导入oracle时,报错ORA-02374: conversion error loading table “XXXX“.“XXXX“ ORA-12899: value……
  9. 现成的MP4地址分享
  10. MAC下安装GDAL库