搜索全站
安装部署 15 分钟阅读

用 WordPress 搭建图片素材站:图片管理、EXIF、批量上传与展示

想把几千张图片做成素材站?本文给出目录扫描批量入库的完整代码,讲清 EXIF 信息的读取与清洗、缩略图尺寸规划、瀑布流展示实现,以及大量图片带来的媒体库性能问题与应对方法。

U
UFO_PF 2018-06-17 · 阅读 15 分钟
用 安装部署
阅读提示
本文约 5,955 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:三千张图传上去,媒体库卡到打不开

有人用 WordPress 做摄影素材站,第一版操作是:把本地三千张照片打包,用后台的”添加媒体”一次性上传。上传到一半超时,重试几次后库里多了一堆重复的附件;好不容易传完,打开媒体库要等十几秒;前台列表页因为没有分页,一次加载几百张原图,页面大小 80MB。

图片站和普通博客的技术特征完全不同:数据量大、单文件体积大、元数据(EXIF)丰富、展示方式以网格为主。用管理文章的方式管理图片,必然踩坑。核心要解决四件事:怎么高效入库、怎么保存和展示图片参数、怎么生成合适的缩略图、怎么在不卡死的前提下展示大量图片。

原理分析:图片在 WordPress 里的三层数据

第一层是附件记录(wp_posts 中 post_type='attachment'),代表媒体库里的一张图;第二层是元数据(wp_postmeta 中 _wp_attachment_metadata),由 wp_generate_attachment_metadata() 生成,包含各尺寸缩略图的文件名、宽高,以及从 EXIF 读出的相机参数;第三层是文件系统,即 uploads 目录下的真实文件与其派生尺寸。

批量入库必须同时写这三层,缺一不可。只插入 wp_posts 记录会出现”媒体库里有图但前台不显示”;只拷贝文件不建记录则媒体库看不到。

层级 关键函数 遗漏后果
附件记录 wp_insert_attachment() 媒体库不显示
元数据 wp_generate_attachment_metadata() 缩略图失效
文件系统 copy() / wp_handle_upload() 文件 404

关于 EXIF,需要知道两件事:一是 PHP 的 exif_read_data() 依赖 exif 扩展,部分精简环境未启用;二是 EXIF 里包含 GPS 坐标,直接展示会泄露拍摄位置,隐私敏感型站点应当剥离。

保姆级实操

步骤 1:准备待导入目录。 把图片按分类放到服务器上的子目录,目录名即分类名。

mkdir -p /tmp/import/{风景,人像,建筑}
# 确认扩展支持
php -m | grep -i exif
php -r 'var_dump(function_exists("exif_read_data"));'

步骤 2:扫描目录批量入库。 用 WP-CLI 脚本,一次性完成三层写入。

<?php
/**
 * 用法:wp eval-file import-images.php /tmp/import --path=/var/www/mimangfei
 */
$base = $args[0];
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';

$dirs = glob( $base . '/*', GLOB_ONLYDIR );
foreach ( $dirs as $dir ) {
    $cat_name = basename( $dir );
    $term     = term_exists( $cat_name, 'gallery_cat' );
    if ( ! $term ) {
        $term = wp_insert_term( $cat_name, 'gallery_cat' );
    }

    foreach ( glob( $dir . '/*.{jpg,jpeg,png,webp}', GLOB_BRACE ) as $file ) {
        $filename = basename( $file );
        $upload   = wp_upload_bits( $filename, null, file_get_contents( $file ) );
        if ( ! empty( $upload['error'] ) ) {
            WP_CLI::warning( $upload['error'] );
            continue;
        }

        $attach_id = wp_insert_attachment( array(
            'guid'           => $upload['url'],
            'post_mime_type' => $upload['type'],
            'post_title'     => preg_replace( '/\.[^.]+$/', '', $filename ),
            'post_status'    => 'inherit',
        ), $upload['file'] );

        $meta = wp_generate_attachment_metadata( $attach_id, $upload['file'] );
        wp_update_attachment_metadata( $attach_id, $meta );

        wp_set_object_terms( $attach_id, (int) $term['term_id'], 'gallery_cat' );

        // 读取 EXIF 并落到自定义字段
        if ( function_exists( 'exif_read_data' ) && 'image/jpeg' === $upload['type'] ) {
            $exif = @exif_read_data( $upload['file'], 'EXIF', true );
            if ( ! empty( $exif['EXIF'] ) ) {
                update_post_meta( $attach_id, '_camera_model', sanitize_text_field( $exif['IFD0']['Model'] ?? '' ) );
                update_post_meta( $attach_id, '_focal_length', sanitize_text_field( $exif['EXIF']['FocalLength'] ?? '' ) );
                update_post_meta( $attach_id, '_aperture', sanitize_text_field( $exif['EXIF']['FNumber'] ?? '' ) );
                update_post_meta( $attach_id, '_iso', sanitize_text_field( $exif['EXIF']['ISOSpeedRatings'] ?? '' ) );
                // 不保存 GPS,避免泄露拍摄位置
            }
        }
    }
}
WP_CLI::success( '图片导入完成' );

步骤 3:规划缩略图尺寸。 只注册真正用到的尺寸,避免每个附件生成七八个文件。

add_action( 'after_setup_theme', function () {
    add_theme_support( 'post-thumbnails' );
    add_image_size( 'gallery_grid', 400, 0, false );  // 网格列表
    add_image_size( 'gallery_large', 1600, 0, false ); // 详情页大图
} );

add_filter( 'intermediate_image_sizes_advanced', function ( $sizes ) {
    unset( $sizes['medium_large'] );
    unset( $sizes['1536x1536'] );
    unset( $sizes['2048x2048'] );
    return $sizes;
} );

步骤 4:前端瀑布流展示。 用 CSS columns 实现,无需 JS 库,配合 srcset 自动选图。

.gallery-masonry {
    column-count: 4;
    column-gap: 16px;
}
.gallery-masonry__item {
    break-inside: avoid;
    margin: 0 0 16px;
    border-radius: 8px;
    overflow: hidden;
    background: #f1f5f9;
}
.gallery-masonry__item img {
    display: block;
    width: 100%;
    height: auto;
}
@media (max-width: 1024px) { .gallery-masonry { column-count: 3; } }
@media (max-width: 640px)  { .gallery-masonry { column-count: 2; } }

步骤 5:输出带 srcset 的图片。 让浏览器按屏幕大小自动选择,节省移动端流量。

<?php
$ids = get_posts( array(
    'post_type'      => 'attachment',
    'post_mime_type' => 'image',
    'posts_per_page' => 24,
    'post_status'    => 'inherit',
    'tax_query'      => array( array(
        'taxonomy' => 'gallery_cat',
        'field'    => 'slug',
        'terms'    => '风景',
    ) ),
) );
echo '<div class="gallery-masonry">';
foreach ( $ids as $img ) {
    echo '<div class="gallery-masonry__item">';
    echo wp_get_attachment_image( $img->ID, 'gallery_grid', false, array(
        'loading' => 'lazy',
        'sizes'   => '(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw',
    ) );
    echo '</div>';
}
echo '</div>';

步骤 6:媒体库性能优化。 附件超过五千条后,媒体库列表会明显变慢。用钩子限制列表查询字段,或按分类筛选使用。

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() && $query->is_main_query() && 'attachment' === $query->get( 'post_type' ) ) {
        $query->set( 'posts_per_page', 40 );
        $query->set( 'no_found_rows', true ); // 跳过总数统计,显著提升速度
    }
} );

常见踩坑

  • 一次性上传几千张,PHP 报 “Maximum execution time of 30 seconds exceeded”。
  • 上传大图提示 “HTTP error.”,实为 post_max_size 或 nginx client_max_body_size 太小。
  • 媒体库慢到打不开,因为默认查询做了 SQL_CALC_FOUND_ROWS 总数统计。
  • 每个附件生成七八种尺寸,磁盘占用是原图的三倍。
  • 直接展示原图,页面单张 5MB,移动端直接卡死。
  • 案例:某摄影站把原图直接放进文章,未生成缩略图。首页一次加载 30 张 4MB 的原图,LCP 超过 12 秒,移动端跳出率 95%。改造后统一输出 gallery_grid 尺寸并加 loading="lazy",首屏图片总体积从 120MB 降到 2.3MB,LCP 降到 1.8 秒。
  • EXIF 里的 GPS 坐标被完整展示,摄影师拍摄地泄露。
  • 图片文件名含中文,部分 CDN 回源时出现 404。

总结

图片站是否做对,看三个数字:单张展示图体积 <200KB、列表页首屏图片总体积 <3MB、媒体库打开时间 <2 秒。行动清单:① 批量入库同时写三层数据;② 只注册必要尺寸;③ 用 wp_get_attachment_image() 输出 srcset;④ 剥离 GPS 等敏感 EXIF;⑤ 开启懒加载;⑥ 附件量大时限制媒体库查询。图片站还涉及版权问题,可继续看 图片水印与版权保护、WordPress 图片优化 与 WordPress 媒体库管理。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。