搜「洗牙」为什么搜不到「洁治」

TL;DR:用户用的是口语,内容库里存的是术语,两边不在同一个词空间。解法不是把标题改得花哨,而是补一层同义词映射、给字段分权重,并在上线前把映射表当数据来校验。本文给出一份可直接改的映射表、一段校验脚本和一份 Checklist。

内容站上线一段时间常会冒出这种反馈:站里明明写过洁治的文章,用户搜「洗牙」却什么都出不来;反过来搜「洁治」,命中一堆标题里带术语、但正文其实在讲日常保健的稿子。运维看日志觉得检索没坏,编辑觉得内容也写了,问题卡在中间那层------词没对齐。

这是个工程问题,也是个内容问题。本文只谈检索与结构化标注,示例数据与占位值都不指向真实机构,也不给出诊疗判断或费用口径。文中阈值均为示例量级,落地前要用自有查询集回测。

一、先把三种「不像」分开

口语与术语的差距,不只是「换了种说法」,至少有三类关系混在一起。

等价关系:同一个意思的不同叫法,例如「洗牙」「洁牙」「预防性洁牙」在多数日常语境里指向同一类服务。这类可以放心做召回扩展。

上下位关系:「洁治」是专业动作,既可能出现在预防场景,也可能属于牙周基础治疗的一步;「刮治」「根面平整」又是更下一层。把上下位当成等价词直接互换,会把治疗性内容推给只想做日常维护的人,语义就串了。

相关但不等价:「牙周炎」「牙龈出血」「牙石」和洁治高度相关,但不是同义词。它们适合做相关词召回或标签,不适合直接替换查询词。

W3C 的 SKOS 把这类关系分成 prefLabel、altLabel 与 broader/narrower,思路可以直接借:先定「规范词」,再挂「替代词」,上下位单独走层级,不混进等价表。

二、映射表要当数据管,不要当文案补丁

常见做法是编辑在后台填几个「同义词」,用逗号分隔,检索时全部拼进查询。这个做法有三个隐患:没有关系类型、没有生效范围、没有校验,改一个词可能影响全站召回。

更稳的做法是把它做成结构化数据,每条带关系类型、适用字段和权重。schema.org 的 FAQPage 适合承载「一问一答」型内容,让机器读懂页面在回答什么问题;Google 搜索中心关于结构化数据的文档也强调标记要与页面可见内容一致。注意:结构化标注只解决「语义可读」,不承诺任何展示形式或流量结果,这部分别写进文案。

《中国居民口腔健康指南》用的是「洁牙」这一表述,而内容库里常见「洁治」,正好是一个真实存在的词形差异。把它写进映射表,比要求所有编辑统一改口更现实。

关系 查询侧 内容侧 处理
等价 洗牙 洁牙、预防性洁牙 扩展召回,权重 0.8(工程取值)
上下位 洁治 龈上洁治、龈下刮治 走层级,不直接替换
相关 牙龈出血 牙周检查、菌斑控制 走标签召回,权重 0.4(工程取值)
排除 洗牙 牙齿美白、贴面 加入否定词表

权重与条目数量都是工程取值,需按自有样本标定,不代表检索效果的承诺。

三、代码段一:映射表与字段权重

下面这份表把「关系类型」显式写出来,避免把上下位词当成等价词。示例站点与地域一律用占位值。

json 复制代码
{
  "site": "example.com",
  "region": "示例省/示例市",
  "version": "syn-1.0",
  "entries": [
    {
      "pref": "预防性洁牙",
      "alt": ["洗牙", "洁牙", "定期洁牙"],
      "relation": "equivalent",
      "boost": 0.8
    },
    {
      "pref": "洁治",
      "alt": ["龈上洁治"],
      "broader": "牙周基础治疗",
      "narrower": ["龈下刮治", "根面平整"],
      "relation": "hierarchy",
      "boost": 0.6
    },
    {
      "pref": "牙周检查",
      "alt": ["牙龈出血", "牙石"],
      "relation": "related",
      "boost": 0.4
    },
    {
      "pref": "预防性洁牙",
      "exclude": ["牙齿美白", "贴面", "冷光美白"],
      "relation": "negative"
    }
  ],
  "field_weights": {
    "title": 1.0,
    "h2": 0.7,
    "body": 0.5,
    "faq_question": 0.9
  },
  "note": "全部权重与条目数为工程取值,需按自有样本标定"
}

这里有个细节:把「牙齿美白」放进否定词表,比在排序阶段硬降权更干净。用户搜「洗牙」时,标题写着美白效果的内容本来就不该被召回,这类内容差异属于编辑判断,不是算法能补的。

四、代码段二:上线前校验脚本

映射表常见的错是「列了不用」与「互相打架」:某个 alt 词既挂在等价关系下,又挂在否定词表里;或者配置里写了一堆替代词,站内根本没有任何一条内容用到。下面这段做三项静态校验。

python 复制代码
import json
from collections import Counter

with open("synonyms.json", encoding="utf-8") as f:
    cfg = json.load(f)

errors = []
seen = {}
for i, e in enumerate(cfg["entries"]):
    if e.get("relation") not in ("equivalent", "hierarchy",
                                 "related", "negative"):
        errors.append(f"entry {i}: unknown relation")
    for w in e.get("alt", []):
        if w in seen and seen[w] != e["pref"]:
            errors.append(f"conflict: {w} -> {seen[w]} / {e['pref']}")
        seen[w] = e["pref"]
    if not 0.0 <= e.get("boost", 0.0) <= 1.0:
        errors.append(f"entry {i}: boost out of range")

# 否定词不得同时作为等价替代词出现
neg = {w for e in cfg["entries"] if e["relation"] == "negative"
       for w in e.get("exclude", [])}
equiv = {w for e in cfg["entries"] if e["relation"] == "equivalent"
         for w in e.get("alt", [])}
for w in neg & equiv:
    errors.append(f"negative+equivalent: {w}")

# 单表条目上限 24 条(工程取值,需按自有样本标定)
if len(cfg["entries"]) > 24:
    errors.append("entries exceed cap 24")

print("entries:", len(cfg["entries"]),
      "alt words:", len(seen),
      "errors:", len(errors))
for e in errors:
    print(" -", e)

脚本只做静态检查,动态部分还要接一次真实查询集:把近 200 条站内搜索词(工程取值)跑一遍,看零结果率有没有变化。零结果率下降才算映射生效,配置改了却没有查询集回测,等于没上线。

五、六个坑位

坑 表现 处理
等价表混入上下位 搜日常维护却出治疗稿 关系类型显式存,层级单独走
替代词只在后台生效 结构化问答里仍是术语 FAQPage 的 question 用口语写法
否定词表缺失 美白内容被洗牙召回 显式维护 exclude
标注与可见内容不一致 页面没写却标了问答 标记只覆盖页面真实存在的问答
改配置不回测 零结果率没动 固定查询集,每次改动跑一遍
权重写死在代码里 调参要发版 放进配置,允许按字段覆盖

六、发布前自检清单

css 复制代码
[ ] 每条映射带 relation,等价与层级不混用
[ ] 否定词与等价替代词无交集(校验脚本通过)
[ ] FAQPage 只标记页面可见的问答,question 用口语
[ ] 示例站点、地域统一占位:example.com/示例省/示例市
[ ] 字段权重走配置,不硬编码
[ ] 固定查询集不少于 200 条(工程取值,需按自有样本标定)
[ ] 单表条目上限 24 条、批量任务单批不超过 500 行(工程取值)
[ ] 全部阈值标注「经验取值/需按自有样本标定」
[ ] 文案不含疗效承诺、不含费用数字、不含机构与真实城市名

七、收口

「搜洗牙搜不到洁治」不是检索坏了,是没人为口语和术语之间那层关系负责。把关系类型写出来、把权重放进配置、把校验脚本接进发布流程,这个问题就从「编辑和运维互相猜」变成一条能逐项核对的流水线。

再补一句边界:结构化标注让机器更容易读懂页面在回答什么,但不承诺任何展示位置或流量变化;页面写的是什么,标注就该是什么。把这条守住,映射表才不会变成另一堆没人维护的补丁。

参考资料

  1. schema.org FAQPage(问答型页面的结构化标记)
  2. W3C SKOS Simple Knowledge Organization System(prefLabel/altLabel/broader/narrower)
  3. Google 搜索中心结构化数据文档(标记与页面可见内容一致)
  4. 《中国居民口腔健康指南》(「洁牙」表述,与内容库「洁治」构成词形差异)

本文使用 AI 辅助整理,经人工审核。

相关推荐
费曼学习法17 分钟前
Agent 死循环的根因不在模型:错误分类、退避、熔断、快照四层实现
前端
修炼的dance18 分钟前
同一张PNG换五种压法,体积差在颜色数上
前端
Lank_M19 分钟前
PNG 有损压缩省下的体积,大头不在 deflate
前端
用户老李42 分钟前
相机模型与标定自学教程 02:外参 \[R | t] 与完整投影链路
前端
用户老李1 小时前
无线电自学教程 03:电路基础——从欧姆定律到 LC 谐振
前端
申君健24864182772021 小时前
ESM 源码发布 + 显式子路径导出
前端
大龄秃头程序员1 小时前
实践 iOS Touch 响应链:从 sendEvent 到 nextResponder 的完整闭环
前端
用户老李1 小时前
YOLO 目标检测自学教程 05:动手——从零实现 mini-YOLOv1
前端
用户老李1 小时前
视频编码自学教程 02:颜色与色彩空间——从 RGB 到 Y'CbCr 4:2:0
前端