用「相关问题」(people_also_ask)做选题库,是个很顺手的办法:用户真实在问什么,谷歌直接摆在你面前。但真跑几十个关键词就会发现一个问题——同一批问题反复出现,而且措辞还不一样:「怎么学 Python」「如何学 Python」「学 Python 的方法」,三条其实是一条。
直接堆起来,几百条里一半是重复。这篇把题库清洗成可用的样子:去重、聚类、标意图。
取数:一次搜索带出问题
网页搜索(1 credit/次)的响应里,people_also_ask 是可选模块,每条通常带 question、answer 和来源链接:
import os
import requests
def fetch_questions(q: str) –> list[dict]:
data = requests.post(
"https://api.serpbase.dev/google/search",
headers={"Content-Type": "application/json",
"X-API-Key": os.environ["SERP_BASE_API_KEY"]},
json={"q": q, "hl": "zh-cn", "gl": "cn"},
timeout=(5, 30),
).json()
if data.get("status") != 0:
return []
return data.get("people_also_ask") or []
rows = []
for kw in keywords:
for item in fetch_questions(kw):
rows.append({"keyword": kw, "question": item.get("question", "")})
第一步:规范化,先统一「说法」
去重之前先归一,否则「怎么」和「如何」永远对不上:
import re
SYNONYMS = [
("如何", "怎么"), ("怎样", "怎么"), ("方法", "怎么"),
("哪些", "什么"), ("为什么", "原因"),
]
def normalize(q: str) –> str:
q = q.strip().lower()
q = re.sub(r"[\\s,。!?、,.!?::;;\\"'()()【】\\[\\]]", "", q)
for a, b in SYNONYMS:
q = q.replace(a, b)
return q
这一步会损失一点语义,换来的是去重准确率的大幅提升。中文题库不清洗,去重就是自欺欺人。
第二步:模糊去重
归一之后仍有语序差异,用相似度兜底。rapidfuzz 比标准库快得多:
from rapidfuzz import fuzz
def dedupe(items: list[dict], threshold: int = 90) –> list[dict]:
kept = []
for it in items:
norm = normalize(it["question"])
dup = False
for k in kept:
if fuzz.token_set_ratio(norm, k["norm"]) >= threshold:
k["sources"].add(it["keyword"])
dup = True
break
if not dup:
kept.append({**it, "norm": norm, "sources": {it["keyword"]}})
return kept
阈值 90 是个起点:低了会误删(「怎么学 Python」和「怎么学 Java」可能被合并),高了漏删。先抽样看二十对,再定阈值。
第三步:聚类,把问题分成主题
去重完还有几百条,需要按主题归堆。最简单的做法是 TF-IDF 加层次聚类:
from sklearn.cluster import AgglomerativeClustering
from sklearn.feature_extraction.text import TfidfVectorizer
texts = [k["norm"] for k in kept]
vec = TfidfVectorizer(analyzer="char", ngram_range=(2, 3), min_df=2)
X = vec.fit_transform(texts)
clusters = AgglomerativeClustering(
n_clusters=12, metric="cosine", linkage="average"
).fit_predict(X)
for k, c in zip(kept, clusters):
k["cluster"] = int(c)
字符 n-gram 对中文短句比词级切分更稳,不用额外装分词器。簇数先按「词量 ÷ 30」估,再人工调整。
第四步:标意图
规则足够用,不必上模型:
RULES = {
"原因": ["为什么", "原因"],
"方法": ["怎么", "步骤", "教程"],
"对比": ["哪个", "对比", "区别", "vs"],
"交易": ["价格", "多少钱", "费用", "购买"],
}
def tag_intent(q: str) –> str:
for label, words in RULES.items():
if any(w in q for w in words):
return label
return "其他"
输出成表:
question | cluster | intent | sources | count
sources 是这条问题出现过的关键词——它比问题本身更有用,能告诉你「哪些词背后是同一个疑问」。
五个坑
坑 1:不做归一直接去重。 「如何」和「怎么」被当成两条,题库虚胖。
坑 2:阈值拍脑袋。 太低误删、太高漏删。先人工抽样,再定阈值。
坑 3:把 PAA 当搜索量。 文档明确:这些问题和联想词不是搜索量测量。它代表「有人问」,不代表「很多人问」。
坑 4:不记来源关键词。 丢掉上下文,后面做内容规划时不知道这个问题该挂在哪篇文章下。
坑 5:一次采集当永久。 PAA 会随时间变,一季度重采一次,才能看到关注点的迁移。
小结
PAA 题库的清洗链:取数(1 credit/次)→ 规范化(同义词 + 去标点)→ 模糊去重(rapidfuzz)→ 聚类(TF-IDF + 层次)→ 意图标注(规则)。清洗完的题库,才配叫「选题库」;没清洗的,只是一堆重复的字符串。
people_also_ask 的字段结构见 SerpBase 文档。你清洗过 PAA 题库吗?评论区聊聊。
网硕互联帮助中心

评论前必须登录!
注册