抓取Bilibili哔哩哔哩网站视频(Java和Python双版本实现)

本文详细分析了如何获取B站视频的真实地址,并提供了Python和Java两种语言的实现,通过解析网页源代码获取视频和音频信息,然后下载并合并音视频文件。主要涉及HTML解析、HTTP请求范围头以及ffmpeg工具的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、B站视频真实地址分析

我一直觉得编程语言只是一种工具,重要的是思想🐶。下面先来分析下B站视频的真实地址。

1.1 获取视频的信息数据

使用PC通过浏览器随便打开一个B站的视频,右键检查或者是按F12,查看网页源代码。我们会发现有一个script标签内的内容是这样的。

嗯,仿佛这就是视频的信息了。下面我们将其复制出来,格式化一下。

window.__playinfo__ = {
    "data": {
        "accept_format": "flv720,flv480,mp4",
        "accept_description": ["高清 720P", "清晰 480P", "流畅 360P"],
        "accept_quality": [64, 32, 16],
        "dash": {
            "video": [{
                "id": 64,
                "baseUrl": "http://cn-sdbz-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=80000000",
                "base_url": "http://cn-sdbz-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=80000000",
                "backupUrl": ["http://cn-sdyt-cu-v-11.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000", "http://cn-hbcd2-cu-v-07.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000"],
                "backup_url": ["http://cn-sdyt-cu-v-11.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000", "http://cn-hbcd2-cu-v-07.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30064.m4s?expires=1589032200&platform=pc&ssig=7TagzkxicmXQCX-eJG1rWw&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000"],
                "bandwidth": 1883922,
                "mimeType": "video/mp4",
                "mime_type": "video/mp4",
                "width": 720,
                "height": 1280,
                "SegmentBase": {"Initialization": "0-974", "indexRange": "975-1162"},
                "segment_base": {"initialization": "0-974", "index_range": "975-1162"},
                "codecid": 7
            },], 
            "audio": [{
                "id": 30280,
                "baseUrl": "http://cn-sdyt-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=80000000",
                "base_url": "http://cn-sdyt-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=80000000",
                "backupUrl": ["http://cn-hbcd2-cu-v-14.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000", "http://cn-sdjn2-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000"],
                "backup_url": ["http://cn-hbcd2-cu-v-14.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000", "http://cn-sdjn2-cu-v-05.bilivideo.com/upgcxcode/18/80/187918018/187918018-1-30280.m4s?expires=1589032200&platform=pc&ssig=ud9zkd5aAUp7mB4yPjI_LA&oi=1894210281&trid=4b3d732f515544e49c843d5f2c87f64bu&nfc=1&nfb=maPYqpoel5MI3qOUX6YpRA==&mid=388810686&logo=40000000"],
                "bandwidth": 319474,
                "mimeType": "audio/mp4",
                "mime_type": "audio/mp4",
                "SegmentBase": {"Initialization": "0-919", "indexRange": "920-1107"},
                "segment_base": {"initialization": "0-919", "index_range": "920-1107"},
            },]
        }
    }, "session": "996ecc0413599104d175e5c254e70fb7", "videoFrame": {}
}

我删除了一些没有的信息,通过上面的信息我们可以得到以下几点信息:

  1. B站的视频是音视频分离的。
  2. 我们可以从js中获取真实音视频地址。
  3. 提供了"高清 720P", "清晰 480P", "流畅 360P"方式供我们选择。不要问我为什么没有1080P的,俺也不知道😢。

下面我们再来看一下B站自己发送请求的信息。

我们发现每次请求时都携带了,此次请求文件的字节位置信息。

1.2 抓取B站视频的思路

  1. 请求想要下载视频的地址,获取页面html。
  2. 从页面中解析出视频的基本信息,音视频的url地址等信息。
  3. 下载音视频文件,发送请求时带上请求的范围(range)。(注:本文的实现中,没有使用多线程,直接请求的整个文件)
  4. 将下载完成的音视频文件合并成完成的视频文件。

1.3 用到的第三方库和软件

  1. 第三方软件

  • ffmpeg:用于合并音视频文件。官方网址:http://ffmpeg.org/。
    • ffmpeg:是处理音视频的利器,感兴趣可以搜索相关资料了解下。本文只要你下载下来,将ffmpeg位置写到代码变量里就可以了。
  1. Python

  • requests:用于发送Http请求
  • ffmpeg-python:方便操作ffmpeg
  • HTMLParser:python自带的HTML解析工具
  1. Java

  • hutool:一个国人开源的Java工具包。强烈推荐。

  • jsoup:Java解析HTML的利器

2、代码实现

注:本文中代码下载音视频均采用的单线程的方式,如果使用多线程,一定要计算好每个请求的请求范围,以及下载完成后,合并文件时的顺序。

2.1 Python的实现

解析html

class BiliHTMLParser(HTMLParser):
    """
    继承自HTMLParser。用于解析html
    """
    def __init__(self):
        super().__init__()
        self.isTitle = 0
        self.videoName = ""  # 视频名称
        self.videoInfo = {}  # 视频信息

    def handle_starttag(self, tag, attrs):
        if tag != 'title':
            return
        self.isTitle += 1

    def handle_endtag(self, tag):
        if tag == 'title' and self.isTitle:
            self.isTitle -= 1

    def handle_data(self, data):
        """
        获取当前页面的视频信息
        :param data: tag中的数据
        :return:
        """
        if data and self.isTitle:  # 用于获取视频名称
            self.videoName = data

        if data.startswith('window.__playinfo__='):
            infoStr = data.split('window.__playinfo__=')[-1]  # 截取`window.__playinfo__=`之后的字符串
            self.videoInfo = json.loads(infoStr)  # 字符串转字典dict

获取视频信息

def getVideo(videoInfo, videoName):
    """
    :param videoInfo: 视频信息字典dict
    :param videoName: 视频名称
    :return:
    """
    # 获取视频的url和初始的大小范围
    videoBaseUrl = videoInfo['data']['dash']['video'][0]['baseUrl']
    videoBaseRange = videoInfo['data']['dash']['video'][0]['SegmentBase']['Initialization']

    # 获取音频的url和初始的大小范围
    audioBaseUrl = videoInfo['data']['dash']['audio'][0]['baseUrl']
    audioBaseRange = videoInfo['data']['dash']['audio'][0]['SegmentBase']['Initialization']
    # 文件下载
    videoSize = getVideoInfo(videoBaseUrl, videoBaseRange)
    videoFileName = downloadFile(videoBaseUrl, videoSize, "video", videoName)
    audioSize = getVideoInfo(audioBaseUrl, audioBaseRange)
    audioFileName = downloadFile(audioBaseUrl, audioSize, "audio", videoName)
    # 合并文件
    outFilePath = "./%s/%s.mp4" % (videoName, videoName)
    mergeFiles(videoFileName, audioFileName, outFilePath)


def getVideoInfo(baseUrl, range):
    """
    获取视频或音频文件的总大小
    :param baseUrl:
    :param range:
    :return:
    """
    headers = {
        'Referer': videoUrl,
        'Range': 'bytes=%s' % (range),
    }
    videoRes = requests.get(url=baseUrl, headers=headers)
    # 获取视频总大小
    headersInfo = videoRes.headers
    total = headersInfo['Content-Range'].split('/')[-1]
    print('资源的总字节数:%s' % total)
    return total

下载音视频

def downloadFile(url, totalSize, type, videoName):
    """
    下载资源
    :param url: 资源url
    :param totalSize: 资源总大小
    :param type: video/audio
    :param videoName: 视频名称
    :return:
    """
    headers = {
        'Referer': videoUrl,
        'Range': "bytes=%s-%s" % (str(0), str(totalSize))
    }
    fileDir = "./%s" % videoName
    if not os.path.exists(fileDir):
        os.mkdir(fileDir)

    fileName = "./%s/%s-%s.mp4" % (videoName, videoName, type)
    if not os.path.exists(fileName):
        res = requests.get(url=url, headers=headers, stream=True)
        print("开始下载:%s" % type)
        data = res.content
        with open(fileName, 'wb') as file_obj:
            file_obj.write(data)
    print("完成%s的下载" % type)
    return fileName

合并音视频

def mergeFiles(videoFilePath, audioFilePath, outFilePath):
    """合并音视频"""
    print("开始合并音视频")
    videoFile = ffmpeg.input(videoFilePath)
    audioFile = ffmpeg.input(audioFilePath)
    stream = ffmpeg.output(videoFile, audioFile, outFilePath, vcodec='copy', acodec='copy')
    ffmpeg.run(stream, cmd=ffmpegPath)
    print("合并音视频完成")

2.2 Java实现

解析Html

    /** 解析HTML获取相关信息 */
    private static void htmlParser(){
        HttpResponse res = HttpRequest.get(VIDEO_URL).timeout(2000).execute();
        String html = res.body();
        Document document = Jsoup.parse(html);
        Element title = document.getElementsByTag("title").first();
        // 视频名称
        VIDEO_INFO.videoName = title.text();
        // 截取视频信息
        Pattern pattern = Pattern.compile("(?<=<script>window.__playinfo__=).*?(?=</script>)");
        Matcher matcher = pattern.matcher(html);
        if (matcher.find()) {
            VIDEO_INFO.videoInfo = new JSONObject(matcher.group());
        } else {
            System.err.println("未匹配到视频信息,退出程序!");
            return;
        }
        getVideoInfo();
    }

获取视频信息

    /** 解析视频和音频的具体信息 */
    private static void getVideoInfo(){
        // 获取视频的基本信息
        JSONObject videoInfo = VIDEO_INFO.videoInfo;
        JSONArray videoInfoArr = videoInfo.getJSONObject("data").getJSONObject("dash").getJSONArray("video");
        VIDEO_INFO.videoBaseUrl = videoInfoArr.getJSONObject(0).getStr("baseUrl");
        VIDEO_INFO.videoBaseRange = videoInfoArr.getJSONObject(0).getJSONObject("SegmentBase").getStr("Initialization");
        HttpResponse videoRes = HttpRequest.get(VIDEO_INFO.videoBaseUrl)
                .header("Referer", VIDEO_URL)
                .header("Range", "bytes=" + VIDEO_INFO.videoBaseRange)
                .header("User-Agent", USER_AGENT)
                .timeout(2000)
                .execute();
        VIDEO_INFO.videoSize = videoRes.header("Content-Range").split("/")[1];

        // 获取音频基本信息
        JSONArray audioInfoArr = videoInfo.getJSONObject("data").getJSONObject("dash").getJSONArray("audio");
        VIDEO_INFO.audioBaseUrl = audioInfoArr.getJSONObject(0).getStr("baseUrl");
        VIDEO_INFO.audioBaseRange = audioInfoArr.getJSONObject(0).getJSONObject("SegmentBase").getStr("Initialization");
        HttpResponse audioRes = HttpRequest.get(VIDEO_INFO.audioBaseUrl)
                .header("Referer", VIDEO_URL)
                .header("Range", "bytes=" + VIDEO_INFO.audioBaseRange)
                .header("User-Agent", USER_AGENT)
                .timeout(2000)
                .execute();
        VIDEO_INFO.audioSize = audioRes.header("Content-Range").split("/")[1];

        downloadFile();
    }

下载音视频

    /** 下载音视频 */
    private static void downloadFile(){
        // 保存音视频的位置
        SAVE_PATH = "." + File.separator + VIDEO_INFO.videoName;
        File fileDir = new File(SAVE_PATH);
        if (!fileDir.exists()){
            fileDir.mkdirs();
        }

        // 下载视频
        File videoFile = new File(SAVE_PATH + File.separator + VIDEO_INFO.videoName + "_video.mp4");
        if (!videoFile.exists()){
            System.out.println("--------------开始下载视频文件--------------");
            HttpResponse videoRes = HttpRequest.get(VIDEO_INFO.videoBaseUrl)
                    .header("Referer", VIDEO_URL)
                    .header("Range", "bytes=0-" + VIDEO_INFO.videoSize)
                    .header("User-Agent", USER_AGENT)
                    .execute();
            videoRes.writeBody(videoFile);
            System.out.println("--------------视频文件下载完成--------------");
        }


        // 下载音频
        File audioFile = new File(SAVE_PATH + File.separator + VIDEO_INFO.videoName + "_audio.mp4");
        if (!audioFile.exists()){
            System.out.println("--------------开始下载音频文件--------------");
            HttpResponse audioRes = HttpRequest.get(VIDEO_INFO.audioBaseUrl)
                    .header("Referer", VIDEO_URL)
                    .header("Range", "bytes=0-" + VIDEO_INFO.audioSize)
                    .header("User-Agent", USER_AGENT)
                    .execute();
            audioRes.writeBody(audioFile);
            System.out.println("--------------音频文件下载完成--------------");
        }

        mergeFiles(videoFile,audioFile);

    }

合并视频

    private static void mergeFiles(File videoFile,File audioFile){
        System.out.println("--------------开始合并音视频--------------");
        String outFile = SAVE_PATH + File.separator + VIDEO_INFO.videoName + ".mp4";
        List<String> commend = new ArrayList<>();
        commend.add(FFMPEG_PATH);
        commend.add("-i");
        commend.add(videoFile.getAbsolutePath());
        commend.add("-i");
        commend.add(audioFile.getAbsolutePath());
        commend.add("-vcodec");
        commend.add("copy");
        commend.add("-acodec");
        commend.add("copy");
        commend.add(outFile);

        ProcessBuilder builder = new ProcessBuilder();
        builder.command(commend);
        try {
            builder.inheritIO().start().waitFor();
            System.out.println("--------------音视频合并完成--------------");
        } catch (InterruptedException | IOException e) {
            System.err.println("音视频合并失败!");
            e.printStackTrace();
        }

    }

3、源码地址

GitHub源码地址

<think>嗯,用户想用Python爬取哩的视频,我得先理清楚步骤。首先,得知道B视频页面结构,找到视频的源链接。可能需要分析网页请求,看看视频数据是怎么加载的。 然后,用户可能需要用requests库发送HTTP请求,不过B可能有反爬机制,比如需要加上headers,特别是User-AgentReferer,否则可能会被拒绝访问。还有可能需要处理cookie,特别是如果视频是会员专享的话,但一般教程可能不会涉及登录,先假设是公开视频。 接下来,解析网页内容,找视频的URL。B视频有时候是分段的,比如多个flv或者mp4片段,或者用m4s格式,需要合并。这时候可能需要用正则表达式或者BeautifulSoup来提取信息,或者直接分析网页中的JavaScript数据,里面可能有包含视频链接的JSON对象。 另外,B现在可能用了动态加载,视频信息可能通过API接口返回,这时候需要找到那个接口,比如通过浏览器的开发者工具查看XHR请求,找到包含视频信息的接口,然后构造请求参数。比如,视频的av号或bv号,还有cid可能都需要作为参数传递。 然后下载视频文件,可能需要用requests的stream模式来分块下载大文件,避免内存占用过高。下载完成后,可能需要用FFmpeg来合并分段视频或者处理音视频分离的情况,比如有些视频的音频视频是分开的,需要分别下载然后合并。 还要注意法律问题,提醒用户遵守B的使用条款,不要侵犯版权,只能爬取公开可用或自己拥有权限的视频。另外,爬虫的频率不能太高,否则可能会被封IP,建议设置延迟,或者使用代理池。 可能遇到的问题包括:反爬虫机制比如验证码,接口参数加密,比如sign字段,需要逆向JavaScript生成,这时候可能需要用selenium或者模拟浏览器行为,但这样效率较低。或者使用第三方库,比如bilibili_api,这个库可能已经封装好了这些操作,可以简化流程。 总结步骤的话,应该是:1. 找到视频的API接口,构造请求;2. 发送请求获取视频信息;3. 解析出视频音频的URL;4. 下载视频音频文件;5. 合并音视频(如果需要);6. 保存文件。其中关键点在于如何获取真实视频地址,可能需要处理加密参数或者动态生成的cid等。 另外,用户可能需要代码示例,比如使用requestsjson解析,还有可能用到多线程下载加速,但考虑到复杂度,先给一个简单的示例。同时提醒用户注意headers的完善,避免被拒绝。 最后,生成相关问题的时候,可以考虑反爬措施、视频合并方法、BAPI的变化以及法律风险等方面的问题。</think>### Python爬取视频的步骤说明 #### 1. 分析视频请求接口 视频数据通过API接口动态加载,需通过浏览器开发者工具(F12)分析网络请求: - 在视频播放页找到`playurl`相关的请求(通常为`api.bilibili.com/x/player/playurl`) - 提取关键参数:`bvid`(视频ID)、`cid`(分P编号)、`qn`(画质代码)[^1] #### 2. 构造请求头 需添加完整请求头避免反爬: ```python headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://www.bilibili.com/" } ``` #### 3. 获取视频信息 ```python import requests def get_video_info(bvid): url = f"https://api.bilibili.com/x/web-interface/view?bvid={bvid}" response = requests.get(url, headers=headers) return response.json()['data'] ``` #### 4. 获取视频直链 ```python def get_video_url(bvid, cid, qn=80): params = { "bvid": bvid, "cid": cid, "qn": qn, "fnval": 16 # 请求dash格式 } response = requests.get("https://api.bilibili.com/x/player/playurl", params=params, headers=headers) return response.json()['data']['dash'] ``` #### 5. 下载视频片段 ```python def download_file(url, filename): with requests.get(url, headers=headers, stream=True) as r: with open(filename, 'wb') as f: for chunk in r.iter_content(chunk_size=1024*1024): if chunk: f.write(chunk) ``` #### 6. 合并音视频(示例) ```python # 需要预先安装FFmpeg import subprocess def merge_media(video_path, audio_path, output_path): cmd = f"ffmpeg -i {video_path} -i {audio_path} -c:v copy -c:a aac {output_path}" subprocess.run(cmd, shell=True) ``` #### 注意事项 1. 遵守`robots.txt`协议,控制请求频率 2. 仅用于学习用途,不得用于商业用途 3. 部分高画质视频需要登录cookie 4. 接口参数可能随时间变化,需定期维护 ```python # 完整调用示例 bvid = "BV1xx411c7mu" video_info = get_video_info(bvid) cid = video_info['pages'][0]['cid'] media_data = get_video_url(bvid, cid) # 下载视频音频流 download_file(media_data['video'][0]['base_url'], "video.mp4") download_file(media_data['audio'][0]['base_url'], "audio.mp3") merge_media("video.mp4", "audio.mp3", "output.mp4") ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值