ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI可见性监测台:用Playwright+CDP量化站点在AI问答中的引用表现

AI可见性监测台:用Playwright+CDP量化站点在AI问答中的引用表现 1. 项目缘起与整体设计思路个人网站上线一年多了文章写了六七十篇但流量一直不温不火。我一开始以为是内容质量问题后来把站点日志翻出来仔细看了一遍发现一个很反直觉的现象来自搜索引擎的爬虫抓取频次不低但真正被引用、被推荐、被AI助手在回答里提到的次数几乎为零。换句话说我的内容在传统搜索里能排上号但在各类AI问答、AI摘要、AI推荐场景里基本是隐形的。这个现象促使我开始琢磨一件事AI 到底是怎么看见一个网站的它和传统搜索引擎爬虫的抓取逻辑有什么不同我能不能自己搭一套监测工具定期去问AI一些问题看看它会不会提到我的站点、引用我的内容、给出正确的信息于是就有了这个项目——AI 可见性监测台。核心思路很朴素准备一批探针问题用自动化脚本定期向多个AI问答入口发起提问把回答抓回来做结构化解析统计我的站点被提及、被引用、被正确描述的频次形成趋势曲线。整套系统跑在本地用 Python 做调度和解析用 Playwright 做浏览器自动化通过 CDP 协议接管真实浏览器上下文避免被识别为脚本流量。为什么选这个方案而不是直接调API原因有三点。第一很多AI问答入口的公开API并不对外开放或者开放的能力和网页端不一致网页端才是用户真实看到的结果。第二API返回的是纯文本而网页端包含引用来源、脚注、推荐卡片等结构化信息这些恰恰是可见性的关键证据。第三我想监测的是普通用户视角下的AI表现而不是开发者视角下的API表现两者经常有差异。整套系统的设计目标很明确可复现、可扩展、可长期跑。探针题库用 YAML 管理方便增删采样器用 Playwright CDP兼容多种页面结构解析器用规则 轻量模型兜底存储用 SQLite单文件、零运维可视化用简单的静态页面不引入重型前端框架。下面我把整个实践过程拆开讲包括踩过的坑和最终稳定下来的方案。2. 探针题库的设计与82道题的由来2.1 为什么是探针题而不是关键词一开始我走的是关键词监测的老路把站点核心词丢给AI看它回答里有没有出现我的域名。跑了两周发现数据几乎全是零问题出在关键词匹配太粗糙。AI回答里出现一个词不代表它真的看见了我的站点可能只是这个词本身太通用。比如我写的是Python 异步爬虫实践AI回答里出现Python和爬虫太正常了但这跟我的站点没有任何关系。后来我换了个思路不问关键词问问题。设计一批具体的、有明确答案指向的问题如果AI的回答里引用了我的内容或者给出了和我文章一致的结论那才算真正的可见。这就是探针题的雏形。探针题的设计遵循几个原则。第一问题要具体到能区分来源。比如Python 里 asyncio.gather 和 asyncio.wait 有什么区别就比Python 异步编程好得多前者有明确的技术边界后者太宽泛。第二问题要覆盖我站点的核心内容。我把自己写过的文章做了主题聚类每个主题设计3到5道探针题确保覆盖面。第三问题要有梯度。有些是基础概念题有些是实操细节题有些是踩坑经验题不同难度的问题能反映AI在不同层面的引用倾向。2.2 82道题的分类结构最终定下来的题库是82道分成五个大类具体分布如下表类别题量设计意图示例方向基础概念题18测试AI是否知道我的站点存在某技术名词的定义与我的文章观点是否一致实操细节题24测试AI是否引用我的具体方案某工具的参数配置、某报错的排查步骤对比选型题14测试AI是否推荐我的技术选型A方案和B方案的取舍我的文章有明确结论踩坑经验题16测试AI是否复述我的独家经验某场景下的非官方解法、非常规配置长尾组合题10测试AI在复杂问题上的引用能力多步骤流程、跨工具协作的完整方案这个分类不是拍脑袋定的。基础概念题用来做基线如果连这类题AI都不提我的站点说明站点在AI语料里的权重极低实操细节题和踩坑经验题是最有价值的因为这类内容原创性强AI如果引用说明它真的读到了我的文章对比选型题和长尾组合题则用来观察AI的推荐倾向。2.3 题库的 YAML 管理方式题库我用 YAML 管理结构大概是这样- id: probe_001 category: basic_concept question: Python 中 GIL 对多线程的实际影响是什么 expected_keywords: - GIL - 多线程 - CPU 密集型 site_related: true priority: high - id: probe_002 category: practical_detail question: Playwright 里如何等待一个动态加载的 iframe 内容 expected_keywords: - frame_locator - wait_for site_related: true priority: high每道题带id、category、question、expected_keywords、site_related、priority六个字段。expected_keywords不是用来做严格匹配的而是用来做回答相关性初筛——如果AI的回答里连这些词都没有那这道题的回答大概率跑题了可以直接标记为无效样本节省后续解析成本。提示题库不要一次性写满建议先写20道跑通流程再逐步扩充。我一开始写了60道结果采样器还没调稳跑一轮要四十多分钟调试效率极低。3. 采样器的核心技术选型与实现3.1 为什么用 Playwright 而不是 Selenium浏览器自动化框架的选择上我对比过 Selenium、Puppeteer 和 Playwright。最终选 Playwright 的原因很实际它对现代前端框架的兼容性更好等待机制更智能而且原生支持 CDP。Selenium 的问题在于等待逻辑要自己写WebDriverWait用起来啰嗦遇到动态渲染的页面经常要手动 sleep稳定性差。Puppeteer 只支持 Chromium 系覆盖面窄。Playwright 支持 Chromium、Firefox、WebKit 三套内核而且locator机制自带自动等待写起来干净很多。更关键的是 Playwright 可以通过 CDP 接管一个已经启动的浏览器实例。这一点对AI问答场景特别重要因为很多入口对全新启动的自动化浏览器有识别机制而接管一个正常启动的浏览器上下文行为特征更接近真实用户。3.2 CDP 接管浏览器上下文的实操CDP 是 Chrome DevTools Protocol 的缩写本质是浏览器暴露出来的一套调试接口。Playwright 通过connect_over_cdp方法可以连接到一个已经运行的浏览器实例。具体操作分两步。第一步用调试端口启动浏览器。以 Chromium 系为例# Linux / macOS /path/to/chrome --remote-debugging-port9222 --user-data-dir/tmp/ai-probe-profile # Windows C:\Program Files\Google\Chrome\Application\chrome.exe --remote-debugging-port9222 --user-data-dirC:\tmp\ai-probe-profile第二步在 Python 里连接from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.connect_over_cdp(http://localhost:9222) context browser.contexts[0] page context.new_page() page.goto(https://example-ai-entry.com) # 后续操作这样拿到的context是浏览器里真实存在的上下文带着正常的 cookie、localStorage、指纹信息。实测下来这种方式比p.chromium.launch()启动的全新实例稳定得多页面加载成功率从七成提升到九成五以上。注意--user-data-dir要指向一个独立的目录不要用默认的用户数据目录否则可能和你日常使用的浏览器配置冲突。我一开始图省事用了默认目录结果浏览器启动时报profile 被占用排查了半天。3.3 采样流程的完整实现采样器的核心流程是读题库 → 逐题发起提问 → 等待回答完成 → 抓取回答内容 → 落库。听起来简单实际实现里有几个关键点。等待回答完成的判断。AI问答页面的回答是流式输出的不能简单地等某个元素出现就抓取否则会抓到半截。我的做法是监听回答区域的文本长度变化连续3秒没有新增字符才认为回答结束。代码大概是这样import time def wait_for_answer(page, answer_selector, timeout60, stable_seconds3): start time.time() last_len 0 stable_start None while time.time() - start timeout: try: text page.locator(answer_selector).inner_text() except Exception: text current_len len(text) if current_len last_len and current_len 0: if stable_start is None: stable_start time.time() elif time.time() - stable_start stable_seconds: return text else: stable_start None last_len current_len time.sleep(0.5) return page.locator(answer_selector).inner_text()这个stable_seconds参数很关键。设太小会截断回答设太大浪费时间。我试过1秒、3秒、5秒最终3秒是平衡点。不同入口的流式速度不一样如果监测多个入口建议把这个参数做成可配置的。多入口的适配。不同AI问答入口的DOM结构差异很大选择器不能写死。我的做法是给每个入口写一个适配器类统一接口内部各自处理选择器和等待逻辑class BaseAdapter: def ask(self, page, question): raise NotImplementedError def extract_answer(self, page): raise NotImplementedError class EntryAAdapter(BaseAdapter): def ask(self, page, question): page.goto(https://entry-a.example.com) page.locator(textarea#prompt).fill(question) page.locator(button#submit).click() def extract_answer(self, page): return wait_for_answer(page, div.answer-content)这样新增一个入口只需要加一个适配器类主流程不用动。采样频率与节流。82道题如果一口气跑对目标站点压力大也容易被限流。我的策略是每题之间随机间隔8到15秒每跑完20题休息3到5分钟。一轮完整采样大概需要50到70分钟。这个节奏实测下来比较稳连续跑了一个月没有触发过任何限制。4. 回答解析与可见性判定逻辑4.1 从原始回答到结构化数据抓回来的回答是纯文本要变成可统计的数据需要经过解析。解析分三层引用来源提取、站点提及检测、内容一致性判断。引用来源提取是最直接的。很多AI问答入口会在回答末尾列出参考链接或者在正文里用脚注标注来源。我用正则先把所有URL抽出来然后过滤出属于我站点的域名。这一步能拿到最硬的证据——AI明确引用了我的页面。站点提及检测是第二层。有些回答不会给链接但会在正文里提到站点名称或者文章标题。我用站点名、站点别名、核心文章标题做模糊匹配命中就记一次提及。内容一致性判断是第三层也是最难的一层。AI可能提到了我的站点但复述的内容和我的原文有出入甚至完全相反。这种情况比没提到更糟糕因为它会传播错误信息。我的做法是把回答和原文做语义相似度比对低于阈值就标记为提及但内容不一致单独统计。4.2 可见性评分模型为了把多个维度的数据汇总成一个可比较的指标我设计了一个简单的评分模型维度权重说明引用链接5AI给出了指向我站点的可点击链接明确提及3正文提到站点名或文章标题但无链接内容一致2回答内容与原文语义一致内容偏差-2提及但内容与原文不符完全未提及0回答里没有任何站点痕迹每道题的得分是各维度加权求和82道题的总分就是这一轮的可见性指数。这个模型不追求绝对精确追求的是趋势可比——同一套题库、同一套权重跑出来的分数变化能反映真实趋势就够了。提示权重不要频繁调整。我一开始每周调一次权重结果趋势曲线完全没法看因为分数变化分不清是真实变化还是权重变化。后来固定权重只在题库大改时才重新校准。4.3 解析器的容错设计解析环节最容易出问题的是页面结构变化。AI问答入口改版是常事选择器一失效整轮采样就废了。我的容错设计有三层。第一层选择器多备份。每个关键元素准备2到3个候选选择器按优先级依次尝试第一个命中就用。第二层解析失败时保存原始HTML。这样即使解析逻辑失效原始数据还在可以事后重新解析不用重新采样。第三层异常告警。如果某一轮采样里解析失败率超过20%脚本会发一封邮件提醒我让我及时检查。def extract_with_fallback(page, selectors): for sel in selectors: try: el page.locator(sel).first if el.count() 0: return el.inner_text() except Exception: continue return None这套容错机制救过我好几次。有一次某个入口改版主选择器失效但因为备份选择器还在采样没中断我是第二天看日志才发现页面结构变了。5. 数据存储、可视化与长期运行5.1 SQLite 存储方案数据存储我选了 SQLite理由很简单单文件、零配置、Python 原生支持、查询够用。表结构设计了三张表。probes表存题库字段包括id、category、question、expected_keywords、priority。samples表存每次采样的原始结果字段包括sample_id、probe_id、entry_name、raw_answer、raw_html、sampled_at。scores表存解析后的评分字段包括sample_id、citation_count、mention_count、consistency_score、total_score。三张表用sample_id和probe_id关联查询的时候 join 一下就能出各种维度的统计。SQLite 在这个数据量级下一个月大概几万条记录性能完全够查询响应都在毫秒级。-- 查询最近30天每个入口的平均可见性得分 SELECT entry_name, AVG(total_score) AS avg_score, COUNT(*) AS sample_count FROM scores JOIN samples ON scores.sample_id samples.sample_id WHERE samples.sampled_at datetime(now, -30 days) GROUP BY entry_name ORDER BY avg_score DESC;5.2 可视化页面的轻量实现可视化我没上重型框架就用 Python 生成静态 HTML配合 Chart.js 画图。每次采样完成后脚本自动重新生成一份报告页面包含趋势曲线、分类得分、Top 提及文章等几个模块。趋势曲线是最重要的横轴是日期纵轴是可见性指数多条线对应多个入口。看曲线能直观判断整体是在涨还是在跌哪个入口的引用在增加哪个在减少。分类得分用柱状图能看出哪类探针题的得分高、哪类低指导后续的内容创作方向。提示报告页面建议加上数据更新时间和本轮样本量否则过一段时间回头看分不清某个数据点是不是异常样本导致的。5.3 长期运行的稳定性保障这套系统我是打算长期跑的所以稳定性比功能丰富更重要。几个实践下来的保障措施。日志分级。DEBUG 级别记录每道题的采样细节INFO 级别记录每轮采样的汇总ERROR 级别记录异常。日志按天切割保留30天。出问题的时候先看 ERROR再看对应时间段的 DEBUG。断点续跑。采样过程中如果中断重新启动时能从上次中断的题目继续不用从头再来。实现方式是在samples表里记录已完成的probe_id启动时先查一遍跳过已完成的。定时任务。用系统的定时任务每天凌晨跑一轮跑完自动生成报告。我试过用 Python 的schedule库但进程挂了就没了还是系统级的定时任务靠谱。资源清理。每轮采样会产生大量临时文件截图、HTML快照我设置了一个清理任务每周删一次超过14天的临时文件避免磁盘被撑爆。6. 常见问题与排查技巧实录6.1 采样环节的典型问题问题一页面加载超时。表现是page.goto卡住或者报超时。排查下来主要有两个原因一是网络波动二是目标站点响应慢。解决方法是给goto设置合理的超时时间我设的30秒并加重试机制失败后等5秒重试最多重试3次。问题二回答抓取不完整。表现是抓到的回答只有开头几句。原因是等待逻辑判断过早流式输出还没结束就抓了。解决方法是调大stable_seconds或者改用更精确的完成信号比如监听停止生成按钮的消失。问题三被识别为自动化流量。表现是页面返回验证码或者直接拒绝访问。这个问题的根源是浏览器指纹和操作行为太机器。缓解措施包括用 CDP 接管真实浏览器、随机化操作间隔、模拟鼠标移动轨迹、避免高频访问同一入口。问题现象可能原因排查方向解决措施页面加载超时网络波动/站点慢看日志时间戳加重试超时配置回答抓取不完整等待逻辑过早对比原始HTML调大稳定等待时间触发验证码指纹/行为异常换CDP接管方式随机化模拟行为解析失败率高页面结构变化看保存的HTML更新选择器备份数据缺失采样中断查断点记录启用断点续跑6.2 解析环节的典型问题问题四引用链接提取遗漏。有些入口的引用链接是动态渲染的正则抓不到。解决方法是等页面完全渲染后再抓或者直接从保存的HTML里用更宽松的规则二次提取。问题五语义相似度误判。有些回答和原文用词不同但意思一致被误判为内容偏差。这个问题的根本原因是纯文本相似度不够智能。我的缓解做法是引入轻量的语义模型做二次判断但要注意模型本身也有误差所以最终判定还是以人工抽检为准。6.3 独家避坑经验经验一先跑通再优化。我一开始想把所有功能都做完美再跑结果拖了两周还没上线。后来改成先跑通最小闭环10道题、1个入口、简单解析当天就出数据了后面再逐步加功能效率高得多。经验二原始数据一定要留。解析逻辑会变但原始数据不会。我坚持把每轮采样的原始回答和HTML都存下来后来解析逻辑改了三次每次都能用历史数据重新跑不用重新采样。经验三题库要定期更新。AI的语料在变我的站点内容也在变。题库如果半年不更新测出来的数据会越来越失真。我的做法是每季度review一次题库淘汰过时的题补充新内容的题。经验四不要只看总分。总分是个汇总指标容易掩盖细节。我后来养成了看分类得分的习惯发现踩坑经验题的得分一直偏低说明AI对我的独家经验引用少这直接指导了我后续的内容调整方向。经验五多入口对比比单入口趋势更有价值。单看一个入口的曲线涨跌可能只是那个入口的算法调整。多个入口一起看如果趋势一致那才是真实的可见性变化。7. 后续可扩展的方向这套系统跑了一个多月基本稳定了。后续我打算往几个方向扩展。一是增加入口覆盖。目前监测的入口有限后续会逐步加入更多AI问答场景观察不同场景下的引用差异。二是引入内容归因。现在只能统计站点被提及还不能精确到哪篇文章被引用后续想通过文章标题和URL的匹配做更细的归因。三是自动化内容建议。根据探针题的得分分布反向指导内容创作——哪类问题AI引用少就说明哪类内容需要加强。四是采样频率的动态调整。现在固定每天一轮后续想根据数据波动自动调整频率波动大的时候加密采样平稳的时候降低频率节省资源。五是多站点支持。现在只监测我自己的站点后续想把这套系统做成通用的支持配置多个站点方便朋友一起用。这套东西说到底就是个用数据说话的工具。以前判断内容效果靠感觉现在有了量化指标调整方向心里有底多了。如果你也在做个人站点又关心AI场景下的可见性这套思路可以直接抄改改适配器就能用。
返回列表