本地服务页 Schema 字段纠错清单:LocalBusiness 最容易写错的字段与官方校验工具的正确用法
一、为什么本地服务页的 Schema 错得格外多
本地服务类页面的结构化数据,本质上是把"一家店"翻译给机器听:名称、地址、营业时间、服务范围、价格区间、评价。翻译得准,AI 引擎就能在用户问"附近哪家店周日开门""XX 区域有没有上门修空调的"时把这家店推荐出去;翻译错一个字段,轻则不引用,重则把错误信息播报给用户——比如把歇业的店标成营业中。
我们审计过几十个本地服务站点和商户页,结论是:LocalBusiness 相关的 Schema 错误率远高于 Article 或 Product,而且大部分错误集中在固定的十来个字段上。这些错误多数不是"不会写",而是对规范条文的细节理解有偏差。这篇就是一份纠错清单,逐项对照官方规范说明错在哪、怎么改。
二、八个高频错误字段
2.1 address:把结构化地址塞进一个字符串
最常见错误是用一个自由文本字符串表达地址。官方规范要求 address 使用 PostalAddress 类型,省、市、区县、街道逐级拆分:
{
"@context": "https://schema.org",
"@type": "HomeAndConstructionBusiness",
"name": "Example 家电维修(湖心路店)",
"address": {
"@type": "PostalAddress",
"streetAddress": "湖心路 128 号 3 幢 101 室",
"addressLocality": "泉州市",
"addressRegion": "福建省",
"postalCode": "362000",
"addressCountry": "CN"
}
}
为什么这么较真:AI 引擎做"附近"类推荐时,需要把地址解析成地理坐标参与召回。整体字符串的解析成功率明显低于结构化字段,尤其在行政区划与街道名重名的情况下(比如"建设路"遍布全国),缺一个 addressLocality 就可能导致定位到另一个城市。
2.2 openingHoursSpecification:时间格式与公共假日
第二高频错误是营业时间。常见坑有三个:用 opens/closes 却不带 dayOfWeek;跨零点营业(如酒吧到凌晨两点)直接写 closes: "02:00" 导致语义变成"当天凌晨两点已关门";节假日特殊时间没处理。
| 写法 | 问题 | 正确做法 |
|---|---|---|
| 只有 opens/closes,无 dayOfWeek | 被理解为每天同一时间,周末不同的店出错 | 按天拆成多条 Specification |
| 跨零点写 closes: 02:00 | 语义歧义 | 拆成两天:当天到 23:59 + 次日 00:00 到 02:00 |
| 节假日无特殊时段 | AI 回答节假日营业状态错误 | 补充 openingHoursSpecification 的有效区间或用特殊公告 |
2.3 geo:坐标精度与地址不一致
geo 字段给了 latitude/longitude 后,AI 引擎通常直接信任坐标。常见错误是坐标取自商户注册地而非实际门店位置,或者复制坐标时丢精度(小数点后少于 4 位在城市尺度上偏差可达数十米)。坐标必须与 address 指向同一个门牌,建议统一从地图服务按门牌号重新取点,不要沿用商户后台的旧数据。
2.4 priceRange:写法不统一等于没写
priceRange 接受字符串,但"¥""¥100-200""人均 80 元""中等"各种写法混杂时,引擎无法归一化。规范层面向的做法是给出区间数字,如 "priceRange": "¥¥" 或 "priceRange": "80-120 CNY"。全站统一样式,比单页"漂亮"的写法更有价值。
2.5 areaServed 与服务半径
上门服务类商户常见错误是漏写服务范围,或者用一段话描述"服务全城及周边"。规范做法是 areaServed 用 City/AdministrativeArea 或 GeoCircle 明确圈出范围:
{
"@type": "HomeAndConstructionBusiness",
"name": "Example 家电维修(湖心路店)",
"areaServed": [
{ "@type": "City", "name": "泉州市" },
{
"@type": "GeoCircle",
"geoMidpoint": { "@type": "GeoCoordinates", "latitude": 24.9139, "longitude": 118.5894 },
"geoRadius": "15000"
}
]
}
geoRadius 单位是米,写成字符串数值即可。有没有这段,直接决定"XX 路附近能不能上门"这类问题的回答质量。
2.6 review 与 aggregateRating:自证评分的风险
评分字段是本地服务页被引用的强信号,但也是造假重灾区。两条纪律:review 的作者、日期必须真实可追溯;aggregateRating 必须来自站内真实评价体系,且 reviewCount 与页面上可见的评价数一致。用假数据填 ratingValue: "5" 的做法,在 AI 引擎交叉验证(对比地图平台评分)时很容易被识别为不可信站点,连累其他字段的引用。
2.7同品牌多门店:Organization 与分店关系
连锁品牌常见的结构错误是每个门店页各写各的,品牌层没有聚合。规范做法是品牌用 Organization,门店用 LocalBusiness 并通过 parentOrganization 关联,同时每家店用独立 URL 与独立 Schema,不要在首页堆一个巨型数组。另有一个容易被忽略的字段是 hasMap:指向地图平台门店页的规范链接(而不是搜索结果链接)能帮助引擎快速完成实体对齐,连锁店多的站点统一填这一字段,对实体消歧有明显帮助。
2.8 把服务页只标成 LocalBusiness
只有门店实体没有服务实体,AI 引擎就不知道"这家店能提供什么服务"。给每项核心服务补 Service 节点并用 provider 关联门店,serviceType 写行业通用的服务名词(如"空调维修"而非营销口号),召回率才会有质的变化。
三、校验工具链:别只靠 Rich Results Test
很多团队的校验流程只有 Google 的富媒体测试一条路,这不够。建议三层校验:
| 层级 | 工具 | 覆盖什么 | 局限 |
|---|---|---|---|
| 语法层 | JSON-LD 解析器 / schema.org 官方定义 | 结构合法、类型字段存在 | 不管语义对错 |
| 语义层 | Rich Results Test、Schema Markup Validator | 字段取值格式、必需字段 | 以 Google 口径为准 |
| 业务层 | 自建规则脚本 | 坐标与地址一致、评分与可见评价数一致、营业时间与后台一致 | 需要自己维护规则 |
业务层是重点。写一个简单的流水线脚本,把站内所有商户页的 Schema 拉下来跑规则:
import json, requests
from datetime import datetime
RULES_CRITICAL = ['name', 'address', 'telephone', 'geo', 'openingHoursSpecification']
def fetch_ld(url: str) -> list:
html = requests.get(url, timeout=10, headers={'User-Agent': 'schema-bot/1.0'}).text
blocks = []
for part in html.split('<script type="application/ld+json">')[1:]:
try:
blocks.append(json.loads(part.split('</script>')[0].strip()))
except json.JSONDecodeError:
blocks.append({'@error': 'json-decode-failed'})
return blocks
def validate(node: dict, url: str) -> list:
problems = []
if node.get('@type') == 'json-decode-failed':
return [f'{url}: JSON 解析失败']
for field in RULES_CRITICAL:
if field not in node:
problems.append(f'{url}: 缺少 {field}')
geo, addr = node.get('geo', {}), node.get('address', {})
if geo and not addr.get('addressLocality'):
problems.append(f'{url}: 有坐标但缺市级行政区')
lat = geo.get('latitude')
if lat is not None and len(str(lat).split('.')[-1]) < 4:
problems.append(f'{url}: 坐标精度不足({lat})')
for spec in node.get('openingHoursSpecification', []):
if 'dayOfWeek' not in spec:
problems.append(f'{url}: 营业时间缺 dayOfWeek')
rating, reviews = node.get('aggregateRating', {}), node.get('review', [])
if rating and rating.get('reviewCount', 0) < len(reviews):
problems.append(f'{url}: aggregateRating.reviewCount 小于可见评价数')
return problems
if __name__ == '__main__':
urls = [line.strip() for line in open('merchant_urls.txt', encoding='utf-8') if line.strip()]
report = []
for u in urls:
for node in fetch_ld(u):
report += validate(node, u)
print('\n'.join(report) or 'ALL PASS', flush=True)
把它挂进发布流程,商户后台每次保存资料就触发一次校验,错误字段在入库前就被拦下,比上线后靠搜索引擎报错反馈要快得多。
四、一份可直接对照的自查清单
把上面的内容收敛成一张表,改造时逐行打勾:
| 字段 | 常见错误 | 达标标准 |
|---|---|---|
| address | 单字符串地址 | PostalAddress 五级拆分 |
| geo | 精度低、与地址不符 | 与门牌号一致,小数≥4 位 |
| openingHours | 缺 dayOfWeek、跨零点错误 | 按天拆分,特殊假日单独处理 |
| priceRange | 写法混乱 | 全站统一数字区间或符号档位 |
| areaServed | 缺失或纯文字 | City 或 GeoCircle 明确圈定 |
| review/aggregateRating | 数据造假、口径不一 | 真实评价,数量与页面一致 |
| 品牌结构 | 门店各自孤立 | parentOrganization 关联 |
| 服务实体 | 只有 LocalBusiness | 核心服务补 Service 节点 |
这张清单的用法建议是"先全局、后逐页":先用批量脚本扫全站,统计八类错误的分布,优先修复覆盖率最高、影响召回最大的两类(我们遇到过的大多数站点是 address 与 openingHours 占全部错误的一半以上);然后再对重点门店页做人工逐字段核对。修复顺序按影响面排,而不是按页面重要性排,因为对 AI 引擎来说,一个字段级错误的重复出现会被泛化理解为"这个站的数据口径不可靠",影响的不只是出错的那一页。
五、误区澄清与收尾
两个常见误区需要澄清。一是认为 Schema 校验通过就万事大吉——语法正确不代表业务正确,坐标指向错误的门店一样会被引用出去,业务层校验不可省。二是认为本地服务页只要做了 Schema 就能被推荐——AI 引擎还会交叉比对地图平台、评价平台的公开数据,站内信息与第三方平台数据长期不一致的商户,可信度会被持续拉低,所以 Schema 改造要和平台资料治理同步做。
趋势上,AI 引擎对本地生活类问题的回答正在从"列清单"转向"给结论",结论依赖的正是这类逐字段的结构化事实。技术上,这件事没有太多发挥空间:把字段写对、把口径统一、把校验做成发布流程的一部分,剩下的交给时间。如果你的站点也在做 LocalBusiness 治理,欢迎在评论区交流你踩过的字段坑。
关键词:LocalBusiness Schema、JSON-LD 结构化数据、PostalAddress 地址规范、openingHoursSpecification 营业时间、aggregateRating 评分、GEO 生成式引擎优化、AI 优化 AIO、Schema 校验工具