产品图 AI 看不懂细节:ImageObject 与 alt 写法差异的 45 天引用对照

2026-09-18 10:10:21 19 次浏览
ImageObject图片 sitemap多模态抓取结构化数据图片SEO

适用读者:负责外贸独立站产品页的前端与 SEO 工程师,需要在多语言目录站上落地图片结构化数据的维护者,以及正在被「AI 回答不引用自家产品图」这个问题困住的运营技术负责人。

上个月初,我们在客户会议室做了一轮人工提问测试:把一台数控设备的产品页链接分别丢给三个常用的 AI 引擎,问「这台设备有几个气动接口」。三份回答都把正文第一段的参数描述换了个说法复述了一遍,图片里铭牌上印着的接口数量,一份都没提到。

那张图拍得并不糊,铭牌数字肉眼可辨。文件名叫 IMG_2841.jpgalt 写的是「产品图」两个字。图片对引擎来说等于不存在。

现场:图片信号断在哪一环

排查用了两天,顺序比工具重要。第一步是 curl 把页面原始 HTML 拉下来看 <img> 标签,抓取器拿到的是未执行 JavaScript 的那一版,而这家站用了一个懒加载脚本,真实地址塞在 data-src 里,src 是一个 1×1 的占位 GIF。第二步是 Search Console 的图片报告,1260 张主图里有展示数据的不到 200 张,而且集中在类目页的那几张 banner 上。第三步是把所有 alt 抄出来做文本量统计,这一步得出的数字最扎眼。

那家站当时的状态是这样的:420 个产品页,每页 3 张主图,共 1260 张。文件名全部是相机默认的 IMG_xxxx.jpg,其中 300 多张还是从微信导出、带 mmexport 前缀的。alt 只有三种写法——「产品图」、「图1」、空字符串,前两种各占一半左右。页脚和证书栏还有 200 多张装饰图,alt 全空。图片没有进 sitemap,产品页的 JSON-LD 只声明了 Productimage 字段直接丢了一个 URL 进去,没有 ImageObject 节点。

这一层用户看得见,引擎看不见;引擎看不见的图片,在任何参数类回答里都不会被引用。 改造方案我们分了三档,每档 420 张,分组时保证三个组的图片尺寸分布、体积分布、来源类目接近,否则对照结果会被人为因素带偏。A 档从第 1 天放到第 15 天,B 档第 16 到 30 天,C 档第 31 到 45 天,三档的观测窗口同步推进到第 45 天结束,这样每一档都有至少 14 天的后续发酵时间。 ## 三档图片写法与 AI 表现的对照 写法的差别集中在三个位置:文件名与 `alt`、图片周边的可见文本、机器可读的结构化声明。B 档只动前两个位置,C 档把第三个补上,A 档什么都不动,作为基线。 | 档位 | 文件名与 `alt` 写法 | 附加信号 | 引擎可读文本量(约) | 参数类提问表现 | | --- | --- | --- | --- | --- | | A 档(420 张) | `IMG_2841.jpg`,`alt="产品图"` | 无 caption、无 sitemap、无 ImageObject | 5 字符,且不含任何实体词 | 60 次提问里 0 次带图,图片仅作正文图片块展示 | | B 档(420 张) | 文件名改成语义 slug,`alt` 写成 25–60 字描述句,含主体、属性、场景 | `
` 写 1 句补充说明,仍未进 sitemap | 30–70 字符,含 1 个实体词与 1 个数值 | 60 次提问里 11 次带图,数值类问题仍以正文为准 | | C 档(420 张) | 同 B 档,另把图片写进图片 sitemap,页面 JSON-LD 增加 `ImageObject` 节点 | sitemap 的 `image:caption`、`image:title`,JSON-LD 的 `caption`、`representativeOfPage` | 90–160 字符,且文本在 sitemap 与 JSON-LD 中重复出现 | 60 次提问里 40 次带图,铭牌数值被单独读出 9 次 | B 档的 `alt` 具体长这样:`八口气动接口的铝合金分配器,正面铭牌标注 8×G1/4,用于自动化产线的末端执行器`。一句话里包含了主体(铝合金分配器)、属性(八口、G1/4)、场景(自动化产线末端执行器),但没有堆关键词。这一点后面会单独说,堆关键词的写法在测试里反而是负收益。 C 档因为是多语言站,`caption` 在英文、西语、德语三个语言版本里各写了一遍,中文原版的 `alt` 保留中文。图片 sitemap 按语言拆成三个文件,`xhtml:link` 互相指向,避免三个语言版本的图被判成重复内容。 ## 45 天分批改造与关键指标 观测口径提前定死了,避免事后扯皮。「图片被发现率」指 Search Console 图片报告中产生过展示的图片数除以该档图片总数。「带图引用比例」指回答里出现该产品图缩略图或图片链接的次数除以提问次数,只在正文重复文字、图片没出现的,一律不算。每周固定 20 个参数类问题,问题模板覆盖接口数量、额定电压、整机重量、外壳材质、安装孔距五类,每类四个问法变体。 | 关键指标 | A 档(仅文件名,无 `alt`) | B 档(描述性 `alt` + caption) | C 档(`alt` + caption + 图片 sitemap + ImageObject) | | --- | --- | --- | --- | | 图片被发现率 | 9% → 11% | 14% → 63% | 17% → 89% | | 带图引用比例 | 0% → 0% | 2% → 19% | 3% → 67% | | 45 天图片搜索点击 | 41 → 46 | 58 → 412 | 63 → 1036 | | 首次出现带图引用 | 全程未出现 | 第 11 天 | 第 6 天 | | 平均被抓取间隔 | 21 天 | 9 天 | 4 天 | 表格里的箭头是「改造前 30 天均值 → 改造后 45 天均值」。A 档的两次数字都在涨,这是站点整体抓取频次上升带来的自然增长,不是写法的功劳,这一点必须说清楚,否则容易把权重算错。C 档的第 6 天出现首次带图引用,那天我们只推了 60 张图,图片 sitemap 是分三次提交的。 真正值钱的变化在最后一行。**被抓取间隔从 21 天压到 4 天,意味着图片 sitemap 提交给了引擎一个稳定的轮询入口,而不是靠它在正文里偶遇那张图。** 那个站有 1260 张图,抓取预算本来就紧,sitemap 相当于把「哪些图值得抓」这个问题替引擎回答了。 ## 原理剖析:多模态抓取与图像文本融合 多模态融合(Multimodal Fusion)这一步是整个链路的关节。图片对抓取器来说是一段二进制,必须先变成文本才能参与检索和引用。变文本有两条路:一条是视觉语言模型(Vision-Language Model)读图生成的机器描述,另一条是从 HTML 里抽出来的作者声明文本。
flowchart TD
    A[抓取器拉取 HTML] --> B[解析 img 与 picture 节点]
    B --> C{真实图片地址是否可达}
    C -- 否 --> D[记录 404 或占位图, 该图退出链路]
    C -- 是 --> E[下载图片二进制]
    E --> F[视觉语言模型读图]
    F --> G[生成机器描述: 主体 属性 可见文字]
    B --> H[抽取 alt / caption / 文件名 / 周边正文]
    G --> I[多模态融合 Multimodal Fusion]
    H --> I
    I --> J{两路信号是否冲突}
    J -- 冲突 --> K[作者声明优先, 机器描述降级为补充信号]
    J -- 一致 --> L[合并为图片文本表示并加权]
    J -- 作者声明为空 --> M[机器描述升为主信号]
    K --> N[与页面正文段落做语义对齐]
    L --> N
    M --> N
    N --> O{与提问意图是否重叠}
    O -- 是 --> P[进入引用候选池]
    O -- 否 --> Q[仅入库, 不参与引用]
**冲突时作者声明优先。** 逻辑不复杂:`alt` 和 `caption` 是作者有意写下的,意图明确;机器描述是概率输出,铭牌上的小号数字、单位符号、易混字母(0 和 O、1 和 l)都容易读错。引擎宁可信一个静态字符串,也不愿把读错的数字写进回答里,因为参数类回答的容错率极低。 但这条优先级有个前提——作者声明得含有信息量。A 档的 `alt="产品图"` 在融合阶段几乎被当成空值处理,此时机器描述会顶上,整张图的文本表示全部来自模型。C 档不一样,`alt`、`caption`、sitemap 的 `image:caption`、JSON-LD 的 `caption` 四处文本互相印证,模型描述只负责补充「铭牌在画面左下角」这类位置信息,两路信号叠起来,图片文本表示的密度才够参数类提问去匹配。
flowchart LR
    A[扫描图片 sitemap 的 image:loc] --> B{HTTP 200 且 MIME 为图片}
    B -- 否 --> C[标记失效并写入修复清单]
    B -- 是 --> D{alt 长度是否在 15 到 125 字符之间}
    D -- 否 --> E[打回重写, 记录原值]
    D -- 是 --> F{alt 与 caption 是否高度重复}
    F -- 重复 --> E
    F -- 否 --> G{图片 URL 是否可被索引}
    G -- 否 --> H[移出图片 sitemap 并检查 robots]
    G -- 是 --> I{尺寸与体积是否达标}
    I -- 否 --> J[转码压缩后更新 image:loc]
    I -- 是 --> K[保留并进入下一轮对照窗口]
第二张图是我们每周跑一次的校验流程,落成了脚本,下面给出实现。 ## 图片 sitemap 生成与 `alt` 校验脚本 环境与依赖:Python 3.10 及以上,仅使用标准库(`xml.etree.ElementTree`、`urllib.request`、`csv`、`re`、`pathlib`)。如果站点图片量超过十万张,把解析部分换成 `lxml.etree` 会快 3 到 5 倍,接口基本一致,需要注意 `lxml` 对命名空间的写法更严格,`ElementTree` 里可用的 `{http://www.sitemaps.org/schemas/sitemap/0.9}url` 这种花括号语法两边都支持。
# -*- coding: utf-8 -*-
# 批量生成图片 sitemap,并对每张图的 alt 做合规校验
# 依赖:Python 3.10+,仅标准库
import csv
import re
from pathlib import Path
# 只用标准库发探测请求,避免为了一个校验脚本引入额外依赖
from urllib.request import Request, urlopen
from xml.etree import ElementTree as ET

# 图片 sitemap 的命名空间,图片扩展必须挂在 url 节点下
NS_SM = "http://www.sitemaps.org/schemas/sitemap/0.9"
NS_IMAGE = "http://www.google.com/schemas/sitemap-image/1.1"
NS_XHTML = "http://www.w3.org/1999/xhtml"
# 注册前缀,否则 ElementTree 会写出一串 ns0 这样的自动前缀
ET.register_namespace("", NS_SM)
ET.register_namespace("image", NS_IMAGE)
ET.register_namespace("xhtml", NS_XHTML)

# alt 与 caption 的长度区间,短于下限信息量不足,长于上限会被截断
ALT_MIN, ALT_MAX = 15, 125

# 零信息量写法,命中即判不合格
EMPTY_ALT = {"产品图", "图片", "img", "image", "photo", "图1", "banner", ""}

# 单张图的行结构:页面地址、图片地址、alt、caption、语言
CSV_PATH = Path("images.csv")
# 生成物统一落到 out 目录,方便整目录提交给引擎做校验
OUT_DIR = Path("out")


def validate_alt(alt: str, caption: str) -> list[str]:
    """返回问题列表,空列表表示这张图的 alt 通过校验。"""
    # 一条图可能同时踩多个坑,所以用列表累积而不是提前返回
    problems: list[str] = []
    # 归一化后比对零信息量写法,避免全角空格和大小写造成漏判
    norm = alt.strip().lower()
    # 零信息量 alt 直接退出,后面的长度与重复判断对它没有意义
    if norm in EMPTY_ALT:
        problems.append("alt 无信息量")
        return problems
    # 长度下限防「分配器」这种单词,上限防整段说明被截断
    if not (ALT_MIN <= len(alt.strip()) <= ALT_MAX):
        problems.append(f"alt 长度 {len(alt.strip())} 不在 {ALT_MIN}-{ALT_MAX}")
    # alt 与 caption 完全一致时,两路信号退化成一路,融合阶段拿不到互证
    if caption and alt.strip() == caption.strip():
        problems.append("alt 与 caption 完全重复")
    # 逗号分隔的关键词堆砌特征:逗号多于 3 个且没有完整句式
    if alt.count(",") + alt.count(",") > 3 and "的" not in alt:
        problems.append("疑似关键词堆砌")
    # 单位符号缺失会让数值类问题无法对齐,例如只写 8 没写 G1/4
    if re.search(r"\d", alt) and not re.search(r"(mm|cm|kg|V|A|G\d|/4|寸|口)", alt):
        problems.append("含数值但缺少单位")
    # 返回空列表表示通过,调用方用 if problems 判断即可
    return problems


def http_head_ok(url: str, timeout: int = 8) -> bool:
    """用 HEAD 探测图片是否可达,失败时不抛异常,交给调用方记账。"""
    try:
        # 带上自定义 UA,部分 CDN 会直接拒绝空 UA 的探测请求
        req = Request(url, method="HEAD", headers={"User-Agent": "img-sitemap-check/1.0"})
        with urlopen(req, timeout=timeout) as resp:
            # 只看状态码不够,还要确认 MIME 是图片,有些错误页也返回 200
            ctype = resp.headers.get("Content-Type", "")
            return resp.status == 200 and ctype.startswith("image/")
    # 超时、证书错误、DNS 失败统一按不可达处理,报表里再人工看
    except Exception:
        return False


def build_sitemap(rows: list[dict], lang: str) -> ET.Element:
    """按语言生成一份 urlset,一条 url 下挂一张或多个 image 节点。"""
    # 同一张图在三个语言版本里的地址不同,alt_url 里存了其余两份地址
    # 根节点走 sitemaps 命名空间,图片节点走 image 扩展命名空间
    urlset = ET.Element(f"{{{NS_SM}}}urlset")
    for row in rows:
        # 每个语言版本单独一份文件,行数据在这里按 lang 过滤
        if row["lang"] != lang:
            continue
        url_el = ET.SubElement(urlset, f"{{{NS_SM}}}url")
        ET.SubElement(url_el, f"{{{NS_SM}}}loc").text = row["page_url"]
        # image:loc 必须写成带协议头与域名的完整 URL,相对路径会被引擎丢弃
        img_el = ET.SubElement(url_el, f"{{{NS_IMAGE}}}image")
        ET.SubElement(img_el, f"{{{NS_IMAGE}}}loc").text = row["image_url"]
        # title 用于图片搜索的标题展示,caption 用于语义融合,两者不要写成同一句
        ET.SubElement(img_el, f"{{{NS_IMAGE}}}title").text = row["title"]
        ET.SubElement(img_el, f"{{{NS_IMAGE}}}caption").text = row["caption"]
        # 多语言站用 xhtml:link 互指,避免三个语言版本被判重复
        for other in ("zh", "en", "es"):
            link = ET.SubElement(img_el, f"{{{NS_XHTML}}}link")
            # rel 固定为 alternate,hreflang 写目标语言代码
            link.set("rel", "alternate")
            link.set("hreflang", other)
            # 同语言指向自身,这是协议要求而不是笔误
            link.set("href", row["image_url"] if other == lang else row["alt_url"][other])
    return urlset


def main() -> None:
    # 输入是一张平铺表:页面地址、图片地址、alt、caption、语言
    rows = list(csv.DictReader(CSV_PATH.open(encoding="utf-8")))
    # report 三列与输出的 csv 表头一一对应,改列序时两处要一起改
    report: list[tuple[str, str, str]] = []
    for row in rows:
        # 可达性放在最前面,失效图不必再看文本质量
        if not http_head_ok(row["image_url"]):
            report.append((row["image_url"], "不可达", row["alt"]))
            continue
        # 每张图的所有问题都写进报表,修图的人只打开一个文件就够
        for problem in validate_alt(row["alt"], row["caption"]):
            report.append((row["image_url"], problem, row["alt"]))
    # 输出目录不存在时自动创建,定时任务里不用额外加一步 mkdir
    OUT_DIR.mkdir(exist_ok=True)
    # 三份 sitemap 分开输出,各自提交,便于单独观察抓取反馈
    for lang in ("zh", "en", "es"):
        tree = ET.ElementTree(build_sitemap(rows, lang))
        # 缩进只为人工核对,引擎不依赖格式
        ET.indent(tree, space="  ")
        # 一份文件超过 5 万条要拆包并配 sitemap 索引,这里先按语言拆
        # xml_declaration 必须打开,缺少声明时部分校验工具直接判错
        tree.write(OUT_DIR / f"sitemap-images-{lang}.xml", encoding="utf-8", xml_declaration=True)
    with (OUT_DIR / "alt-report.csv").open("w", encoding="utf-8", newline="") as fh:
        writer = csv.writer(fh)
        # 表头固定三列,直接丢给内容团队当工单用
        writer.writerow(["image_url", "problem", "alt"])
        writer.writerows(report)
    # 打印一行汇总,方便塞进 CI 的日志里看趋势
    print(f"checked={len(rows)} problems={len(report)}")


if __name__ == "__main__":
    # 入口保持极简,方便挂到定时任务或 CI 的图片合规检查步骤里
    main()
页面上的 `ImageObject` 声明放在产品页原有的 `Product` 节点旁边,两者用 `image` 字段建立引用关系。下面这段为便于阅读保留了 `//` 注释行,部署前删掉注释行即可得到合法 JSON。
// 放在产品页 <head> 内的 application/ld+json 脚本里
// 与 Product 节点同处一个 @graph,用 image 字段互相指向
{
  "@context": "https://schema.org",
  "@type": "ImageObject",
  // @id 用带域名的完整 URL 且全站不重复,方便 Product 用 image 字段指过来
  "@id": "https://example.com/products/pneumatic-manifold-8port#primaryimage",
  // contentUrl 指向可直接访问的原图,不要给懒加载占位图
  "contentUrl": "https://example.com/media/pneumatic-manifold-8port-800.jpg",
  // 缩略图用于列表与回答卡片,宽高比与主图保持一致
  "thumbnailUrl": "https://example.com/media/pneumatic-manifold-8port-320.jpg",
  "name": "铝合金八口气动分配器正面铭牌",
  // caption 与页面 figcaption 的文本保持一字不差,融合阶段会被判定为互证
  "caption": "八口气动接口的铝合金分配器,正面铭牌标注 8×G1/4,用于自动化产线的末端执行器",
  // description 用来复述图上可见文字,给读图模型留一条校验路径
  "description": "铭牌位于画面左下角,标注型号与八口接口规格",
  // 声明这是该页面的代表图,参数类回答优先取用这一张
  "representativeOfPage": true,
  // 宽高写成 QuantitativeValue,比裸数字更容易被尺寸校验流程读到
  "width": { "@type": "QuantitativeValue", "value": 800, "unitCode": "E37" },
  "height": { "@type": "QuantitativeValue", "value": 800, "unitCode": "E37" },
  // encodingFormat 写真实格式,webp 与 jpeg 混用时要逐图声明
  "encodingFormat": "image/jpeg",
  "creditText": "产品实拍",
  // 图片本身的许可声明,缺失时部分引擎会拒绝在回答中直接展示
  "license": "https://example.com/license/photo-usage",
  // 与 Product 节点建立弱关联,帮助引擎把图归到正确的实体名下
  "about": { "@type": "Product", "@id": "https://example.com/products/pneumatic-manifold-8port#product" }
}
## 误区澄清与接下来十二个月的判断 第一个误区是把 `alt` 当关键词堆场。测试初期我们让一个子目录试过 `气动接口 气动接口厂家 气动接口价格 气动分配器`这种写法,30 天里带图引用是 0 次,比描写细腻的 B 档还差。逗号堆词会把 `alt` 从描述句降级成标签集合,融合阶段拿不到句式结构,与正文的语义对齐反而变难。 第二个误区是觉得文件名改了就够。文件名在候选里权重排得靠后,它主要用于 URL 可读性与相近图片的区分。B 档的收益主要来自描述句而不是 slug,这一点我们做过拆分验证:只改文件名、`alt` 保持「产品图」的那 60 张,30 天里带图引用仍然是 0。 第三个误区是只交 sitemap 不做 `ImageObject`。两件事管的不是同一个环节,图片 sitemap 解决「这张图存在且值得抓」,`ImageObject` 解决「这张图是什么、属于哪个实体、能不能作为该页面的代表图」。C 档里单独抽 60 张只提交 sitemap、不加 JSON-LD 的图做对照,带图引用比例停在中位数附近,明显低于同批次加了两者的图。 接下来一年可以预判的方向是引用粒度下沉。现在的带图引用基本停在「回答里放一张产品图」,下一步会走到「回答直接读出铭牌上的具体数值并标注来自哪张图」。要提前准备的事情很具体:让图片结构化字段里的数值与正文表格里的数值严格一致,把铭牌、标签、面板这类「图上有数字」的素材单独标为一组并优先补 `caption`,同时把图上可见文字在 `description` 里用文字复述一遍,给读图模型留一条校验路径。 工程上最省事的落地顺序是:先修懒加载,让真实图片地址出现在原始 HTML 里;再批量改写 `alt`,长度按 15 到 125 字符控;然后生成按语言拆分的图片 sitemap 并提交;最后补 `ImageObject` 节点,用 `@id` 把图片和 `Product` 绑起来。四件事的顺序不要颠倒,前一件没做完就做后一件,观测数据会被污染,45 天的对照就白跑了。脚本和对照口径都在上面,跑完第一轮拿到 report.csv 的同学,可以在评论区贴一下自己站的 `alt` 不合格率,我们对一下量级。 ## 参考与延伸 - [schema.org ImageObject](https://schema.org/ImageObject):`contentUrl`、`caption`、`representativeOfPage` 等属性的权威定义 - [Sitemaps 协议图片扩展](https://www.sitemaps.org/protocol.html#image):`image:loc`、`image:caption`、`image:title` 的官方说明与示例 - [Google 图片 SEO 最佳实践](https://developers.google.com/search/docs/appearance/google-images):图片发现、索引与展示的官方指引 - [Google 图片 sitemap 文档](https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps):图片 sitemap 的构建要求与常见错误 *关键词:ImageObject, alt 文本, 图片 sitemap, 多模态抓取, 结构化数据, 生成式引擎优化, AI优化AIO*
🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。