news 2026/9/20 12:13:34

用PHP解析B站视频下载地址:从BV号到高清播放地址的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用PHP解析B站视频下载地址:从BV号到高清播放地址的完整实现

前几天有个朋友问我,能不能把一个B站学习视频下载到本地,他想在地铁上反复看。我第一反应是丢给他一个在线解析网站,但转念一想,那些网站要么弹广告、要么第二天就失效,与其来回折腾,不如用PHP写个解析脚本,几分钟就能拿到高清播放地址。今天就把这个思路和代码完整放出来,希望能帮到和那位朋友一样困在“B站视频怎么下载”这个问题上的人。如果你懂一点PHP、会用命令行,甚至只是想了解视频网站接口是怎么工作的,下面这5分钟的内容应该能让你少走很多弯路。

这篇文章的主角是一个免费的PHP小工具。它做的事情很简单:输入一个B站BV号,输出一串或者多串可以直接播放的视频地址。核心代码不到60行,不依赖任何框架,也不需要数据库,只要你的机器能跑PHP就行。与其说它是一个“破解工具”,不如说它是一份“接口调用练习”,因为B站网页端本身就是这样拿视频地址的,我们只是把播放器背后发起的请求用PHP模拟了一遍。

1. 为什么我不推荐“在线解析网站”,而是自己写脚本

1.1 在线解析网站的三大痛点

先说说我为什么不建议你用那些所谓“免费B站解析下载网站”。

第一个痛点是广告和诱导。这类站点为了赚流量费,页面上通常堆满了假播放按钮、下载器推广和弹窗。你只是想下载一个视频,结果可能装了一堆流氓插件,或者下载下来是个exe而不是mp4。关于这一点,我在帮朋友排查电脑问题时见过太多次。

第二个痛点是清晰度被锁。免费的在线解析往往只能拿到480P或者720P,想要1080P就引导你开会员或者关注公众号、付费买卡密。问题是,B站很多视频本身就是公开的、你登录账号就能看1080P,这些网站只是把你的清晰度降级再卖给你,属于两头赚。

第三个痛点是失效快。B站接口一调整、风控一收紧,这些站点很短时间内就会集体扑街。你今天收藏的解析网站,下周可能就变成“本站已关停,请加群获取新地址”。这种依赖关系让你永远在追逐一个不稳定的服务。

1.2 自己写脚本的本质:拿到播放地址

别看“解析”这个词听起来很玄,它背后的逻辑其实很简单:视频网页能正常播放,就说明播放地址一定存在,而且能被浏览器获取到。所谓解析,就是去找到那个地址,把它从接口返回的数据里取出来,给它加上必要的请求头,让你能在任意播放器里直接访问。整个过程跟“破解”完全不沾边,更像是在模仿浏览器去和B站服务器“正常对话”。

我见过不少人对“接口”有心理壁垒,觉得那是后端工程师研究的东西。其实你用一个PHP文件加几行cURL代码,就能把“接口”这件事体验一遍。这也是我推荐大家自己写而不是用别人工具的原因:一旦你理解了接口调用、JSON解析、HTTP头设置这几个概念,以后不管是下载其他网站的视频,还是对接各种开放平台API,都会变得很自然。

1.3 这篇文章适合谁

老实说,如果你完全不会PHP,只是想要一个现成工具,那这个脚本也能直接用,运行一下、复制地址出来就行。但如果你希望从这个例子里学到东西,那就需要具备一点点基础:

  • 会打开命令行,知道php xxx.php这种运行方式
  • 能看懂基本的PHP语法就行,不需要会面向对象
  • 对“JSON”这个东西有印象,知道它是接口返回的数据格式

满足这几点,你跟着下面内容走一遍,应该就能把这套逻辑吃透。

2. B站视频地址的获取逻辑:BV号、cid、playurl是怎么串起来的

2.1 从BV号到aid:view接口

B站早期的视频ID是纯数字的,也就是av号,比如av170001。后来为了防爬和去重,B站换成了现在这种BV开头的字符串,比如BV1GJ411x7h7。但是B站内部很多老接口依然只看数字ID,也就是aid

所以我们做的第一件事,就是把用户输入的BV号,转换成接口能识别的aidcid。转换所用的接口是:

https://api.bilibili.com/x/web-interface/view?bvid=BVxxxxxxxx

这个接口返回的是视频的详细信息,包括标题、封面、简介、分区,以及一个非常重要的字段cidcid是分P的唯一标识,B站一个视频可以有多个分P(也就是你看到的视频列表),每一P对应一个不同的cid。所以严格来说,获取播放地址时,BV号并不是最终参数,cid才是。

举个例子,你请求view接口后,返回的JSON结构大概长这样(简化过):

{ "code": 0, "data": { "aid": 123456789, "bvid": "BV1GJ411x7h7", "cid": 234567890, "title": "示例视频标题", "pages": [ { "page": 1, "cid": 234567890, "part": "P1标题" }, { "page": 2, "cid": 234567891, "part": "P2标题" } ] } }

大部分视频只有一P,所以data.cid就是在这个页面下默认播放的那一P的ID。如果你需要处理多P,data.pages数组里有全部信息。这部分后面进阶会讲到。

2.2 从avid + cid到播放地址:playurl接口

拿到aidcid之后,第二步就是请求真正的播放地址接口:

https://api.bilibili.com/x/player/playurl?avid=123456789&cid=234567890&qn=80&fnval=0

这个接口会返回一串播放信息。其中qn是清晰度参数,fnval是格式参数。如果fnval=0,返回的是比较传统的DURL格式,视频通常是一个或多个分段地址,可以直接下载;如果fnval=16,返回的是DASH格式,视频轨和音频轨分离,后面需要额外合并。

在DURL格式下,返回的数据核心是data.durl数组:

{ "code": 0, "data": { "timelength": 1830000, "durl": [ { "url": "https://upos-sz-mirror.../xxx.flv?xxx", "order": 1 } ] } }

我们需要的,就是durl[0].url这个字段。

2.3 防盗链和Referer:B站为什么拦住外站请求

拿到播放地址之后,如果你直接复制到浏览器地址栏打开,大概率会看到403错误,或者下载下来的是一堆乱码。这不是地址错了,而是B站的CDN会校验HTTP请求头里的Referer字段。

它的逻辑是:只有来自https://www.bilibili.com/页面的播放请求才会被允许,其他来源一概拦截。这就是我们常说的“防盗链”。浏览器在播放B站视频时,会自动带上Referer,所以一切正常;而你直接复制地址到新标签页打开时,Referer就变了,于是被拒绝。

很多新手写爬虫、写解析脚本失败,就失败在这一步:明明接口返回了地址,却下载不下来。解决方式也很简单,在cURL请求里面手动设置一下头信息即可。这也是我下面代码里重点强调的部分。

3. 5分钟跑通:完整的PHP解析脚本与逐段拆解

3.1 环境准备

第一步确认你的环境满足要求:

  1. PHP 7.4 或更高版本,命令行输入php -v能看到版本号
  2. PHP已启用cURL扩展,命令行输入php -m | grep curl能看到curl

如果你用的是Windows,最简单的方式是装一个PHPStudy或者Laragon,里面自带了PHP和curl扩展。如果你用的是Mac或者Linux,系统自带的PHP通常也够用。

整个过程不需要安装Composer、不需要引入任何第三方库,因为底层HTTP请求我们用cURL完成,JSON解析用PHP自带的json_decode,完全够用。

3.2 第一段代码:请求view接口,拿到cid

我习惯先封装一个通用的request()函数,把公共的请求头统一设置好。这样可以避免在多个地方重复写User-Agent和Referer。

<?php function request(string $url): array { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer: https://www.bilibili.com/' ]); $result = curl_exec($ch); if (curl_errno($ch)) { throw new RuntimeException('cURL错误: ' . curl_error($ch)); } curl_close($ch); return json_decode($result, true); }

CURLOPT_SSL_VERIFYPEER设为false,是为了避免本地缺少CA证书导致的SSL验证失败。这个设置在生产环境不建议长期开启,但本地跑脚本问题不大。

然后写一个getPlayUrl()方法,第一步先请求view接口取cidaid

function getPlayUrl(string $bvid): array { $viewUrl = "https://api.bilibili.com/x/web-interface/view?bvid={$bvid}"; $viewData = request($viewUrl); if (!isset($viewData['code']) || $viewData['code'] !== 0) { throw new RuntimeException('获取视频信息失败: ' . ($viewData['message'] ?? '未知错误')); } $aid = $viewData['data']['aid']; $cid = $viewData['data']['cid']; echo "标题: " . $viewData['data']['title'] . PHP_EOL; // 下一步:请求播放地址 }

这里先校验返回状态code,只要不为0,就说明BV号有问题或者视频不存在,直接抛异常结束。接下来才进入真正的playurl请求。

3.3 第二段代码:请求playurl,拿到高清播放地址

接着上面的函数继续写:

$playApi = "https://api.bilibili.com/x/player/playurl?avid={$aid}&cid={$cid}&qn=80&fnval=0"; $playData = request($playApi); if (!isset($playData['code']) || $playData['code'] !== 0) { throw new RuntimeException('获取播放地址失败: ' . ($playData['message'] ?? '未知错误')); } return $playData['data']; }

这里的qn=80表示请求1080P,如果当前账号或视频没有这个清晰度,接口会自动降级,不会直接报错。至于fnval=0,表示使用DURL格式,得到的是可以直接播放的FLV或MP4地址,对新手最友好。

3.4 完整源码

把上面几段拼起来,再加一个命令行入口,就是完整的工具,代码不到60行:

<?php function request(string $url): array { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer: https://www.bilibili.com/' ]); $result = curl_exec($ch); if (curl_errno($ch)) { throw new RuntimeException('cURL错误: ' . curl_error($ch)); } curl_close($ch); return json_decode($result, true); } function getPlayUrl(string $bvid): array { $viewData = request("https://api.bilibili.com/x/web-interface/view?bvid={$bvid}"); if (!isset($viewData['code']) || $viewData['code'] !== 0) { throw new RuntimeException('获取视频信息失败: ' . ($viewData['message'] ?? '未知错误')); } $aid = $viewData['data']['aid']; $cid = $viewData['data']['cid']; echo "标题: " . $viewData['data']['title'] . PHP_EOL; $playData = request("https://api.bilibili.com/x/player/playurl?avid={$aid}&cid={$cid}&qn=80&fnval=0"); if (!isset($playData['code']) || $playData['code'] !== 0) { throw new RuntimeException('获取播放地址失败: ' . ($playData['message'] ?? '未知错误')); } return $playData['data']; } $bvid = $argv[1] ?? ''; if ($bvid === '') { die("用法: php bili_parser.php BVxxxxxxxx\n"); } try { $data = getPlayUrl($bvid); if (isset($data['durl']) && $data['durl']) { foreach ($data['durl'] as $index => $part) { echo "分片" . ($index + 1) . "地址: " . $part['url'] . PHP_EOL; } } else { echo "未解析到播放地址。" . PHP_EOL; } } catch (RuntimeException $e) { echo "解析失败: " . $e->getMessage() . PHP_EOL; }

把这段代码保存为bili_parser.php

3.5 实际运行效果

在命令行输入:

php bili_parser.php BV1GJ411x7h7

会先打印视频标题,然后输出一个或多个分片地址。拿B站官方账号的一个视频测试,输出类似这样:

标题: 【官方】B站 2019拜年祭 分片1地址: https://upos-sz-mirroralicn.bilivideo.com/upgcxcode/.../xxx.flv?e=ig...

把这段地址复制到支持FLV的播放器里,比如PotPlayer、VLC,或者直接用ffplay打开,就能正常播放。如果地址被断成好几行,说明是从终端复制的换行符问题,可以重定向到一个文本文件:

php bili_parser.php BV1GJ411x7h7 > output.txt

如果你只是想在浏览器里直接看,也可以把这个PHP脚本放到本地Web服务器目录下,用?bvid=传参。不过命令行方式最简单,也不会被PHP内置的浏览器输出格式干扰,推荐优先用命令行。

4. 我在实测中踩过的坑:防盗链、多分片、清晰度被锁

4.1 坑1:Referer没写对,返回403

我第一次测试的时候,Referer写的是:

'Referer: https://www.bilibili.com'

注意,少了一个结尾斜杠。结果部分视频能解析,部分视频的播放地址拿到之后怎么都下载不了。后来我对比浏览器的请求头才发现,B站CDN校验时对Referer的匹配比较严格,把末尾斜杠加上去:

'Referer: https://www.bilibili.com/'

问题立刻消失。别看这个区别不起眼,在实际使用中可能就是一次请求成功、一次请求失败的差别。

还有一点,如果你改动了请求头大小写,比如写成referer,部分CDN也能识别,但为了稳妥,HTTP头里的固定名称建议按标准来,大小写保持一致。

4.2 坑2:有些视频返回多个durl分片

长视频和高码率视频会被B站切成多个分片,在接口返回里就是durl数组里有多个元素。每个分片都有自己独立的URL、时长和大小。如果只取durl[0]['url'],你可能只下载了视频的前几分钟,后面的全丢了。

所以上面完整代码里我特意用了foreach循环,把所有分片地址都打印出来。如果你要下载完整视频,就得把每个分片都下载下来,再按顺序合并。对于FLV格式,合并可以直接用ffmpeg

ffmpeg -i "concat:part1.flv|part2.flv|part3.flv" -c copy output.flv

或者更清晰的做法,先把每个分片下载到本地,然后用:

ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.flv

其中filelist.txt里每行写一个file 'part1.flv'

顺便说一句,如果你看到一个视频接口返回了十几段分片,不用担心是异常,这是很正常的切片策略。B站的CDN会根据网络状况动态决定分片数量,同一个视频在不同时间请求,分片数都可能不一样。

4.3 坑3:没登录只能拿到480P

B站对未登录用户的清晰度限制非常明显。未登录状态下,即使你请求qn=80,接口也可能只给你返回一个480P的地址。这是因为B站要求视频清晰度必须结合账号权限来校验:普通游客最多看480P,登录用户通常能看720P或1080P,更高清晰度(比如1080P+、4K、原画)则要求大会员。

如果你需要下载1080P,一个办法是在请求头里带上你自己的登录Cookie。怎么拿Cookie?浏览器登录B站后,按F12打开开发者工具,在Network里随便点一个请求,复制请求头里的Cookie字段。然后在上面的request()函数里加一行:

'Cookie: SESSDATA=你的登录凭证; bili_jct=你的csrf_token'

这里只使用你自己账号的Cookie,相当于模拟你自己在网页端看视频,属于正常账号行为,不涉及破解任何东西。但要注意,Cookie属于敏感信息,千万不要把带着Cookie的脚本随便发给别人,更不要把Cookie上传到公开代码仓库。否则别人能用你的账号发弹幕、评论,甚至做一些更麻烦的操作。

我实测发现,携带了Cookie之后,接口返回的清晰度才会真正按你账号的权限走。普通登录账号一般能拿到1080P,大会员账号可以拿到1080P+甚至更高。

4.4 坑4:请求过快返回-412

B站接口有风控策略,如果你高频刷接口,返回的数据里code可能会变成-412,提示“请求被拦截”或者“操作太频繁”。我一开始调试时,用脚本循环测试了好几个BV号,每个之间间隔不到1秒,结果第4个就触发了风控,需要等好几分钟才能继续用。

解决办法很简单:控制请求频率,每次解析之间至少间隔2到3秒,不要批量、高并发地跑这个脚本。同时建议把CURLOPT_TIMEOUT设小一点,比如5到10秒,避免一个接口卡住让脚本一直挂着。

另外,尽量不要在生产环境或者公司网络出口去频繁抓B站接口,因为整个出口IP都可能被限。作为个人工具,控制频率、合理使用,一般不会出问题。

5. 进阶玩法:dash格式、音视频合并、把它变成通用小工具

5.1 什么是dash格式,为什么B站把一个视频拆成两半

如果你把fnval0改成16,接口返回的数据结构会变得不一样:

{ "code": 0, "data": { "dash": { "video": [ { "id": 80, "baseUrl": "https://upos-sz-mirror.../video.m4s?xxx" } ], "audio": [ { "id": 30216, "baseUrl": "https://upos-sz-mirror.../audio.m4s?xxx" } ] } } }

dash对象下面有videoaudio两个数组,视频轨和音频轨是完全分开的两个文件。这就是DASH自适应流媒体协议,它的优势是能根据用户网速动态切换清晰度,让视频加载更流畅。但代价就是,如果你直接下载视频轨文件,打开后会没有声音。

很多用视频下载工具下载B站视频的新手会遇到一个困扰:明明下载下来的是.m4s文件,用播放器打开却只有画面没有声音。这往往是因为他下载的是B站网页缓存里的视频轨,而不是合并后的成品。用接口请求DASH格式时也存在同样的问题。

所以如果你的目标是直接拿一个完整的视频文件,用fnval=0的DURL格式反而是最省事的。只有当你对清晰度有极致要求,连DURL格式都不满足时,才去处理DASH格式。

5.2 用ffmpeg把两个m4s合并成mp4

既然DASH格式把视频和音频分开了,那拿到地址后的最后一步就是合并。合并工具我推荐ffmpeg,几乎全平台通用,而且处理这种纯复制流的合并非常快。

下载好视频轨video.m4s和音频轨audio.m4s之后,在同一目录执行:

ffmpeg -i video.m4s -i audio.m4s -c copy output.mp4

这里的-c copy意思是流复制,也就是不重新编码,只是把视频轨和音频轨封装到同一个MP4容器里。整个合并过程通常几秒就能完成,而且画质没有任何损失。

如果你是在命令行里只拿到了baseUrl,也可以直接用PHP的file_put_contents或者cURL去下载这两个文件。不过要注意,DASH的baseUrl同样需要带Referer请求头,否则下载下来的依然是403。

5.3 支持多P视频

前面说过,view接口返回的data.pages数组里包含了所有分P的信息。把它利用起来,就能把工具从“单P解析”升级成“全P解析”。

思路不复杂:把解析播放地址的逻辑抽成一个resolveByCid($aid, $cid)函数,然后循环遍历pages数组:

$pages = $viewData['data']['pages']; foreach ($pages as $page) { echo "P" . $page['page'] . ": " . $page['part'] . PHP_EOL; $playData = resolveByCid($aid, $page['cid']); // 输出对应地址 }

这样遇到“番剧、课程、合集”这类视频,你一次性就能拿到所有分P的播放地址,而不是只拿第一P。部分官方合集视频可能有几十上百P,用这个工具配合下载工具的批量任务功能,就能实现完整离线备份。

不过还是那句话,合集视频可能涉及UP主和平台版权,个人离线观看、学习剪辑、做笔记都可以,不要二次分发。

5.4 写在最后:关于版权与合规的提醒

每次聊到视频解析、下载这类话题,版权问题都绕不开。我的态度很明确:技术是中性的,但这个能力应该用在合理的地方。

你完全可以用这个工具下载自己上传到B站的视频,做本地备份,这比从后台导出方便得多;你也可以下载一些公有版权、允许二次创作的视频做剪辑素材;你还能用它来学习B站接口的调用逻辑,锻炼自己的PHP和网络编程能力。但我不建议用它去批量抓取他人付费内容,更不建议把下载的视频搬运到其他平台。这不仅涉及版权风险,也违背了创作者分享内容的初衷。

B站的接口和风控策略一直在更新,今天能用的一些参数、字段,过几个月可能就会失效。如果你照着这篇文章操作时发现接口返回异常,最可能的原因就是B站调整了策略。这时候不妨回头看看接口文档、浏览器Network面板里的真实请求,用同样的思路重新适配就好,这也是写代码的乐趣所在。

最后再分享一个小技巧:把上面代码里的request()函数单独保存成一个common.php,以后你在其他项目里需要请求第三方接口时,直接require进来就能复用。我后来写抖音、小红书、YouTube的解析脚本,用的也都是同一个基础函数,只是换了接口地址和参数。站在接口的角度看,这些平台的结构其实大同小异。有了这次的PHP经验,下次你再遇到任何“视频下载”需求,应该都不会慌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 12:12:52

示波器实验报告数据处理:从V/div读数到李萨如图形与误差分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 12:11:41

10 分钟用 TaoToken 跑通 Dify 工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 12:09:11

给Homebrew套上GUI:BrewUI开发实战与踩坑记录

大多数 macOS 开发者的包里都有那么几十个甚至上百个 brew 包&#xff0c;但没几个人能说得清自己机器上到底装了什么、哪些已经没人维护、哪些缓存占了几个 G。我也不例外。某天我盯着终端里刷了上千行的 brew upgrade 输出&#xff0c;突然意识到一个事情&#xff1a;我们天天…

作者头像 李华
网站建设 2026/9/20 12:09:06

Python调用海康工业相机SDK全指南:从环境搭建到图像采集

简介&#xff1a;海康威视相机Python SDK资源包面向需要在安防监控、工业检测、交通管理等场景中调用海康相机的Python开发者&#xff0c;提供图像采集、参数配置、事件回调、远程控制、PTZ与热成像等功能的开发接口&#xff0c;并配有示例代码和文档说明&#xff0c;能显著降低…

作者头像 李华
网站建设 2026/9/20 12:07:19

SasView开源小角散射数据分析平台:模型拟合与批量处理实战指南

简介&#xff1a;SasView是一款面向小角散射&#xff08;SAS&#xff09;数据分析的开源软件&#xff0c;主要服务中子散射&#xff08;SANS&#xff09;和X射线散射实验的科研人员、物理学者及材料科学工作者。它支持直接在倒空间处理一维与二维散射数据&#xff0c;集成PrVie…

作者头像 李华