前言:为什么要折腾 WebP
WebP 是 Google 推出的图片格式,同样画质下体积比 JPEG 小 25%~35%,比 PNG 小 50%~70%。
对网站来说,图片体积直接决定两件事:页面打开速度和服务器带宽消耗。一个图片占大头的站点,全站换 WebP 之后,流量能省掉三分之一到一半。
但WordPress 站点动辄上千篇文章,正文里的 <img src="xxx.jpg"> 早就写死了。一篇篇去改链接,显然不现实。
本文记录的方法是:服务器上多存一份 .webp 文件,由 Apache 在响应时自动替换,网页代码一个字不改。
一、核心原理:URL 只是门牌号,服务器说了算
很多人不理解"链接不改,怎么能返回另一种格式"。关键在于一个容易被忽略的事实:
浏览器判断图片格式,靠的是响应头里的 Content-Type,不是 URL 后缀。
后缀(.jpg / .png)只是给人看的,MIME 类型才是给机器看的。
完整流程
- 浏览器读到
<img src="/images/demo.jpg">,发起请求,请求头里带上Accept: image/webp,image/*(等于自我介绍"我支持 webp") - Apache 收到请求,逐条检查重写规则
- 发现浏览器支持 webp,且磁盘上确实存在
demo.jpg.webp - 内部重写:原本要读 demo.jpg,实际去读 demo.jpg.webp
- 同时把响应头改成
Content-Type: image/webp - 浏览器看到 Content-Type 是 webp,用 webp 解码器正常显示 —— 它从头到尾不知道 URL 后缀是 .jpg
这里的第 4 步是内部重写,不是 301 跳转,所以浏览器完全无感知,地址栏也不会变。
二、准备工作:下载 cwebp 转换工具
cwebp 是 Google 官方提供的免费命令行工具,负责把 jpg/png 转成 webp。
下载地址
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.6.0-windows-x64.zip
如果官方地址下载慢,可以访问版本列表页选择其他版本:
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html
安装建议
解压之后,把 bin 目录里的 cwebp.exe 单独复制到一个短路径,例如:
C:\cwebp\cwebp.exe
这样做的好处是后面写批处理脚本时路径短,不容易出错。
bin 目录里还有很多其他 exe(dwebp、gif2webp、webpmux 等),它们都用不到,只需要 cwebp 一个。
验证安装
C:\cwebp\cwebp.exe -version
能显示版本号即安装成功。注意 cwebp 是命令行工具,双击没有反应是正常的,必须在 CMD 里执行。
三、批量转换:只在原目录新增文件
这一步的目标:把每个 demo.jpg 转成 demo.jpg.webp,放在同一个目录。
为什么要命名为 .jpg.webp 而不是 .webp
因为 Apache 拼接路径时只需要做最简单的操作:
请求的 URL + ".webp" = webp 文件路径
写成 demo.jpg.webp,规则里直接追加后缀即可。如果写成 demo.webp,Apache 还得先把 .jpg 砍掉再换,规则复杂且容易出错。
转换脚本
保存为 convert.bat,双击运行:
@echo off
set CWEBP=C:\cwebp\cwebp.exe
set OPT=-q 80 -alpha_q 100 -mt
for /R "D:\webroot\wp-content\uploads" %%f in (*.jpg *.jpeg *.png) do (
if not exist "%%f.webp" (
"%CWEBP%" %OPT% "%%f" -o "%%f.webp" >nul 2>&1
echo ok %%~nxf
)
)
echo ===== 全部完成 =====
pause
参数说明
| 参数 | 作用 |
|---|---|
| -q 80 | 画质 80,肉眼几乎无差别,体积控制最佳。建议 75~85 之间 |
| -alpha_q 100 | 透明通道无损,避免 PNG 转完后边缘出现毛边 |
| -mt | 多线程,加快转换速度 |
| if not exist | 已转换的自动跳过,脚本可以重复运行,中断后重跑无副作用 |
安全性提示:脚本只新增 .webp 文件,原有的 jpg/png 一张都不会删除,随时可以删掉 webp 回滚。
注意:如果服务器装了防篡改类安全软件,需要先把图片目录加入例外名单,否则转换出来的文件写不进去。
四、配置 Apache 自动改写
这是最关键的一步。规则要放在图片所在目录的 .htaccess里。
<IfModule mod_mime.c>
AddType image/webp .webp
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# 条件1:浏览器支持 webp
RewriteCond %{HTTP_ACCEPT} image/webp
# 条件2:对应的 .webp 文件确实存在
RewriteCond %{REQUEST_FILENAME}.webp -f
# 满足则内部重写,并强制 MIME 为 image/webp
RewriteRule ^(.+\.(jpe?g|png))$ $1.webp [NC,T=image/webp,L]
</IfModule>
# 必须:同一 URL 可能返回不同格式,按 Accept 分开缓存
<IfModule mod_headers.c>
<FilesMatch "\.(jpe?g|png)$">
Header append Vary Accept
</FilesMatch>
</IfModule>
Vary: Accept 为什么不能省
这是最容易漏掉、后果又最隐蔽的一条。
同一个 URL /images/demo.jpg:
- 支持 webp 的浏览器访问 → 服务器返回 webp → 中途某层缓存记住"这个 URL = webp"
- 不支持 webp 的老浏览器访问 → 缓存直接给它 webp → 图片裂开
Vary: Accept 就是告诉所有缓存:这个 URL 返回什么取决于 Accept 头,请按 Accept 分开存。
兼容性说明
不支持 webp 的浏览器发的 Accept 头里没有 image/webp,条件 1 不满足,规则不执行,自动返回原图。这正是原图必须保留的原因 —— 它是降级方案。
五、验证:三条命令定生死
验证时必须模拟浏览器的 Accept 头,否则测不出真实效果。
curl -s -D - -o nul -H "Referer: https://www.example.com/" -H "Accept: image/webp,*/*" https://www.example.com/images/demo.jpg | findstr /i "content-type"
期望结果:
Content-Type: image/webp
再测降级(模拟不支持 webp 的老浏览器):
curl -s -D - -o nul -H "Referer: https://www.example.com/" -H "Accept: image/jpeg,*/*" https://www.example.com/images/demo.jpg | findstr /i "content-type"
期望结果:Content-Type: image/jpeg —— 说明自动降级正常。
六、三个真实踩坑记录
下面这三条是实操中真正卡住的地方,网上教程基本不会提。
坑一:防盗链导致 curl 测出假结果
如果站点开了防盗链,curl 不带 Referer 头时,服务器会返回 HTML 提示页,状态码还是 200,但 Content-Type 是 text/html。
现象:看起来"请求成功了",但返回的不是图片,让人误以为 WebP 没生效、白白折腾半天。
结论:测试图片务必带上 -H "Referer: https://你自己的域名/"。
坑二:子目录写了 RewriteEngine On,父目录规则不再继承
这是 Apache 的一个默认机制:per-directory 上下文中,子目录的 .htaccess 一旦写了 RewriteEngine On,父目录的 rewrite 规则在该子目录就不会生效(除非显式写 RewriteOptions Inherit)。
典型症状:把 WebP 规则写在网站根目录,图片目录里因为有其他规则(比如防盗链)写了 RewriteEngine On,结果根目录规则被"顶掉",规则明明写了却毫无效果。
解决办法(二选一):
- 在子目录的 rewrite 块里加一行
RewriteOptions Inherit - 或者干脆把 WebP 规则分别写进每个图片目录自己的 .htaccess(本文推荐,排查时不用到处找)
坑三:只生成了 .webp 文件,不等于改写生效
直接访问 demo.jpg.webp 能在浏览器里看到图,这只能证明文件生成成功了。
真正的成功标志是:访问 .jpg 原地址,返回的却是 image/webp。
这两件事完全不同,前者是转换,后者是改写,缺一不可。
七、以后上传新图片怎么办
WebP 是预先生成的文件,新上传的图片不会自动有 .webp。有三个方案:
方案 A:手动重跑脚本
每次批量传图后双击运行一次 convert.bat,if not exist 会跳过已转换的,只处理新图。最简单,但靠自觉。
方案 B:WordPress 自动生成(推荐)
在主题的 functions.php 末尾加入:
// 上传图片时自动生成 WebP
add_filter('wp_generate_attachment_metadata', 'auto_make_webp', 10, 2);
function auto_make_webp($metadata, $attachment_id) {
$file = get_attached_file($attachment_id);
if (!$file || !preg_match('/\.(jpe?g|png)$/i', $file)) return $metadata;
$webp = $file . '.webp';
if (file_exists($webp)) return $metadata;
@exec('C:\cwebp\cwebp.exe -q 80 -alpha_q 100 -mt '
. escapeshellarg($file) . ' -o ' . escapeshellarg($webp));
return $metadata;
}
上传一张转一张,永不遗忘。WordPress 生成的多个尺寸缩略图,这个钩子对每个尺寸都会触发。
方案 C:Windows 计划任务
设定每周自动跑一次 convert.bat,适合不常更新的站点。
八、收尾工作
- 确认网站正常:改完 .htaccess 立刻访问首页,返回 200 才对。如果是 500,说明有语法错误,马上恢复备份
- 清理缓存:缓存插件、Redis 各清一次,浏览器 Ctrl+F5 强制刷新
- 安全软件例外保留:图片目录的写入例外不要撤掉,以后还要传图、还要生成 webp
- 改 .htaccess 不需要重启 Apache,保存即时生效
总结
整套方案的核心价值在于零侵入:
- 文章里的图片链接一个字不用改
- 原图全部保留,随时可回滚
- 不支持 webp 的浏览器自动降级
- 搜索引擎爬虫拿到的是原图(它们通常不带 image/webp),SEO 完全不受影响
唯一需要动手的,就是转换文件 + 写一条 rewrite 规则。搞清楚原理之后,整个配置过程半小时以内可以完成。