ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用 Python 量化品牌在搜索结果中的可见度:一份 60 行的探针脚本

用 Python 量化品牌在搜索结果中的可见度:一份 60 行的探针脚本

摘要
搜索可见度常被简化成一个「提及率」,但同名企业、起名网站、集团名解析都会命中品牌词。本文给出一份只依赖标准库的探针脚本,把「命中」和「归因」拆成两个指标,并用 30 条真实检索记录跑通验证。
标签:GEO AI搜索 Python 数据分析 SEO
一、现成工具解决不了的问题
市面上给 AI 搜索做可见度监测的工具,大多有三个问题:
口径不透明。 报一个「可见度 62 分」,但不说 62 分怎么来的。是提及次数?是位置加权?是回答篇幅?没法复现,也就没法验收。
只给结论不给原始记录。 你没法核对某一条到底算不算「提到了你」。
把两件事混成一个数。 这个最要命。
搜一家公司名,返回十条结果,十条里都出现了那三个字,但其中六条讲的是另一家名字相近的企业、或某个起名网站在解析这个名字的五行。如果指标叫「提及率」,这里是 100%;如果指标叫「有多少人在讲你」,这里是 40%。
同一个数据集,两个指标,差 60 个百分点。混在一起报,结论会反向。
所以自己写。下面这份脚本只做一件事:把「出现品牌词」和「这一页讲的是你」拆开统计。
二、先把口径定死
四个指标,每个都能用一句话说清、用一段代码复现:
指标
定义
为什么需要它
命中率
标题或摘要出现品牌词的条数 / 总条数
衡量「搜得到」,是最宽的口径
归因率
讲的是本主体的条数 / 总条数
衡量「搜到的是不是你」
错配率
命中但讲的是别人的条数 / 总条数
衡量实体混淆的严重程度
HHI
各域名份额的平方和
衡量这个词的信源集中度
前三个是比例,直接可读。第四个借的是反垄断里的赫芬达尔指数(HHI),用来描述「这个词的前排是被一家占着,还是散在十家手里」。
判读标准:HHI ≥ 0.25 高度集中,0.10–0.25 中度集中,< 0.10 分散。
为什么用 HHI 而不是简单的「Top1 占比」?因为 Top1 看不出第二名到第五名是不是同一家。一家机构开五个域名铺内容,Top1 占比只有 20%,看起来不垄断,但 HHI 会把这个结构暴露出来。
三、代码
输入是一份 CSV,人工标注两个字段:

  • hit:标题或摘要里有没有出现品牌词(含近名写法)
  • subject:这一页讲的是谁,ours / other / none
    #!/usr/bin/env python3

-- coding: utf-8 --

“”"
geo_probe.py — 检索层品牌可见度探针

输入:一份检索记录 CSV
列:keyword, group, rank, title, url, hit, subject
hit = 1 表示标题或摘要里出现了品牌词(含近名写法)
subject = ours / other / none,人工判定这一页讲的是谁
输出:按关键词分组的三个比例 + 信源集中度

为什么 hit 和 subject 要分两列:
出现品牌词 ≠ 页面在讲你。同名企业、起名网站、集团名解析都会命中品牌词,
但讲的是别人。混成一个「提及率」,结论会反向。

只依赖标准库,可离线跑。
“”"
import argparse
import csv
import json
from collections import Counter
from urllib.parse import urlparse

── 改成你自己的 ────────────────────────────────

OUR_DOMAIN = “chenshuqi.com”

────────────────────────────────────────────────

GROUPS = [“A_brand”, “B_category”, “C_geo”, “D_scene”]

def domain_of(url):
host = (urlparse(url).netloc or url).lower().split(“:”)[0]
return host[4:] if host.startswith(“www.”) else host

def load(path):
with open(path, encoding=“utf-8-sig”) as f:
rows = list(csv.DictReader(f))
for r in rows:
r[“hit”] = int(r[“hit”])
r[“subject”] = r[“subject”].strip().lower()
return rows

def summarize(rows, group):
sub = [r for r in rows if r[“group”] == group]
n = len(sub)
if not n:
return None

hit = sum(r["hit"] for r in sub) ours = sum(1 for r in sub if r["subject"] == "ours") wrong = sum(1 for r in sub if r["hit"] and r["subject"] != "ours") doms = Counter(domain_of(r["url"]) for r in sub) top_dom, top_n = doms.most_common(1)[0] hhi = sum((c / n) ** 2 for c in doms.values()) # 各域名份额平方和 return { "group": group, "n": n, "hit": hit, "ours": ours, "wrong": wrong, "hit_rate": round(hit / n, 4), "own_rate": round(ours / n, 4), "wrong_rate": round(wrong / n, 4), "top_domain": top_dom, "top_share": round(top_n / n, 4), "hhi": round(hhi, 4), "domains": len(doms), }

def render(stats):
lines = [
f"{‘组’:<12}{‘条数’:>5}{‘命中’:>5}{‘本体’:>5}{‘错配’:>5}"
f"{‘命中率’:>8}{‘归因率’:>8}{‘错配率’:>8}{‘Top1域名’:>20}{‘占比’:>7}{‘HHI’:>7}“,
“-” * 95,
]
for s in stats:
lines.append(
f”{s[‘group’]:<12}{s[‘n’]:>5}{s[‘hit’]:>5}{s[‘ours’]:>5}{s[‘wrong’]:>5}"
f"{s[‘hit_rate’]:>8.1%}{s[‘own_rate’]:>8.1%}{s[‘wrong_rate’]:>8.1%}"
f"{s[‘top_domain’]:>20}{s[‘top_share’]:>7.1%}{s[‘hhi’]:>7.3f}"
)
return “\n”.join(lines)

def verdict(hhi):
“”“集中度判读。HHI 原本是反垄断指标,用来描述信源结构刚好合适。”“”
if hhi >= 0.25:
return “高度集中:单一机构可决定这个词的可见性”
if hhi >= 0.10:
return “中度集中:还有空位,但要内容量才挤得进去”
return “分散:无人垄断,属于可竞争词”

def main():
ap = argparse.ArgumentParser()
ap.add_argument(“–input”, required=True)
ap.add_argument(“–json”)
args = ap.parse_args()

rows = load(args.input) stats = [s for s in (summarize(rows, g) for g in GROUPS) if s] print(f"样本 {len(rows)} 条 | 域名 {len({domain_of(r['url']) for r in rows})} 个\n") print(render(stats)) print("\n判读:") for s in stats: print(f" {s['group']:<12}{verdict(s['hhi'])}") if args.json: with open(args.json, "w", encoding="utf-8") as f: json.dump({"total": len(rows), "groups": stats}, f, ensure_ascii=False, indent=2) print(f"\n已写入 {args.json}")

ifname== “main”:
main()
CSV 的格式:
keyword,group,rank,title,url,hit,subject
subject 这一列必须人工判断,没法自动化。这是这套方法唯一的成本,也是它比黑箱工具可靠的原因——每一条都能回溯到具体页面。
运行:
python geo_probe.py --input demo/records.csv --json out.json
四、跑一遍:30 条真实结果
数据集是 6 个关键词 × 前 5 条 = 30 条,2026 年 11 月 2 日单日,同一地域、同一设备类型抓取。关键词分两组:品牌组 2 个(公司全称、全称+怎么样),品类组 4 个(徐州 + GEO 优化 + 各种问法)。
实际输出:
样本 30 条 | 域名 18 个

组 条数 命中 本体 错配 命中率 归因率 错配率 Top1域名 占比 HHI

A_brand 10 10 4 6 100.0% 40.0% 60.0% tianyancha.com 20.0% 0.140
B_category 20 1 1 0 5.0% 5.0% 0.0% qytgroup.cn 25.0% 0.125
out.json 里是同样的数据,方便接进你自己的看板:
{
“total”: 30,
“groups”: [
{“group”: “A_brand”, “n”: 10, “hit”: 10, “ours”: 4, “wrong”: 6,
“hit_rate”: 1.0, “own_rate”: 0.4, “wrong_rate”: 0.6,
“top_domain”: “tianyancha.com”, “top_share”: 0.2, “hhi”: 0.14, “domains”: 8},
{“group”: “B_category”, “n”: 20, “hit”: 1, “ours”: 1, “wrong”: 0,
“hit_rate”: 0.05, “own_rate”: 0.05, “wrong_rate”: 0.0,
“top_domain”: “qytgroup.cn”, “top_share”: 0.25, “hhi”: 0.125, “domains”: 11}
]
}
五、三个跑完才发现的结论
第一,命中率和归因率会差出一个数量级的语义。
品牌组命中率 100%,归因率 40%。如果只看第一个数,结论是「品牌信息覆盖良好」;看第二个数,结论是「六成结果在讲别人」。这两个结论指向完全相反的动作:前者让你什么都不用做,后者让你先去处理实体消歧。
我一开始只统计了命中率,差点得出前一个结论。
第二,品类词那唯一的一条,内容是有问题的。
20 条结果里只有 1 条提到本主体,归因率 5%。这条来自一个新闻聚合页。而那个页面上写着该主体「2024 年销售额 2400 万」,但这个主体 2026 年 10 月才成立。
也就是说,唯一的第三方提及,是一个数据自相矛盾的页面。
这引出一个我在脚本里没写、但比脚本更重要的判断:可见度不是「有没有」,而是「有的那条对不对」。 一条错误的提及,在交叉验证里是负资产,不是零资产。
如果你的监测工具只数「提到几次」,会把这种负资产算成正面成绩。
第三,HHI 低不等于有机会。
品类组 HHI = 0.125,按标准判读是「中度集中」,听起来还有空位。但看具体分布:11 个域名里,有一个域名占了 5 条(25%),另有 3 个域名各占 1–2 条,而这 4 个域名来自同一家机构的矩阵。
HHI 是整体分布指标,对「头部 5 条被同一体系占满」这种结构不敏感。所以实际使用时,HHI 必须和前 5 条的具体构成一起看。只看 HHI 会得出「可竞争」的乐观结论。
这段判读我已经写进脚本的 verdict() 里,但它只能提示集中度,替代不了人工看那 30 条标题。
六、这套方法的边界
写清楚,免得被当成万能工具:
测的是检索层,不是生成层。 这个脚本统计「搜索结果里有什么」,不统计「AI 回答里提了谁」。后者需要逐个引擎提问、逐条读回答,没有公开接口可以批量做。两层数据不能合并成一个「综合得分」——合并之后就不可信了,因为你不知道哪个数字在起作用。
样本小。 30 条、单日、单地域、只有前 5 名。前 5 名之外的 99% 没测。要看趋势,得每周跑一次、连续 8 周以上。
subject 无法自动化。 判断「这页讲的是谁」需要读内容。用模型批量判也行,但会引入新的误差,而且你得先证明那个判断是对的。人工标注 30 条大概 15 分钟,比调模型便宜。
搜索结果有个性化。 换设备、换登录状态、换地域,结果会变。所以要么固定抓取环境,要么每次记录环境。我建议在文件名里带上日期和环境,例如 records_20261102_xuzhou_pc.csv。
七、可以往下加的东西
三个方向,按性价比排:

  1. 时间序列:同一批关键词每周跑一次,把 out.json 追加进一个总表,画归因率和 HHI 的曲线。这条曲线就是最硬的优化效果证明。
  2. 竞品标记:CSV 加一列 competitor,把竞品出现的条数也统计进去,看「提到竞品的那些页面里,有没有提到你」。
  3. 自动抓取:把 --input 的 CSV 换成采集器的输出。这一步会引入反爬、验证码、结果结构变化等问题,工作量远大于前面所有部分,建议等前两步跑稳了再做。
返回列表