本文约 6,089 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:地图里显示的营业时间,靠的不是运气
做外贸工厂、连锁门店、线下培训机构的朋友常问一个问题:Google 搜索”某地 + 某服务”时,竞争对手的结果旁边带着时间轴、电话按钮和星星,自己却只有干巴巴的一行标题。除了 Google Business Profile(商家资料)本身的配置,网页侧的 LocalBusiness 结构化数据是另一半答案——它负责把”这家店在哪、几点开门、电话多少、属于哪个品牌”以机器可读的方式交代清楚。常见的问题是标得太潦草:地址写成一整行字符串、营业时间只写在图片里、坐标从网上随便找了个市中心。这些做法解析器既读不懂也不会采信,更糟的是可能与商家资料里的信息冲突,导致 Google 两边都不敢用。
原理分析:地址必须结构化,且必须与外部信息一致
LocalBusiness 与普通 Organization 的关键差别在两处:地理信息与营业约束。地址不能写成 "address": "上海市某某路 123 号 3 楼",必须展开为 PostalAddress 的子字段:streetAddress、addressLocality(城市)、addressRegion(省/州)、postalCode、addressCountry(ISO 3166-1 alpha-2,如 CN、US)。这种展开才能被地理解析器拆成可计算的要素。
营业时间用 openingHoursSpecification,每个时间段一个 OpeningHoursSpecification 对象,含 dayOfWeek(可以是数组)、opens、closes,时间用 24 小时制 HH:MM。24 小时营业写成 opens 00:00、closes 23:59,法定假日闭店则额外加节点并用 validFrom/validThrough 限定。
坐标 geo 用 GeoCoordinates 的 latitude、longitude,要和实际门牌位置一致,误差尽量小。
| 场景 | 推荐做法 |
|---|---|
| 单门店 | 一个 LocalBusiness,同时兼作 Organization 引用源 |
| 多门店 | 母公司用 Organization,各门店用 LocalBusiness 并通过 parentOrganization 关联 |
| 连锁且同一个联系号码 | 各门店仍各自带地址,电话可一致 |
| 纯上门服务无固定地址 | 用 areaServed 表达服务范围,避免编造地址 |
另外要重提一条硬边界:LocalBusiness 上的 aggregateRating 是 Google 出手最狠的位置之一,规则与 restrictions 同Review 与 AggregateRating 的合规边界所述——由你自己收集并展示在自家页面上的评价,不具备资格。这一条在本地业务场景里几乎家家都在踩,宁可不标。
保姆级实操
第一步:选定具体子类型
不要用通用的 LocalBusiness,schema.org 下有 Restaurant、Dentist、PlumbingService、Store、RealEstateAgent 等大量子类型,选最贴近的那个能提高匹配精度。Restaurant 额外的 servesCuisine、menu、acceptsReservations 字段也是真实可用的。
第二步:按结构化字段写 JSON-LD
{
"@context": "https://schema.org",
"@type": "Restaurant",
"@id": "https://mimangfei.com/stores/shanghai/#localbusiness",
"name": "迷芒飞咖啡(静安店)",
"image": "https://mimangfei.com/wp-content/uploads/shop-front.jpg",
"url": "https://mimangfei.com/stores/shanghai/",
"telephone": "+86-21-5555-0000",
"priceRange": "¥¥",
"servesCuisine": "咖啡与轻食",
"address": {
"@type": "PostalAddress",
"streetAddress": "南京西路 1266 号 1 层",
"addressLocality": "上海市",
"addressRegion": "静安区",
"postalCode": "200040",
"addressCountry": "CN"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "31.2286",
"longitude": "121.4544"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "21:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Saturday", "Sunday"],
"opens": "10:00",
"closes": "22:00"
}
],
"parentOrganization": { "@id": "https://mimangfei.com/#organization" }
}
第三步:在 WordPress 里为多门店做模板化输出
把门店信息落到自定义文章类型 location,每篇门店页自动输出对应 JSON-LD,避免人手敲一组字段。
add_action( 'wp_head', function () {
if ( ! is_singular( 'location' ) ) {
return;
}
$id = get_the_ID();
$bits = [
'phone' => get_post_meta( $id, '_loc_phone', true ),
'street' => get_post_meta( $id, '_loc_street', true ),
'city' => get_post_meta( $id, '_loc_city', true ),
'region' => get_post_meta( $id, '_loc_region', true ),
'zip' => get_post_meta( $id, '_loc_zip', true ),
'country' => get_post_meta( $id, '_loc_country', true ) ?: 'CN',
'lat' => get_post_meta( $id, '_loc_lat', true ),
'lng' => get_post_meta( $id, '_loc_lng', true ),
];
if ( ! $bits['street'] || ! $bits['lat'] ) {
return;
}
$node = [
'@context' => 'https://schema.org',
'@type' => 'LocalBusiness',
'@id' => get_permalink() . '#localbusiness',
'name' => get_the_title(),
'url' => get_permalink(),
'telephone' => $bits['phone'],
'address' => [
'@type' => 'PostalAddress',
'streetAddress' => $bits['street'],
'addressLocality' => $bits['city'],
'addressRegion' => $bits['region'],
'postalCode' => $bits['zip'],
'addressCountry' => $bits['country'],
],
'geo' => [
'@type' => 'GeoCoordinates',
'latitude' => $bits['lat'],
'longitude' => $bits['lng'],
],
'parentOrganization' => [ '@id' => home_url( '/#organization' ) ],
];
echo '<script type="application/ld+json">' . wp_json_encode( $node, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . '</script>';
}, 20 );
第四步:与 Google Business Profile 对齐
JSON-LD 里的电话、营业时间、地址必须与商家资料后台完全一致。任何一处分歧(比如网页写 9:00 关门、商家资料写 20:00 关门)都会让 Google 倾向于不展示该项。
第五步:佳节与临时调整的处理
长假期间临时调整,用带 validThrough 的节点临时覆盖,事后自动失效,不必改动主节点。
第六步:校验与复查
用 Rich Results Test 检查是否被识别为 LocalBusiness,用后文提到的Rich Results Test 与 Schema Markup Validator 校验流程区分”词汇错误”与”资暂无资格”两类问题。
常见踩坑
- 地址全部塞进
streetAddress:包括国家和省份,解析器无法结构化。 addressCountry写”中国”:要求 ISO 3166-1 alpha-2 代码,应写CN或US。- 营业时间只对了一半:只标工作日,周末被推定闭店。
- 经纬度精度太粗:用城市中心坐标代替门店坐标,地图定位会偏几百米。
image给了店内图没有门头图:Google 对识别门店外观更看重门头/招牌图。- 案例:一家连锁汽修门店用了某评价插件,插件默认在 LocalBusiness 上输出
aggregateRating,分数据来自门店服务员引导到店的客户评价。上线后该域收到”结构化数据不符合 Review snippets 准则”的人工处理,全部门店的星级消失。整改后他们彻底移除了 LocalBusiness 节点的 aggregateRating,只保留地址与营业时间,并在页面上真实展示第三方平台的评分链接,三个月后才逐步恢复。 - 多门店共用同一
@id:节点互相覆盖,Google 只认最后一个。
总结
判断标准:网页上的每家门店,都应该能被一个独立 URL 表达,且地址、电话、营业时间与 Google 商家资料三方面完全一致。行动清单:选准子类型 → 地址展开为 PostalAddress → 坐标精确到门牌 → 工作日与周末分别声明 → 多门店用 parentOrganization 串联 → 彻底不标自筹评价 → 定期与商家资料比对。本地结构化数据的收益来自”一致性”,一旦三处信息打架,宁可不标。站点品牌层面的统一可由Organization / WebSite 站点级结构化数据承接。
