☰
PHP短视频解析源码:上传即用,无水印直链提取方案
2026/10/9 8:55:47 网站建设 项目流程

简介:这是一套面向开发者与数据分析人员的短视频解析源码,主打上传即可使用,无需复杂配置即可提取视频链接、封面、标题、播放量、评论等关键数据,适用于内容监控、市场趋势研究与第三方应用开发等场景。资源包共14个文件,约203KB,以php接口文件为核心,配合js与css构建前端交互与播放器界面,另含ini配置、htaccess规则及html页面,整体结构轻量、便于二次修改与部署。目前已有200人学习下载,说明其在同类工具中具备一定参考价值。源码按请求、解析、数据存储、界面展示与错误处理等模块组织,读者可据此快速理解短视频数据抓取流程,并在此基础上调整接口调用与解析逻辑,提升数据获取效率。使用时需注意遵守平台协议与版权法规,合法合规地使用数据。

1. 上传即可使用的短视频解析源码:一套 PHP 方案到底解决了什么

把源码包丢到服务器上,改两个配置就能跑,这种「上传即可使用」的短视频解析源码,核心解决的是内容搬运和二次分发场景里最脏最累的那一段:拿到分享链接,解析出无水印的直链地址,再交给前端播放或下载。它面向的不是大厂的内容中台,而是个人站长、小型工具站、私域运营和做去水印小程序源码那批人——预算有限、没有专职后端、希望今天部署明天就能用。

这套方案通常由三个文件撑起门面:index.php负责页面渲染和交互,api.php负责接收链接、调度解析、返回 JSON,前端用 layui 拼出输入框和结果卡片。听起来简单,但真正决定它能不能长期跑的,是解析层怎么对抗平台链接格式的频繁变动,以及接口怎么防止被人白嫖。下面按「先讲清原理和选型,再落到能抄的代码和参数」的顺序拆开,中间会专门用一章讲那些上线后才会暴露的坑。

2. 解析链路拆解:从分享口令到无水印直链要过几道手

2.1 为什么是 PHP + api.php + index.php 这套组合

选 PHP 不是因为它优雅,而是因为它在「上传即可使用」这个约束下几乎没有对手。虚拟主机、宝塔面板、LNMP 一键包默认都带 PHP 运行时,用户不需要装 Node、不需要配 Python 虚拟环境、不需要碰 Docker。index.php和api.php分离是这套源码的标准骨架:前者是入口页面,后者是纯接口,两者通过 HTTP 通信,职责清晰。

index.php的职责被压到最薄:渲染一个输入框、一个按钮、一块结果区,把用户粘贴的分享文本 POST 给api.php,拿到 JSON 后决定是显示视频还是提示失败。它不碰任何解析逻辑,这样即使解析规则天天变,页面本身不用动。api.php才是真正干活的地方,它要完成四件事:清洗输入、识别平台、请求真实地址、组装返回结构。

前端选 layui 也是同一个逻辑。layui 的 CDN 引入一行就够,表单、按钮、卡片、加载动画都是现成组件,不需要构建工具,不需要 npm install。对于一个「上传即可使用」的包,任何需要npm run build的前端方案都是负担。常见做法是把 layui 的 css 和 js 放在本地static/目录,避免 CDN 被墙或加载慢导致页面白屏。

提示:如果你的服务器在国内,layui 的 CDN 建议换成本地文件,否则首屏会卡在等待样式上,用户以为坏了。

2.2 分享文本清洗:正则要处理的五种脏输入

用户粘贴进来的从来不是干净的 URL。真实场景里你会收到带中文说明的、带 emoji 的、带一堆话题标签的、甚至带换行的整段分享文案。api.php第一步必须把这些噪音剥掉,否则后面所有请求都会失败。

<?php // api.php 输入清洗片段 function extractUrl($raw) { // 去掉首尾空白和不可见字符 $raw = trim($raw); // 匹配 http/https 开头的链接,直到遇到空格或中文 if (preg_match('/https?:\/\/[^\s\x{4e00}-\x{9fa5}]+/u', $raw, $m)) { return $m[0]; } return ''; } $input = $_POST['url'] ?? ''; $url = extractUrl($input); if ($url === '') { echo json_encode(['code' => 400, 'msg' => '未识别到有效链接']); exit; }

这段正则的关键在[^\s\x{4e00}-\x{9fa5}]+:它允许链接里出现字母、数字、斜杠、问号、等号、下划线,但一旦碰到空白或中文字符就停止。这样即使用户粘贴的是「看看这个 https://xxx.com/abc 好好笑」也能正确截出链接。参数上,u修饰符必须加,否则中文字符范围匹配会出错。

清洗完还要做一次平台识别。常见做法是用域名关键字判断,比如链接里包含douyin、kuaishou、xiaohongshu就分派到不同的解析分支。这一步不要用strpos硬匹配整个域名,因为分享链接经常走短链跳转,域名可能是v.douyin.com这种。用stripos($url, 'douyin') !== false这种宽松匹配更稳。

2.3 请求真实地址:header 伪装和超时设置

拿到清洗后的链接,下一步是请求它,让它 302 跳到真实页面,再从页面里提取视频直链。这一步是整套源码最容易翻车的地方,因为平台会检查请求头。

<?php // api.php 请求片段 function fetchPage($url) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, // 跟随 302 跳转 CURLOPT_MAXREDIRS => 5, // 最多跳 5 次,防止死循环 CURLOPT_TIMEOUT => 8, // 总超时 8 秒 CURLOPT_CONNECTTIMEOUT => 3, // 连接超时 3 秒 CURLOPT_SSL_VERIFYPEER => false, // 虚拟主机常缺 CA 证书 CURLOPT_USERAGENT => 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15', CURLOPT_HTTPHEADER => [ 'Accept: text/html,application/xhtml+xml', 'Accept-Language: zh-CN,zh;q=0.9', ], ]); $html = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode === 200 ? $html : ''; }

参数逐个说:FOLLOWLOCATION必须开,因为短链不跳转就拿不到内容;MAXREDIRS设 5 是防止某些异常链接无限跳转把 PHP 进程拖死;TIMEOUT设 8 秒是平衡——太短正常请求还没回来,太长用户等不及关页面;SSL_VERIFYPEER关掉是因为很多虚拟主机没装最新 CA 证书,开着会直接报 SSL 错误,这是血泪经验。

USERAGENT伪装成 iPhone 很关键。很多平台对桌面 UA 返回的页面结构和移动端不一样,移动端页面里嵌的视频直链更容易提取。Accept-Language带上zh-CN也是同理,避免被识别成爬虫返回空页面。

拿到 HTML 后,提取直链通常用正则匹配play_addr或video/tos这类关键字。不同平台字段名不同,所以api.php里一般会写多个正则分支,按平台分派。匹配到之后返回 JSON,结构建议统一成{code, msg, data: {title, cover, url}},这样index.php不用关心是哪个平台。

3. 前端用 layui 拼出可用界面:三个必须处理的交互状态

3.1 输入框、按钮、结果卡片的 DOM 结构

layui 的组件化写法让页面结构很清晰。核心是一个表单区域加一个结果区域,结果区域默认隐藏,解析成功后再显示。

<!-- index.php 页面结构 --> <div class="layui-container" style="margin-top:40px;"> <div class="layui-form-item"> <textarea id="shareUrl" class="layui-textarea" placeholder="粘贴分享链接或分享文案"></textarea> </div> <div class="layui-form-item"> <button id="parseBtn" class="layui-btn layui-btn-fluid">开始解析</button> </div> <div id="resultCard" class="layui-card" style="display:none;"> <div class="layui-card-header" id="videoTitle"></div> <div class="layui-card-body"> <video id="videoPlayer" controls style="width:100%;"></video> <a id="downloadLink" class="layui-btn layui-btn-normal" style="margin-top:10px;">下载视频</a> </div> </div> </div>

这里用textarea而不是input,因为用户经常粘贴多行分享文案。layui-btn-fluid让按钮占满宽度,移动端点击区域更大。结果卡片默认display:none,避免页面一开始就出现空白卡片。

3.2 用 layui 的 layer 做加载和错误提示

解析请求是异步的,用户点完按钮到拿到结果之间有几秒空窗,必须给反馈,否则用户会重复点击。

// index.php 交互逻辑 layui.use(['layer', 'jquery'], function(){ var layer = layui.layer, $ = layui.jquery; $('#parseBtn').on('click', function(){ var url = $('#shareUrl').val().trim(); if (!url) { layer.msg('请先粘贴链接'); return; } var loadIndex = layer.load(2); // 2 是加载动画样式 $.post('api.php', { url: url }, function(res){ layer.close(loadIndex); if (res.code !== 200) { layer.msg(res.msg || '解析失败,请检查链接'); return; } $('#videoTitle').text(res.data.title || '解析结果'); $('#videoPlayer').attr('src', res.data.url); $('#downloadLink').attr('href', res.data.url); $('#resultCard').show(); }, 'json').fail(function(){ layer.close(loadIndex); layer.msg('网络异常,请稍后重试'); }); }); });

layer.load(2)的 2 是动画样式编号,不同编号动画不同,选一个不刺眼的即可。layer.close(loadIndex)必须在成功和失败两个分支都调用,否则加载动画会一直转。.fail()处理的是 HTTP 层面的失败,比如api.php返回 500 或超时,这和res.code !== 200是两回事,都要处理。

3.3 下载按钮的直链处理和跨域注意

下载按钮直接指向解析出的视频直链,但这里有个坑:如果直链是跨域的,浏览器点击下载可能变成在线播放而不是下载。解决办法是给a标签加download属性,但download属性对跨域链接无效。

<!-- 更稳的下载方式:走后端中转 --> <a href="api.php?action=download&url=<?php echo urlencode($videoUrl); ?>" class="layui-btn layui-btn-normal">下载视频</a>

在api.php里加一个action=download分支,用readfile或curl把视频流中转给用户,同时设置Content-Disposition: attachment。这样虽然消耗服务器流量,但下载行为可控,也能顺便做防盗链。如果服务器流量有限,就保留直链下载,接受部分浏览器在线播放的现实。

注意:中转下载会占用服务器带宽,如果视频较大且并发高,建议加一个简单的频率限制,比如同一 IP 每分钟最多下载 3 次。

4. 避坑与排查:上线后最容易翻车的五个地方

4.1 解析突然全部失败,但代码没动过

现象:昨天还好好的,今天所有链接都返回「解析失败」,api.php日志里 curl 返回空。

原因:平台改了页面结构或接口字段名,原来的正则匹配不到了。这是短视频解析源码的宿命,平台不会通知你。

解决:在api.php里加一个调试开关,把 curl 拿到的 HTML 存到logs/目录,出问题时对比前后 HTML 差异,定位是哪个字段变了。常见做法是每天定时跑一个测试链接,失败就发邮件提醒。

4.2 虚拟主机上 curl 报 SSL 错误

现象:本地测试正常,上传到虚拟主机后所有请求返回 false,错误信息是SSL certificate problem。

原因:虚拟主机的 PHP 环境缺少最新的 CA 证书包,curl 无法验证 HTTPS 证书。

解决:在curl_setopt里加CURLOPT_SSL_VERIFYPEER => false和CURLOPT_SSL_VERIFYHOST => false。这是妥协方案,安全性降低,但在虚拟主机环境下是唯一能跑通的办法。如果服务器可控,正确做法是更新 CA 证书包。

4.3 接口被白嫖,流量跑光

现象:服务器流量异常飙升,api.php被大量外部请求调用,但index.php的访问量没变。

原因:api.php是公开接口,别人直接 POST 链接就能用你的服务器解析,你成了免费代理。

解决:加一个简单的 token 校验。index.php渲染时生成一个带时间戳的 token 塞到页面里,api.php校验 token 是否有效且未过期。同时用Referer检查请求来源,虽然Referer可以伪造,但能挡住大部分低级爬虫。

<?php // api.php 简易 token 校验 session_start(); $token = $_POST['token'] ?? ''; if (empty($_SESSION['token']) || $token !== $_SESSION['token']) { echo json_encode(['code' => 403, 'msg' => '非法请求']); exit; } // token 有效期 10 分钟 if (time() - $_SESSION['token_time'] > 600) { echo json_encode(['code' => 403, 'msg' => '请求过期']); exit; }

4.4 中文标题乱码

现象:解析成功,视频能播,但标题显示成ä½ å¥½这种乱码。

原因:api.php返回 JSON 时没有设置Content-Type: application/json; charset=utf-8,或者 curl 拿到的 HTML 编码不是 UTF-8,提取时没做转换。

解决:在api.php开头加header('Content-Type: application/json; charset=utf-8');。如果 curl 拿到的页面是 GBK 编码,用mb_convert_encoding($html, 'UTF-8', 'GBK')转一下再匹配。判断编码可以用mb_detect_encoding,但它经常误判,更稳的做法是看 HTML 里的meta charset标签。

4.5 并发一高就 502

现象:几个人同时用没问题,十几个人同时点解析,服务器返回 502 Bad Gateway。

原因:api.php里 curl 超时设得太长,每个请求占用 PHP-FPM 进程 8 秒,进程池被占满,Nginx 等不到响应就报 502。

解决:把CURLOPT_TIMEOUT降到 5 秒,CURLOPT_CONNECTTIMEOUT降到 2 秒。同时在api.php入口加一个基于文件锁的并发限制,超过阈值直接返回「当前请求过多,请稍后重试」,而不是让请求堆积。

<?php // api.php 并发限制 $lockFile = sys_get_temp_dir() . '/parse.lock'; $fp = fopen($lockFile, 'c'); if (!flock($fp, LOCK_EX | LOCK_NB)) { echo json_encode(['code' => 429, 'msg' => '请求过多,请稍后']); exit; } // 业务逻辑... flock($fp, LOCK_UN); fclose($fp);

这个锁是进程级的,只能防单机并发,多台服务器需要换 Redis 计数器。但对「上传即可使用」的单机场景,文件锁足够。

5. 进阶:把解析成功率从 70% 拉到 95% 的两个技巧

5.1 多正则兜底和字段模糊匹配

单一正则匹配是脆弱的,平台改一个字段名就全挂。更稳的做法是准备一组正则,按优先级依次尝试,命中即返回。

<?php // api.php 多正则兜底 function extractVideoUrl($html) { $patterns = [ '/"play_addr":\{"url_list":\["([^"]+)"/', '/"video_url":"([^"]+)"/', '/src="(https?:\/\/[^"]+\.mp4[^"]*)"/', '/"url":"(https?:\/\/[^"]+\.mp4[^"]*)"/', ]; foreach ($patterns as $p) { if (preg_match($p, $html, $m)) { // 把 \u002F 这类转义还原成 / return str_replace('\\u002F', '/', $m[1]); } } return ''; }

四个正则覆盖了常见的字段名变体,最后一个匹配.mp4后缀是兜底中的兜底。str_replace('\\u002F', '/')处理的是 JSON 里斜杠被转义的情况,这个坑很隐蔽,匹配到了但链接打不开,就是因为斜杠是\u002F。

5.2 用缓存减少重复请求

同一个视频链接被多次解析是常态,每次都去请求平台既慢又容易被限流。加一层文件缓存,把链接的 MD5 作为文件名,缓存解析结果 10 分钟。

<?php // api.php 缓存逻辑 function getCache($url) { $file = sys_get_temp_dir() . '/parse_' . md5($url) . '.json'; if (file_exists($file) && time() - filemtime($file) < 600) { return json_decode(file_get_contents($file), true); } return null; } function setCache($url, $data) { $file = sys_get_temp_dir() . '/parse_' . md5($url) . '.json'; file_put_contents($file, json_encode($data)); }

缓存时间设 10 分钟是个平衡:太短起不到效果,太长用户可能拿到过期的直链(部分平台的直链有时效)。sys_get_temp_dir()在虚拟主机上通常可写,如果不可写就换成当前目录下的cache/并确保有写权限。

5.3 验证解析是否真的可用

解析返回 200 不代表视频能播。有些平台返回的直链需要特定的Referer才能访问,直接丢给浏览器会 403。验证方法是拿到直链后再发一个 HEAD 请求,看返回的Content-Type是不是video/mp4。

<?php // api.php 直链验证 function verifyUrl($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_NOBODY => true, // HEAD 请求 CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3, CURLOPT_SSL_VERIFYPEER => false, ]); curl_exec($ch); $type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); curl_close($ch); return strpos($type, 'video') !== false; }

如果验证失败,就换下一个正则的结果重试。这个步骤会增加 1 到 2 秒延迟,但能把「解析成功但播不了」的投诉降下来。我一般会在缓存里标记验证结果,验证失败的链接不缓存,下次重新解析。

这套源码值不值得做,取决于你能不能接受「解析规则需要持续维护」这个前提。如果只是想一次性部署然后不管,它一定会烂掉;如果愿意每周花十分钟看看日志、更新一下正则,它能稳定跑很久。我自己维护的一套跑了两年多,最大的习惯就是每天上班先看一眼解析成功率,低于 90% 就翻日志找原因。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询