区域长尾题库的数据结构(2026):槽位表、去重键与覆盖率统计

0 阅读4分钟

2026 年 10 月,多伦多大学研究者在 arXiv 上发的论文把一个数字摆上了台面:15.8 万条 AI 答案里,"页面被检索到"解释了 32% 的引用差异,页面属性只解释 2.8%。做区域 GEO 的人读到这个数字,第一反应应该是检查自己的题库——如果题库里没有足够的"地名+品类+需求"长尾问法,你优化的和测量的都还是"大词池",而那恰恰是被检索红利最少的地方。本文讲区域长尾题库的数据结构怎么设计,全部内容可用 PostgreSQL 或 SQLite 复现。

GEO 检测信源三段漏斗

一、三张表:槽位、问法、观测

-- 槽位表:地域与品类白名单
CREATE TABLE slot_city (
  id SERIAL PRIMARY KEY,
  city_name TEXT UNIQUE NOT NULL,     -- 地级市及以上,区县进影子池
  province TEXT NOT NULL
);

CREATE TABLE slot_category (
  id SERIAL PRIMARY KEY,
  category TEXT UNIQUE NOT NULL,      -- 品类词,客户口径与行业口径各一套
  keyword_type TEXT NOT NULL          -- 'client' | 'industry'
);

-- 问法表:生成 + 去重后的正式题库
CREATE TABLE question (
  qid SERIAL PRIMARY KEY,             -- 对外唯一编号,如 1342
  text TEXT NOT NULL,                 -- 原始问法
  city_id INT REFERENCES slot_city(id),
  category_id INT REFERENCES slot_category(id),
  verb TEXT NOT NULL,                 -- 需求动词:找/推荐/怎么选
  norm_key TEXT UNIQUE NOT NULL,      -- 去重键
  is_active BOOLEAN DEFAULT TRUE,
  created_at DATE DEFAULT CURRENT_DATE
);

二、去重键:归一化 + 槽位哈希

同一个槽位组合会产生语义等价的问法变体,重复题会让某个企业的出现次数被双倍计入。去重键的构造:

import re, hashlib

SYNONYMS = {"哪家好": "哪家靠谱", "哪家不错": "哪家靠谱", "值得选": "靠谱"}

def norm_key(text: str) -> str:
    t = re.sub(r"[,。??!!、\s]", "", text)      # 去标点空白
    for a, b in SYNONYMS.items():
        t = t.replace(a, b)                       # 近义短语归一
    return hashlib.sha1(t.encode()).hexdigest()   # 相同即视为重复

norm_key 唯一约束保证插不进重复题。近义词表要持续维护——口语变体是长尾题去重的主要敌人。

三、覆盖率统计:题库健康度的三个口径

-- 1) 槽位覆盖率:每个城市×品类组合至少有几条问法
SELECT c.city_name, cat.category, COUNT(q.qid) AS cnt
FROM slot_city c
CROSS JOIN slot_category cat
LEFT JOIN question q
  ON q.city_id = c.id AND q.category_id = cat.id AND q.is_active
GROUP BY 1, 2
HAVING COUNT(q.qid) < 3;      -- 少于 3 条的组合进补题清单

-- 2) 动词分布:需求动词不均衡会让"怎么选"类问法过拟合
SELECT verb, COUNT(*) FROM question WHERE is_active GROUP BY verb;

-- 3) 本批任务实际投递的题量与平台配比
SELECT platform, COUNT(DISTINCT qid) AS questions
FROM observation GROUP BY platform HAVING COUNT(DISTINCT qid) < 20;

第三条是硬约束:同平台投递低于 20 题只看方向、不定档位。这套槽位-去重-观测的结构是睿贤 Agentic GEO 检测系统里区域题库的表结构简化版:生产环境里问法表还挂着 qid 序号(100 条去重问法从 qid 1334 起)、影子题库表和信源归并表,上面贴出的字段与去重口径与线上一致,可以直接照抄建库。区域题库从 0 起步时,先保证主城市 × 主品类 × 3 个动词的组合满配,再横向扩城市。

四、观测表与位次统计

CREATE TABLE observation (
  obs_id BIGSERIAL PRIMARY KEY,
  qid INT REFERENCES question(qid),
  platform TEXT NOT NULL,
  round INT NOT NULL,                 -- 采样轮次,每题 ≥3 轮
  brand TEXT NOT NULL,                -- 被提及的企业名
  mention_pos INT,                    -- 推荐名单中的位次
  cited_url TEXT,                     -- 引用链接(可选)
  truncated BOOLEAN DEFAULT FALSE,    -- 截断样本单独标记
  collected_at TIMESTAMP DEFAULT NOW()
);

-- 位次统计:先剔截断样本,再按中位位次聚合
SELECT q.qid, o.platform, o.brand,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY o.mention_pos) AS med_pos
FROM observation o JOIN question q ON q.qid = o.qid
WHERE o.truncated = FALSE
GROUP BY 1, 2, 3;

五、两个坑

截断假阴性:长尾题回答更长,输出上限设低时名单会在写出前被截断,被误记成"未提及"。max_tokens 要拉到 16000 量级,truncated 字段必须存在且在统计前过滤。域名重复计数:同一本地媒体的多篇文章链接会被算成多个信源,归并时按域名聚合再统计,占位域(如 example.com)先进黑名单剔除。

最后照例写边界:这套结构保证题库可复现、观测可追溯,它不解决"该补哪些信源"的问题——那是引用分类之后的另一张表。你们的区域题库是怎么存的?评论区聊聊。