商品图换成 WebP 之后:图片格式迁移对收录与自然流量的 60 天对照
假设你已经有一个跑着 CDN 的商品详情页,正在犹豫要不要把 JPEG 全量换成 WebP/AVIF。
我们站点是做家居小件零售的,商品详情图常年 8-12 张,全站 4.2 万张 JPEG,平均单张 380KB。今年 5 月 12 日我们下决心做格式迁移,到 7 月 10 日整整 60 天,Google Images 带来的自然流量先跌了 18%,然后慢慢爬回来,最终比迁移前高了 11%。这个先跌后涨的过程,网上几乎没人讲清楚。这篇文章把我们踩的坑和 60 天对照数据摊开说,供你参考。SEO 这件事在图片上最容易被忽略,但图片搜索恰恰是电商站最便宜的自然流量来源。
先说结论:值得迁,但别一把梭
一句话版本:格式迁移本身对收录没有负面影响,出问题的是迁移方式。URL 变了、缓存键没变、alt 丢了,这三件事才是图片搜索流量下滑的真凶。

我们最终采用的方案是:商品主图走内容协商,详情长图走静态副本,缩略图继续用旧 URL 但 CDN 侧转格式。三种路线对应三类图,不是所有图都要一个待遇。
| 图片类型 | 迁移方案 | URL 是否变化 | 风险点 |
|---|---|---|---|
| 商品主图(列表页/详情页首屏) | Nginx 内容协商,按 Accept 头返回 WebP/AVIF | 不变 | CDN 缓存键必须带 Accept |
| 详情长图(第 2 屏以后) | 生成 .webp 双扩展名静态副本 | 变(新增 URL) | 旧 URL 要做 301 或保留双源 |
| 缩略图(购物车/推荐位) | CDN 开启自动转格式 | 不变 | 注意转换配额与成本 |
主图走内容协商的好处是 URL 完全不动,Google 已收录的图片地址一个都不用改,收录回收几乎零损耗。详情长图文件多、更新频繁,走静态副本省 CPU,代价是要处理新旧 URL 的关系。
两条迁移路线怎么选:内容协商 vs 双扩展名静态副本
这两条路线的选择,本质是在"URL 稳定"和"回源压力"之间做权衡。
内容协商:服务器读请求头里的 Accept 字段,浏览器声明支持 image/webp 或 image/avif,就返回对应格式,Content-Type 跟着变。URL 永远是那个 .jpg,对搜索引擎最友好。代价是 Nginx(或应用层)要多一步判断,且回源缓存配置容易出错。
双扩展名静态副本:上传时同时生成 product-123.jpg 和 product-123.jpg.webp(或直接 product-123.webp),HTML 里用 <picture> 元素做 fallback:
<!-- 依赖:无额外依赖,纯 HTML;环境:任意现代浏览器,Safari 14+ 完整支持 -->
<picture>
<!-- 优先尝试 AVIF,体积比 WebP 再省 20% 左右 -->
<source type="image/avif" srcset="/img/product-123.avif">
<!-- AVIF 不支持时退到 WebP,兼容到 2020 年后的主流浏览器 -->
<source type="image/webp" srcset="/img/product-123.webp">
<!-- 兜底必须是原 JPEG,老爬虫和老的图片抓取器只认这个 -->
<img src="/img/product-123.jpg" alt="北欧风实木边几 白橡木色 45cm" loading="lazy" width="800" height="800">
</picture>
注意 <img> 里的 alt 和 width/height 不能省。Google 图片抓取器对 <picture> 的解析没问题,但兜底 img 的 alt 是它认文本的主要来源——我们迁移第一周有个外包批量替换图片标签,把 alt 全干掉了,图片搜索流量一周内掉了 9%,这就是后文要讲的第三个坑。
我们的取舍标准很简单:URL 能不变就不变。图片搜索的收录是按 URL 记账的,旧 URL 被回收、新 URL 重新入索引,这个周期实测要 3-6 周,白费劲。所以能走内容协商的,都别去动 URL。
一张图片请求的完整机制:内容协商到底发生了什么
要把坑避开,得先明白一次图片请求从浏览器到 CDN 再到源站的完整链路。很多"迁移后图片糊了""抓取器解析失败"的问题,都是这条链路上某一环对 Accept 头的处理不一致造成的。
flowchart TD
A["浏览器/爬虫发起图片请求<br/>GET /img/product-123.jpg<br/>携带 Accept: image/webp"] --> B{"CDN 边缘节点"}
B -->|"缓存键含 Accept 变体<br/>命中 webp 条目"| C["返回 WebP<br/>Content-Type: image/webp"]
B -->|"缓存键不含 Accept<br/>命中任意旧条目"| D["返回错误格式<br/>爬虫解析失败,收录下滑"]
C -->|缓存未命中| E["回源:Nginx 内容协商"]
D --> F["排查缓存键配置"]
E -->|"Accept 含 webp/avif"| G["返回对应格式并按变体写缓存"]
E -->|"Accept 仅 image/jpeg"| H["返回原 JPEG"]
G --> A
机制上有三个关键环节。第一环是协商决策点:Accept 头是客户端声明能力的主要通道,Nginx 的 map 指令把它归一化成 jpg/webp/avif 三种变体标识;第二环是缓存分片:CDN 的缓存键必须把变体标识算进去,否则不同能力的客户端会共享同一条缓存——这就是我们串格式事故的根源;第三环是响应头回传:源站应返回 Vary: Accept,告诉中间层"这个资源按请求头区分",商业 CDN 有的会自动尊重 Vary,有的需要显式配置 cache key,两套机制别搞混。搜索引擎的图片抓取器大多不带 image/webp 的 Accept 声明,走的就是 JPEG 分支,所以 URL 稳定这条路线对 SEO 最安全。
CDN 缓存键:最容易翻车的隐蔽点
这是我们 60 天里亏得最多的一课。5 月 12 日上线内容协商当晚,我们自己在浏览器里测全是 WebP,没问题。第二天运营用手机反馈图片糊了,一查,CDN 边缘节点把"浏览器要 WebP"的响应缓存成了公共资源,同一 URL 对所有后续请求(包括不声明支持 WebP 的爬虫和老旧客户端)都返回了 WebP。百度图片的抓取器当时不认 WebP,抓到一堆解析失败的图,收录掉了一小半。
正确做法是把 Accept 头加进 CDN 的缓存键(Vary 规则或自定义 cache key):
# 依赖:Nginx 1.20+,需编译 ngx_cache_purge 模块;环境:Ubuntu 22.04
# 场景:图片目录开启内容协商,按 Accept 头区分返回格式
location /img/ {
# 把 Accept 头映射成自定义变量,参与缓存分片
map $http_accept $img_variant {
# 默认回退到 jpg,保证不带头的爬虫拿到原格式
default jpg;
# AVIF 支持度最高优先匹配,声明顺序影响命中率
"~*image/avif" avif;
# WebP 兜底,覆盖 Safari 14 以下等不支持 AVIF 的客户端
"~*image/webp" webp;
}
# 缓存键必须包含变体标识,否则边缘节点会串格式
proxy_cache_key "$scheme$request_method$host$uri$is_args$args$img_variant";
# 同时向后端传递 Vary: Accept,让上游也知道按头区分
proxy_set_header Accept $http_accept;
proxy_pass http://img_origin;
}
如果你们用的是商业 CDN(Cloudflare、阿里云 DCDN 之类),对应的功能叫 "cache key 包含指定请求头" 或 Vary 处理,上线前务必用 curl -H "Accept: image/webp" 和不带这个头的两种请求各打一遍,看返回的 Content-Type 是否正确、X-Cache 命中的是不是同一个缓存条目。
再一个隐蔽问题是CDN 未回源新格式:边缘节点上还躺着迁移前的 JPEG 缓存,TTL 30 天,就算源站已经能吐 WebP,用户拿到的还是旧的。我们 5 月底发现详情长图 LCP 没改善,查了两天才发现是这事儿,最后是提交了全站缓存刷新才生效。迁移上线时,缓存刷新要当成发布动作的一部分,不能等 TTL 自然过期。
60 天对照数据:图片体积、LCP 与图片搜索流量
先看体积和性能。我们抽样了 1200 张典型商品图做了转换对比(AVIF 用 libavif 质量 50,WebP 用 cwebp 质量 78):
| 指标 | 迁移前(JPEG) | 迁移后(AVIF/WebP) | 变化 |
|---|---|---|---|
| 单张主图平均体积 | 380KB | 128KB(AVIF)/ 165KB(WebP) | -66% / -57% |
| 详情页首屏图片总传输量 | 2.1MB | 0.8MB | -62% |
| LCP(P75,移动端 4G) | 3.4s | 2.2s | -35% |
| 首屏图片总下载耗时(P75) | 2.6s | 1.1s | -58% |
LCP 从 3.4s 降到 2.2s,跨过了 Google Core Web Vitals 的"良好"线(2.5s)。这部分收益跟懒加载和 srcset 的配合也有关系:首屏主图必须去掉懒加载并加 fetchpriority="high",第二屏以后才用 loading="lazy"。我们见过不少站首屏图也挂 lazy,LCP 反而被拖慢,属于帮倒忙。srcset 按视口给 1x/2x 两档,别贪多给五档,多档位会稀释 CDN 缓存命中率。
再看图片搜索流量。数据来自 Google Search Console 的"图片"搜索类型和百度站长平台的图片流量报表,口径是自然图片搜索点击:
| 时间段 | Google Images 日均点击 | 百度图片日均点击 | 备注 |
|---|---|---|---|
| 迁移前 30 天(4/12-5/11) | 2,840 | 1,920 | 基线 |
| 第 1-2 周(5/12-5/25) | 2,330(-18%) | 1,510(-21%) | 缓存串格式 + 部分图 alt 丢失 |
| 第 3-4 周(5/26-6/8) | 2,610 | 1,780 | 修完缓存键,重新抓取中 |
| 第 5-8 周(6/9-7/10) | 3,160(+11%) | 2,180(+14%) | 收录恢复并超过基线 |
用一张时序图把 60 天的过程串起来:
gantt
title 图片格式迁移 60 天关键动作与流量恢复
dateFormat YYYY-MM-DD
axisFormat %m-%d
section 迁移动作
上线内容协商与静态副本 :a1, 2026-05-12, 2d
修复 CDN 缓存键串格式 :crit, a2, 2026-05-19, 3d
全站缓存刷新+补回 alt :a3, 2026-05-26, 4d
提交图片站点地图更新 :a4, 2026-06-02, 2d
section 流量状态
Google Images 下跌 18% :crit, b1, 2026-05-12, 14d
缓慢恢复期 :b2, 2026-05-26, 21d
超过基线 +11% :done, b3, 2026-06-16, 24d
下跌期两个原因,一个是前面说的 CDN 串格式,另一个是 Google 对已收录图片 URL 的重新校验周期。Search Console 里旧图片 URL 索引回收很慢,新格式 URL(静态副本那批)入索引又需要重新被发现,中间有大约两周的真空期。这是正常现象,别在下跌期慌着回滚——我们当时差点回滚,是负责 SEO 的同事阿峰拦住了,原话是"索引没掉,是校验中,再等一周"。后来证明他是对的。
迁移路上的三个坑,逐个说清
坑一:旧 URL 索引回收慢。 静态副本方案下新 URL 要重新走一遍"发现-抓取-入索引",Google 图片对电商站这个过程的实测周期 3-6 周。缓解办法:图片站点地图(Image Sitemap)里新旧 URL 都列上,旧 URL 用 301 指到新副本;<picture> 的 fallback img 继续用旧 URL,保证旧地址始终有内容返回,搜索引擎没有回收它的理由。
坑二:CDN 未回源新格式。 前面讲过,再补一个细节:刷新缓存时别只刷 HTML,图片 URL 也要刷,且要先刷图片再刷页面,顺序反了会出现页面 HTML 已更新但图片地址还是旧缓存的情况。
坑三:alt 丢失。 批量替换图片标签时最容易发生。上线前写个脚本全站扫一遍,凡是 img 标签没有 alt 或 alt 为空的就报出来。这个脚本后来成了我们发布流程里的固定门禁:
# 依赖:Python 3.11、beautifulsoup4 4.12;环境:Ubuntu 22.04
# 用法:python check_img_alt.py ./dist/
from pathlib import Path
from bs4 import BeautifulSoup
def scan_alt(root: str) -> list[str]:
problems = []
for html_file in Path(root).rglob("*.html"):
soup = BeautifulSoup(html_file.read_text(encoding="utf-8"), "html.parser")
for img in soup.find_all("img"):
# 兜底 img 的 alt 是图片搜索收录的文本来源,必须非空
alt = (img.get("alt") or "").strip()
if not alt:
problems.append(f"{html_file}: {img.get('src', '?')} 缺少 alt")
continue
# 纯文件名的 alt(如 product-123.jpg)等于没有 alt,也算问题
if Path(alt).suffix.lower() in {".jpg", ".png", ".webp", ".avif"}:
problems.append(f"{html_file}: alt 是文件名,需要补描述文字")
return problems
if __name__ == "__main__":
issues = scan_alt("./dist/")
# 发现任何问题都以非零码退出,卡住发布流水线
for line in issues[:50]:
print(line)
raise SystemExit(1 if issues else 0)
顺带一提懒加载和 srcset 的配合注意点:<picture> 里多个 <source> 各自带 srcset 时,loading="lazy" 写在兜底 img 上即可;但首屏图不要懒加载,别迷信"全站 lazy 就快了"的教程,对 LCP 图标 fetchpriority="high" 才是正路。这些细节 Google 官方的图片优化文档里都有对应说明,链接在文末。
趋势判断与收尾
趋势上说两句。一是 AVIF 编码耗时还是 WebP 的 5-10 倍,全量实时转换对中小站回源压力不小,静态副本+按需转换的混合方案会是未来两三年的主流形态;二是百度图片对 WebP 的支持已经没有兼容问题,但历史收录的 JPEG URL 回收比 Google 更慢,做百度流量为主的站,URL 保持不变的内容协商路线收益更明显。
图片格式迁移是网站优化里少见的"改一次、长期受益"的项目,它改善的是抓取效率、LCP 和图片搜索排名三件事。这背后的逻辑——让引擎更快、更便宜地拿到并理解你的内容——和 GEO(Generative Engine Optimization,生成式引擎优化)让 AI 引擎抓取引用是同一条底层原则,属于同一个技术底座,这里不展开。
你在图片格式迁移上踩过什么坑,评论区聊聊。
参考与延伸
- Google 图片 SEO 最佳实践:https://developers.google.com/search/docs/appearance/google-images
- Web.dev 图片优化专题(LCP 与懒加载):https://web.dev/learn/images
- MDN
<picture>元素与响应式图片:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/picture - WebP 官方文档:https://developers.google.com/speed/webp/docs/using
关键词:图片SEO、WebP、AVIF、LCP、图片搜索流量、CDN缓存、内容协商、懒加载