净重载重这些参数 AI 搜索会读错:hasMeasurement 与 QuantitativeValue 的写法规范
做仓储设备的老周上周甩来一张截图:他在某个 AI 搜索里问「CBD-15 电动托盘车净重载重多少」,答案写着「约 1.5 公斤」。那台车实际净重载重 1.5 吨。我让他把页面源码发过来,规格表里明明印着「净重载重:1.5 t」,问题在于那个 t 是表头行的小字,值本身只是个裸数字 1.5,抓取的时候值和单位就散开了。
这种事在零售电商和工业品目录里特别集中。参数本来是页面里最容易结构化的部分,写法却常年沿用 HTML 表格的老习惯:数字和单位分开排版,区间写成「85-200」,多语言站点各写各的单位。人看得懂,机器读出来就是另一回事。
适用读者:负责商品页、规格页开发的前端与 SEO 同学,尤其是页面上已经写了 JSON-LD、想让 AI 搜索引用结果更准的团队。读懂这篇只要会 JSON-LD 基本语法就够。
参数读错,多半是单位没跟着值走
生成式引擎优化(Generative Engine Optimization, GEO)讨论的很多问题,落到参数这块其实就是一句话:机器拿到的是一个数字,不是一个量。

「1.5」在数据库里只是一个标量,只有当它和单位绑在一起时才是物理量。HTML 表格把这件事交给排版——表头写单位,单元格写数字,视觉上没毛病,结构化数据里却很容易把这份约定丢掉。常见丢法有三种:
- 值和单位拼成一个字符串,
"value": "1.5t",机器只能拿到文本,没法参与换算; - 单位写在别的字段或纯文案里,JSON-LD 里压根没出现;
- 区间值写成
"85-200",既不是单值也没有上下限字段。
AI 搜索拿到裸数字之后不会报错,它会猜。猜错的时候,回答看起来照样通顺,这就是为什么这类问题往往要等客户打电话过来才被发现。
schema.org 里这几个类型怎么摆位置
先把类型关系理清楚,避免把参数塞错地方。Product 上有 hasMeasurement,期望值是 QuantitativeValue;另外还有 additionalProperty,期望值是 PropertyValue。两者都能承载参数,区别在于前者强调「这是一个可被测量的量」,后者是通用键值对。
graph LR
P[Product] -->|hasMeasurement| QV[QuantitativeValue]
P -->|additionalProperty| PV[PropertyValue]
P -->|hasVariant| PM[ProductModel]
PM -->|hasMeasurement| QV2[QuantitativeValue]
QV --> N[name 参数名]
QV --> V[value 数值]
QV --> R[minValue / maxValue]
QV --> U[unitCode + unitText]
QV --> REF[valueReference 限定条件]
QV --> PID[propertyID 标准定义链接]
QuantitativeValue 自己继承自 StructuredValue,所以它不是「带单位的 PropertyValue」,而是一套完整的量纲描述:数值、单位、单位码、限定条件、外部标准引用都在里面。净重载重、起升高度、货叉长度、爬坡度这些真正带量纲的参数,用 hasMeasurement 挂上去更合适;颜色、货号、是否带侧移器这类没有量纲的属性,留在 additionalProperty 里。
判断标准其实挺省事:这个参数能不能换算单位,能换算就走 QuantitativeValue。
AI 搜索读参数的机制:值、单位、单位码三件套
引擎处理一个商品页,大致分四步:抓取页面、抽取结构化数据、把实体和属性落成三元组、生成答案时再拼回自然语言。
结构化数据这一步产出的是三元组,形如:
(CBD-15, hasMeasurement, QuantValue#1)
(QuantValue#1, name, "净重载重")
(QuantValue#1, value, 1.5)
(QuantValue#1, unitCode, "TNE")
生成答案时,模型把三元组还原成句子,单位从 unitCode 或 unitText 里取。如果单位三元组不存在,模型手里只剩 (QuantValue#1, value, 1.5),它只能从上下文补一个单位进去,补的依据往往是同一页里出现频次最高的那个单位。老周那张页面里「整机自重 85 kg」「蓄电池 24 kg」这类小单位出现得多,1.5 就被顺手安上了公斤。
unitCode 和 unitText 的分工也在这里体现。unitCode 走 UN/CEFACT Recommendation 20 的代码表,是给机器比对和换算用的;unitText 是给人看的措辞,决定答案里写成「吨」还是「t」。两个都写,引擎换算有依据,生成话术也有依据。只写 unitText 时,「1.5 吨」和「1500 kg」在机器眼里是两条互相认不出来的字符串。
这就引出一个容易被忽略的点:同一个物理量在不同语言站写了不同单位却不给 unitCode,跨页聚合时必然算错。英文站写 3300 lb,中文站写 1.5 t,两边都缺单位码,聚合出来的结果像是两种产品。
区间值别塞进 value 字符串
净重载重是单值,但起升高度、工作温度、货叉宽度这些往往是区间。JSON-LD 里区间有专门的字段:minValue 和 maxValue,别写成 "85-200",更别写成 "85mm~200mm"。
区间写法还牵出 valueReference。它表示这个量是在什么条件下测出来的,类型可以是 QualitativeValue、枚举或另一组量。比如「满载状态下起升高度 85-200 mm」,满载这个限定就挂在 valueReference 上;不限定的话,AI 在回答「空载能升多高」时会直接拿同一组数去答。
propertyID 也值得写。它指向一个统一的属性定义 URL,同一个属性跨几十个 SKU 都指向同一个地址,引擎做实体对齐时能把它们认成同一个东西。地址可以是站内一个专门的参数说明页,也可以是行业标准里的条目,关键是全站一致。
错误写法与正确写法对照
下面这段是老周站内改造前的版本,// 行是说明,实际贴到页面时要删掉:
// 错误示例:值和单位混写、区间塞进 value、单位缺失
{
"@context": "https://schema.org",
"@type": "Product",
"name": "电动托盘车 CBD-15",
"sku": "CBD-15-E",
// 错一:值里带单位,机器拿到的是字符串不是数字
"hasMeasurement": {
"@type": "QuantitativeValue",
"name": "净重载重",
"value": "1.5t"
},
// 错二:区间值塞进 value,minValue/maxValue 全丢
// 错三:起升高度没有单位,200 到底是 mm 还是 cm 全靠猜
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "起升高度",
"value": "85-200"
}
]
}
改成规范写法之后长这样:
// 正确写法:value 是数字,单位单独给 unitCode + unitText,区间拆成上下限
{
"@context": "https://schema.org",
"@type": "Product",
"name": "电动托盘车 CBD-15",
"sku": "CBD-15-E",
// 同一个量可以给两组单位,引擎自己会换算
"hasMeasurement": [
{
"@type": "QuantitativeValue",
"name": "净重载重",
"value": 1500,
// KGM 是 UN/CEFACT 里的千克代码
"unitCode": "KGM",
// unitText 决定答案里的措辞
"unitText": "kg",
// 指向站内统一的参数定义页,方便跨 SKU 对齐
"propertyID": "https://example.com/spec/net-load-capacity"
},
{
"@type": "QuantitativeValue",
"name": "净重载重",
"value": 1.5,
// TNE 是公吨代码,和上面的 KGM 描述同一个物理量
"unitCode": "TNE",
"unitText": "t",
"propertyID": "https://example.com/spec/net-load-capacity"
}
],
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "起升高度",
// 区间必须拆成上下限,不能写 "85-200"
"minValue": 85,
"maxValue": 200,
// MMT 是毫米代码,和 unitText 要对应
"unitCode": "MMT",
"unitText": "mm",
// 满载这个测量条件单独挂出来
"valueReference": {
"@type": "QualitativeValue",
"name": "满载状态"
}
}
]
}
把两类写法并排看,差别主要在单位是否独立成字段:
| 场景 | 常见错误写法 | 规范写法 | AI 读出来的结果 |
|---|---|---|---|
| 单值带单位 | "value": "1.5t" |
"value": 1.5 + "unitCode": "TNE" |
错误写法只能当文本匹配,换算全丢 |
| 区间值 | "value": "85-200" |
"minValue": 85 + "maxValue": 200 |
错误写法回答区间时会截断或取一半 |
| 只有中文单位 | "unitText": "公斤" |
补 "unitCode": "KGM" |
缺单位码时跨语言站无法对齐 |
| 测量条件 | 混在 name 里 "满载起升高度" |
"valueReference" 单独挂 |
错误写法回答空载场景会串值 |
| 参数名不统一 | 各 SKU 写「净载重」「额定载重」 | 统一 name + propertyID |
不统一时同一属性被拆成多个三元组 |
单位码不用死记,常用的几个记住就够:
| 物理量 | unitCode | unitText 常见写法 | 说明 |
|---|---|---|---|
| 质量(千克) | KGM | kg / 千克 / 公斤 | 电商参数里出现频率最高 |
| 质量(公吨) | TNE | t / 吨 | 工业设备载重常用 |
| 长度(毫米) | MMT | mm / 毫米 | 尺寸类参数 |
| 长度(厘米) | CMT | cm / 厘米 | 包装尺寸常用 |
| 容积 | LTR | L / 升 | 液体、油箱类 |
| 功率 | KWT | kW / 千瓦 | 电机、整机功率 |
写个脚本把存量页面扫一遍
改造不能靠人肉翻页面。下面这个脚本把页面里所有 JSON-LD 抽出来,递归找 QuantitativeValue,把四类常见问题报出来。环境是 Python 3.8 以上,装 beautifulsoup4 和 requests 两个包即可:
# -*- coding: utf-8 -*-
# 依赖:pip install beautifulsoup4 requests,Python 3.8+
# 用途:扫描商品页 JSON-LD,找出量测参数的单位与区间写法问题
import json
# json 用来解析页面里 ld+json 的脚本文本
import re
# re 用来识别 "1.5t"、"85-200" 这类值和单位混写
import sys
# sys 用来读命令行参数并把结果通过退出码交给 CI
from bs4 import BeautifulSoup
# BeautifulSoup 只用来取 script 标签,不做 DOM 分析,解析开销小
import requests
# requests 负责抓取页面,批量跑时建议换成 session 复用连接
# UN/CEFACT Recommendation 20 常用单位码,用来核对 unitCode 与 unitText 是否一致
UNIT_PAIRS = {
"KGM": ("kg", "千克", "公斤"), # 千克
"TNE": ("t", "吨"), # 公吨
"GRM": ("g", "克"), # 克
"MMT": ("mm", "毫米"), # 毫米
"CMT": ("cm", "厘米"), # 厘米
"MTR": ("m", "米"), # 米
"LTR": ("L", "升"), # 升
"KWT": ("kW", "千瓦"), # 千瓦
}
# 上面的键是 UN/CEFACT 代码,值是允许出现在 unitText 里的写法
# 站上新出现一种单位时,先往这张表里补,再改模板
# ---------------- 遍历工具 ----------------
# 为什么要自己写递归:JSON-LD 的嵌套层数不固定
# 有些模板把 hasMeasurement 挂在 hasVariant 的子对象里,只取顶层 key 会漏
def walk(node, path="$"):
# 深度遍历 JSON-LD,把所有对象连同路径一起吐出来
if isinstance(node, dict):
# 字典:先把当前节点交出去,再对每个键值继续下钻
yield path, node
for key, val in node.items():
yield from walk(val, f"{path}.{key}")
elif isinstance(node, list):
# 列表:按下标继续下钻
# 数组逐个下钻,路径里带上序号方便定位
for idx, val in enumerate(node):
yield from walk(val, f"{path}[{idx}]")
# ---------------- 规则检查 ----------------
# 每条规则命中就往 problems 里塞一条带路径的说明
# 路径形如 $.hasMeasurement[0],照着它回模板里改字段很省事
def check_quantitative(node, path, problems):
# 只处理 QuantitativeValue 类型的节点
if node.get("@type") != "QuantitativeValue":
return
# 先把有没有区间字段记下来,后面判断 value 时要用到
has_range = "minValue" in node or "maxValue" in node
value = node.get("value")
# 情况一:区间值塞进了 value 字符串,例如 "85-200"
if not has_range and isinstance(value, str) and re.search(r"\d\s*[-~到]\s*\d", value):
problems.append(f"{path}: 区间值写进了 value,应改用 minValue + maxValue")
# 情况二:值和单位混写,例如 "1.5t"
if isinstance(value, str) and re.match(r"^[\d.]+\s*[a-zA-Z\u4e00-\u9fa5]+$", value):
problems.append(f"{path}: value 里混了单位,value 应只放数字")
# 情况三:两个单位字段全空,AI 只能靠猜
if not node.get("unitCode") and not node.get("unitText"):
problems.append(f"{path}: 缺少 unitCode 与 unitText")
# 情况四:unitCode 与 unitText 描述的不是同一个量纲
# 典型例子是 unitText 写"公斤"、unitCode 却写成 TNE
code = node.get("unitCode")
text = str(node.get("unitText", ""))
if code in UNIT_PAIRS and text and text.lower() not in [x.lower() for x in UNIT_PAIRS[code]]:
problems.append(f"{path}: unitCode={code} 与 unitText={text} 不匹配")
# 情况五:区间上下限写反
if "minValue" in node and "maxValue" in node and node["minValue"] > node["maxValue"]:
problems.append(f"{path}: minValue 大于 maxValue")
# ---------------- 页面抓取 ----------------
# 只认 type="application/ld+json" 的 script,微数据格式这次不处理
def audit(url):
# 取页面 HTML,超时设 15 秒,慢页面直接跳过别卡住整批
resp = requests.get(url, timeout=15, headers={"User-Agent": "Mozilla/5.0 geo-audit"})
soup = BeautifulSoup(resp.text, "html.parser")
problems = []
blocks = soup.find_all("script", type="application/ld+json")
# 一个页面常有多段 JSON-LD,坏段单独报错,不影响其他段继续跑
for idx, tag in enumerate(blocks):
try:
data = json.loads(tag.string or "")
except json.JSONDecodeError as exc:
# 解析失败多半是模板拼接时多了一个逗号,或者文案里的引号没转义
problems.append(f"ld+json#{idx}: 解析失败 {exc}")
continue
# 遍历到的每个对象都过一遍检查
for path, node in walk(data):
check_quantitative(node, path, problems)
return problems
# ---------------- 入口 ----------------
# 批量扫描时把站点地图导出的 URL 列表逐行传进来即可
def main():
# 命令行第一个参数是商品页地址,缺参数时直接提示用法
if len(sys.argv) < 2:
print("用法: python audit_measurement.py <商品页URL>")
return 2
url = sys.argv[1]
# 一个 URL 对应一份问题清单,空清单表示这一页没问题
problems = audit(url)
if not problems:
# 没发现问题就正常退出
print(f"{url} 未发现量测写法问题")
return 0
# 有问题逐个打印,退出码 1 供 CI 拦截
# 输出统一带 [问题] 前缀,CI 里直接 grep 这个标记就能出报告
for item in problems:
print("[问题]", item)
return 1
# 退出码语义:0 通过、1 有问题、2 用法错误
if __name__ == "__main__":
sys.exit(main())
跑起来就一行命令:
# 环境:Python 3.8+,已安装 beautifulsoup4、requests
# 用法:把商品页 URL 传进去,退出码 1 表示页面有问题
python audit_measurement.py "https://example.com/product/cbd-15"
# 只想本地看一段 JSON-LD 里有多少 QuantitativeValue,用 jq 快速过一遍
jq '.. | objects | select(."@type"=="QuantitativeValue")' product.jsonld
把脚本挂进 CI,每次商品页模板改动都跑一遍全量 SKU,比事后接客户电话划算。老周那边是每次发布前扫一遍站点地图里的一千多个商品 URL,扫出来的报告直接进发布卡点。
flowchart TD
A[取商品页 URL 列表] --> B[抓取 HTML]
B --> C[抽 ld+json 块]
C --> D{JSON 能否解析}
D -- 否 --> E[报语法错并跳过]
D -- 是 --> F[递归找 QuantitativeValue]
F --> G{value 是否混入单位}
G -- 是 --> H[拆出数字并补 unitCode]
G -- 否 --> I{是否有 minValue/maxValue}
I -- 否且是区间 --> J[改写为上下限字段]
I -- 是 --> K{unitCode 与 unitText 是否匹配}
K -- 否 --> L[按 UN/CEFACT 校正]
K -- 是 --> M[写入模板并发布]
H --> M
J --> M
L --> M
M --> N[跑官方校验工具复核]
官方校验工具怎么用
改完不能只看脚本。schema.org 官方的验证器和 Google 的富媒体结果测试都要过一遍,两者看的维度不一样:前者只校验类型和属性是否符合 schema.org 定义,后者额外看商品页富媒体结果要求的必填字段。
用法上有个顺序问题,先跑 validator.schema.org 确认没有类型错误,再跑 Google 的测试看缺哪些必填项。本地调试时把 JSON-LD 直接粘进「代码」页签比填 URL 快,改一行刷一次;线上复核再用 URL 方式,抓的是真实渲染结果。顺便提一句,商品富媒体结果要求 offers、review 或 aggregateRating 至少有一组,参数写对了但 offers 缺失,富媒体结果照样出不来,只是 AI 搜索的引用不受这条限制。
改造前后,AI 回答变了什么
老周那边改造完跑了三十天,抽了 12 个高频问法每天问一遍,统计口径是「单位正确且数值正确」算命中。数据是内部统计,样本不大,看趋势就够:
| 指标 | 改造前 | 改造后 30 天 | 说明 |
|---|---|---|---|
| 单位正确率 | 41% | 93% | 主要是补齐 unitCode 带来的 |
| 数值正确率 | 88% | 97% | 区间值拆分后少了一半截断 |
| 区间类问题命中 | 23% | 79% | minValue/maxValue 起的作用 |
| 跨型号对比类问题命中 | 12% | 61% | propertyID 统一后实体能对齐 |
| 客户来电纠错的次数 | 每周 6-8 通 | 每周 1 通 | 客服记录的口径 |
数值正确率本来就不低,因为数字大多是对的,错的只是单位。这也说明一个问题:参数类内容的 GEO 改造,收益集中在单位与区间这两块,不用一上来就重写整张参数表。先扫一遍,把缺单位的补齐,把区间拆开,性价比最高。
几个容易踩的坑
同页面多段 JSON-LD 互相打架是最常见的一个。模板里有一段 Product,运营又在富文本里塞了一段,两段的净重载重一个是 1500 kg 一个是 1.5 t,谁也没写错,但机器会看到两个冲突的三元组。做法是页面只保留一段权威 JSON-LD,其余位置的数据从同一个数据源渲染出来。
多语言站的换算也容易漏。英文站把 1.5 t 写成 3307 lb,数值换算了,单位码却还留着 TNE,unitCode 和 unitText 直接对不上。改单位得同时改三个字段,这件事在发布前用脚本卡住比人肉检查靠谱。
还有一种是表格和 JSON-LD 不同步。运营在后台改了参数,表格渲染跟着变了,JSON-LD 是模板里写死的旧值。这类问题脚本扫不出来,因为单看结构化数据它是合法的,只能靠把 JSON-LD 也接到同一个数据源上解决。
收尾
参数结构化的门槛不在语法,在于写的时候能不能忍住别把单位塞进值里。数字归数字,单位归单位,区间拆上下限,条件挂 valueReference,这四条做到了,AI 搜索读错参数的概率会掉一大截。至于要不要给每个参数都补 propertyID,看 SKU 规模,几十个以内手写也行,上千个就把参数定义页做成模板变量统一注入。
你们站上的参数是怎么写的,扫出来问题多不多,评论区可以贴几段 JSON-LD 一起看。
参考与延伸
- schema.org — QuantitativeValue
- schema.org — Product(hasMeasurement 属性说明)
- Google 搜索中心 — 商品结构化数据文档
- Google 富媒体结果测试工具
GEO|AI搜索优化|schema.org|QuantitativeValue|JSON-LD|结构化数据|电商商品页