本文约 5,795 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:方便背后的三个隐形代价
WordPress 4.4 引入 oEmbed 之后,在编辑器里粘贴一个 YouTube、Twitter、B 站的链接,它会自动变成嵌入播放器。这个功能确实方便。
但它有三个不太被注意的代价:
- 每个页面都加载
wp-embed.min.js(哪怕文章里一个嵌入都没有); - 首次嵌入要做一次外部 HTTP 请求去拉取 oEmbed 数据,慢且可能超时;
- 任何 WordPress 站点都能嵌入你的文章(
wp-embed端点默认开启),等于把内容开放给了外部调用。
对性能敏感的站点,这三点都值得处理。
原理分析:oEmbed 是怎么工作的
oEmbed 是一个开放协议:资源方(如 YouTube)提供一个 oEmbed 接口,消费方(你的 WordPress)请求它拿到嵌入 HTML。
流程:
粘贴 URL → WordPress 识别为 oEmbed 提供方 → 请求对方的 /oembed 端点
→ 拿到 HTML 片段 → 存进 postmeta 缓存 → 前台输出 iframe
WordPress 用 WP_Embed 类实现,通过 WP_oEmbed 做发现:
// 注册一个 oEmbed 提供方
wp_oembed_add_provider( 'https://bilibili.com/video/*', 'https://api.bilibili.com/x/web-interface/view' );
内置支持的常见站点(部分):
| 站点 | 说明 |
|---|---|
| YouTube | 需要能访问 youtube.com |
| Twitter / X | 需要能访问 |
| Vimeo、Flickr、SoundCloud、Spotify | 需要能访问 |
| Instagram、TikTok | 需要能访问 |
| 国内的 B 站、优酷、腾讯视频 | 默认不支持,需要插件或手工注册 |
这是国内站点最容易踩的坑:WordPress 内置的提供方全是境外站点,国内直接粘贴 B 站链接不会自动嵌入,而且因为要请求境外接口,还会拖慢编辑器加载。
缓存机制:WordPress 把拉取到的嵌入 HTML 存在 wp_postmeta 的 _oembed_xxx 键里:
SELECT post_id, meta_key FROM wp_postmeta WHERE meta_key LIKE '_oembed_%';
所以第一次慢,之后走缓存。
保姆级实操
一、正常使用
在区块编辑器里:粘贴 URL 到空行 → 自动出现”嵌入”提示 → 点”嵌入”即可。
或者用专门的嵌入区块(YouTube / Vimeo / Twitter 等独立区块)。
二、给国内站点加 oEmbed 支持(以 B 站为例)
add_action( 'init', function() {
// B 站没有标准 oEmbed 端点,用自定义解析
wp_embed_register_handler(
'bilibili',
'#https?://(?:www\.)?bilibili\.com/video/(?:av(\d+)|BV([0-9A-Za-z]+))#i',
'mmf_bilibili_embed_handler'
);
});
function mmf_bilibili_embed_handler( $matches, $attr, $url, $rawattr ) {
$bvid = isset( $matches[2] ) ? $matches[2] : '';
if ( empty( $bvid ) ) {
return '';
}
$embed = sprintf(
'<div class="video-wrap"><iframe src="//player.bilibili.com/player.html?bvid=%s&high_quality=1" scrolling="no" frameborder="0" allowfullscreen="true" loading="lazy"></iframe></div>',
esc_attr( $bvid )
);
return apply_filters( 'embed_bilibili', $embed, $matches, $attr, $url, $rawattr );
}
配套 CSS(响应式 16:9):
.video-wrap {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
max-width: 100%;
}
.video-wrap iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
三、禁用 oEmbed(性能优化常见操作)
完全禁用(包括”别人嵌入我的文章”):
// 停用 oEmbed 发现
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
// 移除前台的 wp-embed.min.js
add_action( 'wp_enqueue_scripts', function() {
wp_deregister_script( 'wp-embed' );
}, 100 );
// 禁用 REST 的 oEmbed 端点
add_filter( 'rest_endpoints', function( $endpoints ) {
unset( $endpoints['/oembed/1.0/embed'] );
return $endpoints;
});
// 彻底关闭 oEmbed 解析(粘贴 URL 不再自动嵌入)
add_filter( 'embed_oembed_discover', '__return_false' );
只移除 JS 但保留嵌入功能(最常用,效果最好):
add_action( 'wp_footer', function() {
wp_deregister_script( 'wp-embed' );
} );
说明:
wp-embed.min.js只用于”嵌入本站文章时的自适应高度通信”和”嵌入内容的响应式调整”。文章里放 YouTube iframe 不依赖它。所以移除它是安全的。
四、阻止别人嵌入自己的文章(内容保护)
// 移除 oEmbed 发现链接
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );
// 服务端拒绝
add_filter( 'rest_endpoints', function( $endpoints ) {
unset( $endpoints['/oembed/1.0/embed'] );
return $endpoints;
});
再在 Nginx 层加 X-Frame-Options(更彻底):
add_header X-Frame-Options "SAMEORIGIN" always;
但注意:这条会让所有 iframe 嵌入失效,包括你自己文章里的 YouTube 播放器。只想阻止外部站点嵌入,用 Content-Security-Policy:
add_header Content-Security-Policy "frame-ancestors 'self'" always;
五、清理 oEmbed 缓存
改了嵌入内容后前台没变,是 postmeta 缓存:
DELETE FROM wp_postmeta WHERE meta_key LIKE '_oembed_%';
wp post meta delete $(wp post list --format=ids) _oembed_time_xxx # 逐个
批量脚本:
wp eval '
global $wpdb;
$n = $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE \"_oembed_%\"");
echo "deleted: {$n}\n";
'
六、用嵌入做懒加载(性能提升明显)
视频 iframe 是前台最大性能杀手之一。改成”点击才加载”:
add_filter( 'embed_oembed_html', function( $html, $url, $attr, $post_id ) {
if ( is_admin() ) {
return $html;
}
// 只在有 iframe 时处理
if ( false === strpos( $html, '<iframe' ) ) {
return $html;
}
$thumb = '';
if ( preg_match( '/youtube\.com\/watch\?v=([\w-]+)/', $url, $m )
|| preg_match( '/youtu\.be\/([\w-]+)/', $url, $m ) ) {
$thumb = 'https://i.ytimg.com/vi/' . $m[1] . '/hqdefault.jpg';
}
return sprintf(
'<div class="lazy-embed" data-embed="%s" %s>
<div class="lazy-embed-btn">▶ 点击加载视频</div>
</div>',
esc_attr( base64_encode( $html ) ),
$thumb ? 'style="background-image:url(' . esc_url( $thumb ) . ')"' : ''
);
}, 10, 4 );
document.addEventListener('click', function (e) {
var box = e.target.closest('.lazy-embed');
if (!box) return;
box.insertAdjacentHTML('beforeend', atob(box.dataset.embed));
box.classList.add('loaded');
});
常见踩坑与失败案例
- 案例一:某站在国内服务器上,编辑器里粘贴 YouTube 链接一直转圈,最后超时——WordPress 去请求
youtube.com/oembed拿不到数据。国内站点应当禁用境外 oEmbed,改用 iframe 或国内视频源。 - 案例二:移除
wp-embed脚本后,自己文章里嵌入的本站文章(跨文章嵌入)高度不对。这是wp-embed.min.js唯一的真实作用,遇到这种情况就别移除它。 - 禁用后旧文章里的嵌入失效:不会。
embed_oembed_discover只影响新解析,已缓存的 HTML 还在 postmeta 里,前台照常输出。只有清了缓存才会失效。 - 缓存的 oEmbed meta 堆积:每篇嵌入过的文章都有几条
_oembed_*,文章多了会膨胀 postmeta。定期清理。 - Twitter/X 嵌入在国内不显示:需要能访问
platform.twitter.com的 JS。国内站点要么放弃,要么用截图+链接替代。 X-Frame-Options加了导致自己视频也打不开:用 CSP 的frame-ancestors代替,它只限制”谁可以嵌我”,不影响我嵌别人。- oEmbed 的 HTTP 请求拖慢后台:编辑器加载时会做 oEmbed 发现请求。国内站点建议直接关掉发现:
add_filter('embed_oembed_discover','__return_false')。
总结
oEmbed 的价值是”粘贴即嵌入”,代价是额外的 JS、外部 HTTP 请求、以及内容被外部嵌入。国内站点的务实做法:禁掉境外 oEmbed 发现(避免编辑器卡在请求 YouTube/Twitter),为 B 站等国内站点手工注册 handler,移除 wp-embed.min.js(除非用到跨文章嵌入),视频一律做点击懒加载。内容保护用 CSP 的 frame-ancestors,不要用 X-Frame-Options(会误伤自己的视频嵌入)。
