我需要从用户可以输入的任何类型的 YouTube 网址中提取
video Id
和 start time
。我有一个可行的解决方案,但它不正确。
问题:
2024/01/16更新:它也必须与播放列表一起工作
我已经检查了this stackoverflow 页面来构建我自己的 youtube url 解析器。
此 preg_match 可以提取
video Id
和 start time
但无法处理许多不同的 YouTube 网址格式:
preg_match("/[a-zA-Z\/\/:\.]*youtu(?:be.com\/watch\?v=|.be\/)([a-zA-Z0-9\-_]+)(?:[&?\/]t=)?(\d*)(?:[a-zA-Z0-9\/\*\-\_\?\&\;\%\=\.]*)/i", $url, $matches);
此 preg_match 处理许多不同的 YouTube 网址(也许是所有类型?),但不提取开始时间:
preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)/", $url, $matches);
我已经更改了它并且它对我有用,但我知道我的更改是不正确的,因为我没有正确解析网址的末尾:
preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)(?:[&?\/]t=)?(\d*)/", $url, $matches);
代码
<?php
declare(strict_types=1);
namespace AppBundle\Value;
class YoutubeVideoData
{
private function __construct(public ?string $videoId = null, public ?int $time = null)
{
}
public static function fromUrl(string $url): self
{
// `#action=share` is not supported
preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)(?:[&?\/]t=)?(\d*)/", $url, $matches);
$videoId = null;
if (isset($matches[1])) {
$videoId = $matches[1];
}
$time = null;
if (isset($matches[2]) && $matches[2] !== "") {
$time = (int) $matches[2];
}
return new self($videoId, $time);
}
}
测试:
<?php
namespace Justimmo\Tests\Value;
use AppBundle\Value\YoutubeVideoData;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
/**
* @covers \AppBundle\Value\YoutubeVideoData::class
*/
class YoutubeVideoDataTest extends TestCase
{
#[DataProvider('urlProvider')]
public function testUrls(string $url, ?string $expectedVideoId, ?int $expectedTime)
{
$videoData = YoutubeVideoData::fromUrl($url);
$this->assertSame($expectedVideoId, $videoData->videoId);
$this->assertSame($expectedTime, $videoData->time);
}
public static function urlProvider(): iterable
{
// playlist
// playlist
yield 'youtube_link_pl1' => ['https://www.youtube.com/watch?v=YjdIF7PuUug&list=PLiIQbaWYR99iZFpLIJ5SImA2y8DaDTv9G&index=21', 'YjdIF7PuUug', null];
yield 'youtube_link_pl2' => ['https://www.youtube.com/watch?list=PLiIQbaWYR99iZFpLIJ5SImA2y8DaDTv9G&v=YjdIF7PuUug&index=21', 'YjdIF7PuUug', null];
yield 'youtube_link_pl3' => ['https://youtu.be/YjdIF7PuUug?list=PLiIQbaWYR99iZFpLIJ5SImA2y8DaDTv9G', 'YjdIF7PuUug', null];
// without https://www
yield 'youtube_link_1' => ['youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_2' => ['youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_3' => ['youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_4' => ['youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_5' => ['youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_6' => ['youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_7' => ['youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_8' => ['youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_9' => ['youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_10' => ['youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_11' => ['m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
// without https://
yield 'youtube_link_12' => ['www.youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_13' => ['www.youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_14' => ['www.youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_15' => ['www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_16' => ['www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_17' => ['www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_18' => ['www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_19' => ['www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_20' => ['www.youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_21' => ['www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
// http
yield 'youtube_link_22' => ['http://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_23' => ['http://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_24' => ['http://youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_25' => ['http://www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_26' => ['http://www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_27' => ['http://www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_28' => ['http://www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_29' => ['http://www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_30' => ['http://youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_31' => ['http://www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_32' => ['http://m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
// https
yield 'youtube_link_33' => ['https://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_34' => ['https://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_35' => ['https://youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_36' => ['https://www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_37' => ['https://www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_38' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_39' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_40' => ['https://www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_41' => ['https://youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_42' => ['https://www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
yield 'youtube_link_43' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
// with start time
yield 'youtube_link_44' => ['https://youtube.com/v/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_45' => ['https://youtube.com/v/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_46' => ['https://youtube.com/vi/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_47' => ['https://www.youtube.com/?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_48' => ['https://www.youtube.com/?vi=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_49' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_50' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_51' => ['https://www.youtu.be/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_52' => ['https://youtube.com/embed/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_53' => ['https://www.youtube.com/shorts/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
yield 'youtube_link_54' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
// with feature
yield 'youtube_link_55' => ['https://www.youtube.com/watch?dev=inprogress&v=7HCZvhRAk-M&feature=related', '7HCZvhRAk-M', null];
yield 'youtube_link_56' => ['https://youtube.com/v/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_57' => ['https://youtube.com/v/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_58' => ['https://youtube.com/vi/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_59' => ['https://www.youtube.com/?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_60' => ['https://www.youtube.com/?vi=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_61' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_62' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_63' => ['https://www.youtu.be/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_64' => ['https://youtube.com/embed/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_65' => ['https://www.youtube.com/shorts/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
yield 'youtube_link_66' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
// these tests do not work - with #action=share
// yield 'youtube_link_67' => ['https://youtube.com/v/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
// yield 'youtube_link_68' => ['https://youtube.com/v/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_69' => ['https://youtube.com/vi/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
// yield 'youtube_link_70' => ['https://www.youtube.com/?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_71' => ['https://www.youtube.com/?vi=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
// yield 'youtube_link_72' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_73' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
// yield 'youtube_link_74' => ['https://www.youtu.be/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_75' => ['https://youtube.com/embed/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_76' => ['https://www.youtube.com/shorts/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
// yield 'youtube_link_77' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
}
}
您可以使用 PHP 的
parse_url
和 parse_str
方法,而不是使用正则表达式。
$urlParts = parse_url($url);
$queryParams = [];
parse_str($urlParts['query'], $queryParams);
$videoId =
$queryParams['vi'] ?: (
$queryParams['v'] ?: (
basename($urlParts['path'])
)
);
$time =
isset($queryParams['t']) ? (int) $queryParams['t'] :
null;
我还没有针对您的所有测试用例进行过尝试,但这似乎是一种更可靠的方法。
如果您想对结果 $videoId
值更有信心,您甚至可以将此与 KIKO Software 的答案 (
https://stackoverflow.com/a/79358821/20418616) 结合起来。
我不太喜欢又长又复杂的正则表达式。它们很难理解,而且太多可能会出错。为什么不采用稍微不同的方法呢?
假设我们采用这样的 youTube URL:
https://www.youtube.com/watch?v=Og40mpl8VNc
视频 ID 为
Og40mpl8VNc
。这是一个 base64 编码 的数字。这是 URL 中唯一经过 Base64 编码的内容。所有其他部分,如 https
、www
、youtube
、com
等)都不是有效的 Base64 编码字符串。也许我们可以用这个?
检查某个内容是否为有效的 Base64 字符串的一个简单方法是对其进行解码和重新编码。那不应该改变字符串。只有
Og40mpl8VNc
可以在不改变的情况下解码和重新编码。
我们可以使用您的
urlProvider()
方法将此检查应用于您所有 Youtube 视频 URL 的所有部分:
$youtubeId = [];
foreach (urlProvider() as $data) {
foreach (array_reverse(preg_split('/[\/?=#&]/', $data[0])) as $part) {
if (trim(base64_encode(base64_decode($part, true)) , '=') == $part) {
$youtubeId[$data[0]] = $part;
break;
}
}
}
我将 URL 拆分为这五个字符:
/?=#&
,以获取所有部分。然后反转这些,因为视频 id 通常位于末尾,然后遍历所有部分寻找有效的 base64 字符串。
请注意,我还删除了所有 base64
=
填充。
现在无可否认,我还没有彻底测试过这一点。这只是一个想法。显然,误报的风险非常小,但我希望这种风险可以忽略不计。
现在我没有花时间,因为我认为你可以很容易地得到它,因为它始终是一个参数。我也没有检查整个 URL 是否有效,但我认为这并不是您真正想知道的。
下面的正则表达式清除了 80 个链接(更新为清除 3 个播放列表链接)。*
(http:\/\/|https:\/\/)?(((www.)?youtu.be\/)|(www.|m.)?youtube.com\/((embed\/|shorts\/)|(\?|(watch\?(dev=inprogress&|list=[0-9a-zA-Z_-]+&)?)?)?vi?(\/|=)))(([0-9a-zA-Z_-])+)([$=#&?\/'"\s])(t=([0-9]+(:[0-9](2))*)*)?
<sup> (NOTE: Above updated to include playlist 19JAN2025.)
它将捕获
<video-id>
和 <start-time>
。开始时间可以是整数,也可以是整数后跟冒号后跟 2 位整数,后跟冒号后跟 2 位整数...,例如t=2055577:33:44:00
在 PHP** 中,您可以通过在捕获括号的左括号“
?<video-id>
”之后立即添加 ?<start-time>
和 (
来识别/捕获组模式,在本例中为 和 模式,如下所示( *): (?<video-id>([0-9a-zA-Z_-])+)
和 (?<start-time>t=([0-9]+(:[0-9](2))*)*)?
这是 PHP 的更新正则表达式,带有
<video-id>
和 <start-time>
组标识符:
(http:\/\/|https:\/\/)?(((www.)?youtu.be\/)|(www.|m.)?youtube.com\/((embed\/|shorts\/)|(\?|(watch\?(dev=inprogress&|list=[0-9a-zA-Z_-]+&)?)?)?vi?(\/|=)))(?<video-id>([0-9a-zA-Z_-])+)([$=#&?\/'"\s])(?<start-time>t=([0-9]+(:[0-9](2))*)*)?
(注:以上内容已更新,包含 2025 年 1 月 19 日的播放列表。)
* 在 https://regex101.com/ 测试正则表达式模式,适用于 PHP>=7.3
** PHP 的正则表达式捕获组位于 * https://www.phptutorial.net/php-tutorial/regex-capturing-groups/,
我的观点是,如果您使用正则表达式或其他字符串手术工具来解析 PHP 已经具有本机解析器的内容,您的代码库将带有“updoc”的味道。
此外,您的任务似乎更多的是文本提取而不是文本验证,所以我假设我们始终使用合法的 youtube 格式的字符串。
解析 url,然后解析查询字符串(如果存在)。 然后,当您尝试从生成的数组中提取所需的值时,进行空合并。 与维护繁琐的正则表达式相比,这将为您省去很多麻烦。 演示
foreach (urlProvider() as $data) {
unset($params); // prevent previous iteration data bleeding into current iteration
$components = parse_url($data[0]);
if (isset($components['query'])) {
parse_str($components['query'], $params);
}
var_export(
[
'id' => $params['vi'] ?? $params['v'] ?? basename($components['path'] ?? '') ?: null,
't' => $params['t'] ?? null,
]
);
}
此方法似乎适用于您提供的所有测试用例。
现在我正在查看其他答案,我的答案似乎是 Rob 答案的更可靠版本(他们的答案没有充分利用空合并)。