
为什么外链图片不能长期用
写技术文章时,引用外部图片(别人博客的截图、GitHub 上的图)看起来很方便,但坑不少:
- 对方一改动,你的文章就破图了(链接失效、目录改名);
- 防盗链:很多站会对非本站 Referer 返回 403,读者看到的是碎图;
- 对方能看到你所有访客的 IP(浏览器直接请求他们的服务器);
- 每次都要额外建连:多一个域名 = DNS 查询 + TLS 握手,拖慢首屏;
- https 页面里引用 http 图片会被浏览器判为混合内容,直接拦掉。
所以正确的做法是:发文时自动把外链图抓回自己的图床,并把地址换掉。
一、要处理两种位置
图片在正文里通常出现两次:
<img src="外链图片">—— 正文显示的小图;<a href="外链图片"><img …></a>—— 点击放大的灯箱链接,指向原图。
只处理第 1 种是常见疏漏:显示正常了,但读者点开大图,走的还是别人的服务器(而且很可能因为防盗链打不开)。
二、最大的坑:别用 wp_update_post() 写回
第一版我这么写:
// ❌ 会造成递归
add_action( 'transition_post_status', function ( $new, $old, $post ) {
// ...下载、替换...
wp_update_post( array( 'ID' => $post->ID, 'post_content' => $new_content ) );
}, 10, 3 );
wp_update_post() 会再次触发 transition_post_status,于是又进一次本函数 → 又更新一次 → 无限递归。
正确做法是直接更新数据库、并清理缓存,绕开钩子:
global $wpdb;
$wpdb->update( $wpdb->posts, array( 'post_content' => $new_content ), array( 'ID' => $post->ID ) );
clean_post_cache( $post->ID );
再加一把锁防重入:
function my_localize_external_images( $post_id ) {
static $running = false;
if ( $running ) { return false; }
$running = true;
// ...处理...
$running = false;
return $changed;
}
三、完整思路
add_action( 'transition_post_status', function ( $new_status, $old_status, $post ) {
if ( ! $post instanceof WP_Post ) { return; }
if ( 'publish' !== $new_status || 'publish' === $old_status ) { return; } // 只在首次发布
if ( wp_is_post_revision( $post->ID ) || wp_is_post_autosave( $post->ID ) ) { return; }
my_localize_external_images( $post->ID );
}, 10, 3 );
处理函数里做四件事:
- 收集候选地址:
<img src>全部 +<a href>里看起来是图片文件的那些(按扩展名判断,避免把普通网页链接也下载下来); - 跳过本站地址(含图床域名),否则会把已经本地化的图再下载一遍;
- 下载:用 WordPress 自带的
media_sideload_image(),它会走媒体库、生成各尺寸缩略图,并落到你配置的图床路径; - 替换地址:下载成功才替换,失败保留原地址——宁可是外链,也不要出现空
src(空 src 会让浏览器重新请求当前页面,非常糟糕)。
$path = (string) wp_parse_url( $url, PHP_URL_PATH );
if ( ! preg_match( '/\.(jpe?g|png|gif|webp|avif|bmp)$/i', $path ) ) { continue; }
$attachment_id = media_sideload_image( $url, $post_id, null, 'id' );
if ( is_wp_error( $attachment_id ) ) { continue; } // 失败就跳过,保留原地址
$new_url = wp_get_attachment_url( $attachment_id );
$content = str_replace( $url, $new_url, $content );
四、和"链接本地化"配合
外链图还有第二种失败模式:<img src> 下载成功了,但外层 <a href> 因为防盗链下载失败——于是显示是本地的、点开还是外链。
所以我又加了一个兜底的插件,用更高的优先级在发布后执行:凡是 <a href> 指向外链图片、而它包着的 <img> 已经是本站地址的,就把 href 直接改成图片自身的地址。
两个插件的分工:
| 优先级 | 干什么 |
|---|---|
| 10 | 把 <img src> / <a href> 的外链图下载并替换成图床地址 |
| 20 | 兜底:扫描剩下的"外链图片链接",回落到本地图片地址 |
五、怎么验证
# 发布一篇带外链图的测试文,然后查正文里还有没有外部域名
或者在数据库里直接看这篇的正文是否还残留外部图片域名。重点确认两件事:
- 正文和图床里都有这张图(下载成功);
<a href>和<img src>指向的是同一个(或同源)地址。
六、回滚
删掉插件文件即可。已经本地化过的图片不回滚——它们在媒体库里,删除插件不会让地址变回去(这通常是好事)。想彻底回到外链,只能手动改回。
一句话总结:外链图片的自动图床化本身不复杂,难在两处——写回数据库不能用 wp_update_post()(会递归),以及别忘了 <a href> 那一层(只换 src 会导致"显示正常、点开是外链")。

评论 (0)