WordPress 删除文章时同步清理图片附件与缩略图的实战方法

分享一段实用的WordPress代码片段,将其加入主题functions.php后,删除文章时会自动同步删除文章的特色图片和图片附件,告别手动清理媒体库释放主机空间,是WordPress站长必备技巧。

张文保 更新 14 分钟阅读 2,040 阅读

我是保哥。运营 WordPress 站点这些年,我见过太多博主忽略一个细节:在后台删掉一篇文章后,文章里上传过的图片、特色图(缩略图)以及附件记录,会原封不动地留在媒体库和服务器磁盘里。短期看不出问题,时间一长,主机空间被几千张早已用不到的图片塞满,备份越来越慢,迁站时光是图片打包就要好几个小时。

这篇文章把我自己用了多年的解决方案完整整理一遍:通过一段挂在 before_delete_post 钩子上的 PHP 代码,让 WordPress 在删除文章的同时,把这篇文章关联的特色图、附件、postmeta 记录一并清掉。下面会从原理、代码、风险、扩展用法四个角度展开。

为什么 WordPress 默认不删除文章里的图片

这是一个常被误解的设计。很多人觉得 WordPress「漏」了一个功能,但实际上保留是有意为之。

在 WordPress 的数据模型里,图片本身是一种 post_type = 'attachment' 的独立内容,它和文章是「父子关系」而不是「从属关系」。一张图片可以被多篇文章引用,也可以被插入到页面、产品、自定义文章类型里。如果删文章就连带删图,对于复用图片的站点会造成大面积丢图。

所以官方默认策略是「保守」:只删 wp_posts 里那一条文章记录,附件和媒体库里的图片文件保持不动,由站长自己决定是否清理。

但是对于绝大多数个人博客、企业站来说,一张图只对应一篇文章,根本不会跨文章复用。这种场景下默认行为反而成了负担:

  • 媒体库越来越乱,找历史图片像在大海捞针。
  • 服务器磁盘被「孤儿图片」吃满,云主机扩容要花真金白银。
  • 备份文件臃肿,小水管 VPS 经常因为 tar 打包过大失败。
  • SEO 上还可能留下一堆指向 404 文章的图片 URL,被搜索引擎反复抓取。

搞清楚这个背景,下面的代码方案才不会用得糊里糊涂。

核心代码:把图片清理挂在 before_delete_post 钩子上

下面这段代码是我目前在用的版本,放进当前主题的 functions.php(建议放在子主题里,避免主题更新被覆盖)即可生效:

/**
 * 删除文章时同步删除关联的特色图与图片附件
 * 挂载点:before_delete_post(在文章被永久删除前触发)
 */
function zwb_delete_post_and_attachments( $post_ID ) {
    global $wpdb;

    // 1. 删除特色图(_thumbnail_id 指向的 attachment)
    $thumbnails = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id' AND post_id = %d",
            $post_ID
        )
    );
    foreach ( $thumbnails as $thumbnail ) {
        wp_delete_attachment( (int) $thumbnail->meta_value, true );
    }

    // 2. 删除以本文为父级的所有附件(图片、PDF、视频等)
    $attachments = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'attachment'",
            $post_ID
        )
    );
    foreach ( $attachments as $attachment ) {
        wp_delete_attachment( (int) $attachment->ID, true );
    }

    // 3. 兜底清掉残留的 _thumbnail_id 记录
    $wpdb->delete(
        $wpdb->postmeta,
        array(
            'post_id'  => $post_ID,
            'meta_key' => '_thumbnail_id',
        ),
        array( '%d', '%s' )
    );
}
add_action( 'before_delete_post', 'zwb_delete_post_and_attachments' );

相比网上流传的老版本,我做了三处加固:

  1. 使用 $wpdb->prepare() 拼接 SQL:原始写法直接把 $post_ID 拼到字符串里,遇到不规范的调用方(比如自定义插件传入字符串)就有 SQL 注入风险,prepare 是 WordPress 官方推荐的安全做法。
  2. 强制类型转换 (int)wp_delete_attachment 期望整型 ID,转一次能避免一些边角情况下的 warning。
  3. 改用 $wpdb->delete() 替代手写 DELETE:可读性更好,参数化也更稳。

钩子选择 before_delete_post 而不是 delete_post 的原因是:前者在文章数据被清前触发,此时还能查到 postmeta;如果用 after_delete_post,部分元数据已经被 WordPress 清掉了,会出现「找得到附件但找不到关系」的尴尬情况。

把代码放进 functions.php 之前,先做这几件事

functions.php 是有风险的,少一个分号都可能让整站白屏。我自己每次改之前都按这个流程走,从没翻过车:

1. 备份当前主题

通过 SSH 或者宝塔面板,把整个主题目录打包:

# 假设主题路径如下,按实际情况替换
cd /www/wwwroot/example.com/wp-content/themes/
tar -czvf my-theme-backup-$(date +%Y%m%d).tar.gz my-theme/

2. 用子主题接管修改

直接改父主题的 functions.php,下次主题更新就全没了。我一直推荐建一个子主题,子主题的 functions.php 是「叠加」而不是「覆盖」,更新主题不会动它。

3. 测试环境先跑一遍

在本地用 Local by Flywheel 或 Docker 起一个 WordPress,把代码贴进去,新建一篇带特色图和多张内文图的文章,再删掉,去媒体库确认图片是否消失,去服务器对应的 wp-content/uploads/年/月/ 目录确认物理文件是否真的没了。

我自己踩过的坑:这几种情况要谨慎

这套代码在「一图一文」场景下非常稳,但下面三种情况要格外小心,否则会误删:

情况一:同一张图被多篇文章引用

如果你习惯在不同文章里复用同一张配图,这段代码不会判断「这张图是否还在被别人用」,它会直接删。后果是:A 文章删了,B 文章里的同一张图变成 404。解决思路是在 wp_delete_attachment 之前加一段查询,看 post_content 里是否还有别的文章引用了这个 URL,没有再删。我在这部分会推荐用 WP_Query + s 参数搜索,不要用 LIKE 直接扫数据库。

情况二:附件被 ACF / 自定义字段引用

用 ACF(Advanced Custom Fields)的站点,图片往往不是「父子关系」,而是通过字段 ID 关联,post_parent 可能是 0。这种情况下上面代码的第二段查询根本扫不到这些附件,要额外写逻辑去 wp_postmeta 里查 ACF 字段对应的附件 ID。

情况三:CDN / 对象存储独立存储图片

如果你把媒体库挂载到了七牛、阿里云 OSS、又拍云这类对象存储,本地其实没有图片文件,只有 wp_posts 里的附件记录。wp_delete_attachment 触发的时候,对应插件(比如 WP Stateless、阿里云 OSS for WordPress)通常会监听 delete_attachment 钩子,连带把云端的对象一起删掉。建议先在云存储后台开启版本控制或回收站,万一删错可以恢复。

进阶:把功能做成插件而不是塞进主题

等代码经过一段时间稳定运行,我会建议把它从 functions.php 里挪出来,做成一个独立的 mu-plugin(必装插件)。原因有三个:

  • 换主题的时候功能不会丢。
  • wp-content/mu-plugins/ 下的插件不能在后台被禁用,更可靠。
  • 跟主题逻辑解耦,便于后续维护和迁移。

做法很简单。在 wp-content/ 下新建 mu-plugins/ 目录,里面放一个 zwb-clean-attachments.php 文件:

<?php
/**
 * Plugin Name: ZWB Clean Attachments On Post Delete
 * Description: 删除文章时同步清理特色图与附件,防止媒体库堆积孤儿图片。
 * Version: 1.0
 * Author: 保哥
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// 直接复用前面 zwb_delete_post_and_attachments 函数体,这里省略

WordPress 启动时会自动扫描 mu-plugins/ 目录加载,无需在后台激活。

清理历史「孤儿附件」的一次性脚本

上面的代码只能管「以后删的文章」,对历史上已经堆积的孤儿附件无能为力。这种情况我一般推荐用一次性 WP-CLI 脚本扫一遍:

# 列出所有 post_parent = 0 且未在任何文章 content 中出现的图片附件
wp post list --post_type=attachment --post_parent=0 --format=ids

拿到 ID 列表后,再写一段脚本判断哪些 URL 在 wp_posts.post_content 里出现过,没出现的就是真孤儿,可以批量删除。这个清理操作非常重,一定要先做整站数据库 + uploads 目录的备份,跑完用 du -sh wp-content/uploads/ 对比清理前后的体积,心里就有数了。

FAQ

Q1:执行后图片只是从媒体库消失,还是物理文件也被删了?

A:wp_delete_attachment( $id, true ) 第二个参数为 true,表示「强制删除」,会调用 wp_delete_attachment_files() 把对应的原图、各种尺寸的缩略图以及 EXIF 中间文件都从磁盘上一并删除。如果只想删数据库记录、保留物理文件,把第二个参数改成 false 即可,但不推荐这样做,会留下更多孤儿文件。

Q2:用回收站恢复文章时,图片还能找回来吗?

A:找不回。before_delete_post 是文章「永久删除」(也就是从回收站再次点删除)时才触发的钩子,文章在被丢进回收站时不会触发,所以图片还在;但一旦从回收站清空,图片就跟文章一起永久消失了。如果你担心误操作,可以把删除前的图片先复制到一个备份目录,等观察一段时间再清理。

Q3:能不能让管理员删文章时清图片,普通编辑不清?

A:可以,在函数开头加一层权限判断:

if ( ! current_user_can( 'manage_options' ) ) {
    return;
}

这样只有「管理员」角色触发删除时才会同步清图,避免编辑误删。

Q4:装了「WP Smush」「ShortPixel」这类图片优化插件会冲突吗?

A:不会。这类插件的工作时机在「上传图片时」或「图片被请求时」,跟 before_delete_post 钩子完全不在一个生命周期。我自己同时跑着 ShortPixel 和这段清理代码,长期使用没出过任何问题。

小结一下:WordPress 默认保留图片是出于安全考虑,但对一图一文的博客没必要。用 before_delete_post + wp_delete_attachment 这套组合拳,可以把删文章变成一次「干净的清理」,长期下来能省下大量主机空间。代码不复杂,但放进生产环境前一定要走「备份-子主题/mu-plugin-测试-上线」这个标准流程。

分享到
标签
版权声明

本文标题:《WordPress 删除文章时同步清理图片附件与缩略图的实战方法》

本文链接:https://zhangwenbao.com/wordpress-delete-article-pictures-when-deleting-articles.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交