AI 搜索工具 Perplexity 在回答末尾会附上一排引用来源用户通常默认这些蓝色链接能支撑上面的大段结论。Haus Research 的一次公开审计给出了一个值得注意的结果大约三分之一的引用页面并不包含被引用的数字。换句话说看似严谨的引用并不等于信息来源真的支持这条断言。这篇文章不打算复述一份报告而是用工程化方式拆解一个更通用的问题当我们需要验证 AI 搜索的引用质量时样本怎么选、规则怎么定、脚本怎么写、结果怎么解读。对做 AI 搜索测评、信息质量研究或内容审核的人来说这是一条可复现的审计路径对普通用户来说也能建立对 AI 搜索答案更准确的判断尺度。这里说的审计是“引用来源完整性审计”不是源代码审计也不是日志审计。1. 先理解 Perplexity 引用机制与审计目标1.1 AI 搜索的引用不是被动的文字标注传统搜索引擎返回的是链接列表用户自己判断哪条结果可点、哪条可信。Perplexity 这类 AI 搜索工具不一样它会先理解问题再综合多个网页内容生成一段回答并在关键句子上打上[1]、[2]这样的引用标记最后在回答底部列出对应的 URL。问题就出在“标记”这个动作上。引用标记的位置是模型在生成回答时自己决定的。模型可以从检索到的网页里抽取信息也可以根据训练记忆补全信息还可以把多个来源的信息重新组合。组合的时候来源 A 的统计数字可能被标成来源 B来源 C 的年份可能被挪到来源 D 的结论里。用户看到[1]默认认为“这句话是第 1 个链接里的内容”但实际链接页面里可能根本没有这句话甚至没有这个数字。Haus Research 的审计针对的正是这种“引用位置与来源内容的对应关系”。它不直接判断 Perplexity 的回答是对是错而是检查当回答中引用了一个数字时这个数字是否真的存在于对应的引用页面中。1.2 审计关注数字而不是所有观点引用完整性审计的第一步是选一个容易客观判定的对象。Haus Research 的公开结论特别提到“数字”这是很合理的选择。数字具有天然的可验证性。“约 72% 的用户”“成本下降 31%”“2023 年第二季度营收”这类表述要么能在原文里找到要么找不到。数字不像“效果更好”“风险较高”这类主观判断每个读者可能会有不同解读。数字审计的典型粒度为审计对象例子验证难度数字“全球市场份额达到 23%”低可做字符串匹配实体“某公司在 2024 年发布了新版本”中需要实体识别和上下文判断完整论断“该政策导致中小商家收入下降”高需要语义理解依赖主观判断因此以数字为突破口能让审计规则更清晰也让结果更容易复现。1.3 三分之一这个比例代表什么“约三分之一的引用页面并不包含被引数字”是一个抽样结果不是对 Perplexity 历史上所有回答的精确统计。它的意义在于提示一种系统性问题大型语言模型生成的引用标记并不天然等于来源页面的真实支撑。解读这个比例时至少要考虑三层口径判定“不包含”的标准是精确数字缺失还是连等价表述都不存在。审计样本覆盖了哪些类型的问题是偏向数据型问题还是包含新闻、观点、常识类问题。审计时间是哪一天页面是否发生了改版、下线或区域跳转。所以不要因为看到“三分之一”就说“Perplexity 有一半答案都在胡说”。“引用页面没有精确数字”和“回答内容完全错误”是两件事。前者是引用质量问题后者是事实准确性问题。两者相关但不等同。注意阅读任何 AI 搜索审计报告先检查“样本量、问题构成、判定规则”三个要素否则很容易把局部结论放大成全局定性。2. 引用完整性审计的通用分析框架2.1 先定义“包含”与“不包含”如果没有统一的判定规则审计结果无法横向对比。建议把引用来源按四级分类判定等级含义示例命中引用页面包含被引数字或等价表述回答写“31%”页面也写“31%”语义等价页面用其他表达方式说明了同一数字回答写“31%”页面写“百分之三十一”部分相关页面内容与主题相关但无法找到被引数字页面讨论同一产品但没有该统计值未命中页面内容不包含该断言或页面不可用页面 404或页面是另一个主题无法判定页面需要登录、被动态渲染、数字在图片中技术手段无法读取正文定义规则后必须固定到审计文档里。例如“包含”指精确数字匹配允许千分位分隔符不同“等价表述”指中英文数字、百分比符号、小数点精度差异“页面不可用”计入“无法判定”不计入“未命中”。这样做的原因是脚本判断一个页面“没有数字”很容易但人眼复核时可能发现数字被写成了“三成”或“30 %”。规则不统一最终比例就没有意义。2.2 抽样要覆盖不同类型的查询不要只测“2024 年全球智能手机出货量”这类单一类型的问题。AI 搜索在不同查询上的表现很可能不均衡。建议按以下几个维度抽样数据类型包含具体百分比、金额、年份、排名的查询。时效类型刚刚发生的新闻、一个月内的新闻、一年前的背景信息。语言类型中文、英文等不同语言的查询引用页面的结构差异很大。主题类型科技、财经、医疗、体育、民生不同主题的资料来源质量不同。热度类型热搜问题、长尾问题、专业性较强的问题。样本量不需要一开始就很大第一批可以先做 50 到 100 条。重点不是追求样本无限多而是保证判定规则稳定、人工复核可执行。2.3 记录访问时间和页面状态网页内容处于持续变化中。今天能打开的页面明天可能 404今天的正文里有数字改版后可能被移除。因此审计结果必须带有时间维度和页面状态。每条审计记录建议包含字段{ query: 某公司2024年营收是多少, answer_sentence: 某公司2024年营收为120亿美元[1], cited_url: https://example.com/reports/2024, http_status: 200, audit_time: 2026-01-18T10:20:00Z, has_number_in_page: false, judge_level: unmatched, manual_review: true }有了这样的记录后续别人可以重复访问页面即使内容发生变化也知道当时的判断依据。2.4 机器初筛加人工复核自动化脚本适合做批量初筛但“页面是否包含被引数字”这个问题不能完全交给一个简单的if number in text判断。同一个数字页面可能是“31.0%”“31 percentage”“百分之三十一”脚本若不处理就会误判。建议采用“机器初筛 人工抽检”的方式脚本批量抓取引用页面抽取正文脚本输出每个样本的数字匹配结果将“未命中”的样本全部人工复核将“命中”的样本随机抽 10% 复核记录人工复核与脚本结果不一致的案例反向修改脚本规则。3. 用最小脚本复现一次引用完整性检查3.1 技术选型与合规边界下面用 Python 写一个最小可运行的审计脚本。它负责三件事抓取引用页面文本、抽取回答中的数字和关键短语、判断页面中是否存在对应内容。依赖pip install requests beautifulsoup4 lxml抓取外部页面时必须注意合规要求遵守目标网站的robots.txt控制请求频率不并发爆破只读取公开可访问的页面不绕过登录页用于学习和研究不能用于商业数据采集。注意不要为了拿到动态页面里的数字而去破解验证码或伪造指纹。遇到反爬时正确的做法是把页面标记为“无法判定”而不是绕过访问限制。3.2 准备测试数据真实环境中可以从 Perplexity 的回答里提取句子和对应的 URL。这里先用一组最小测试数据演示sample { query: 某公司2024年营收同比增长多少, answer_text: 某公司2024年全年营收为120亿元同比增长31%[1], citations: [ { index: 1, url: https://example.com/company-2024-results } ] }实际项目里回答文本和引用列表可以从浏览器控制台、网页 DOM 或官方 API 中获取。获取方式不同但核心判断逻辑一致。3.3 抓取页面并提取正文import re import time import requests from bs4 import BeautifulSoup USER_AGENT Mozilla/5.0 (ResearchBot/1.0; educational use) def fetch_text(url): headers {User-Agent: USER_AGENT} resp requests.get(url, timeout10, headersheaders) if resp.status_code ! 200: return None, resp.status_code content_type resp.headers.get(Content-Type, ) if text/html not in content_type: return None, resp.status_code soup BeautifulSoup(resp.text, lxml) for tag in soup([script, style, noscript]): tag.decompose() text soup.get_text( , stripTrue) return text, resp.status_code这段代码的作用是拿到 HTML 页面后先移除脚本和样式再提取可见文本。要注意requests拿到的只是服务端返回的 HTML。如果页面数字由 JavaScript 动态加载这段代码会漏掉。3.4 抽取数字并匹配页面正文拿到后第一步是从回答中提取数字第二步是检查数字是否出现在页面正文里。def extract_numbers(text): matches set() patterns [ r\d(?:\.\d)?%?, r\d(?:,\d{3})(?:\.\d)?, r\d ] for pattern in patterns: for match in re.findall(pattern, text): normalized match.replace(,, ) matches.add(normalized) return matches def check_number_in_page(page_text, answer_numbers): if not page_text: return False for number in answer_numbers: if number in page_text: return True return False这是一个最原始的判断方式。它足够演示流程但在生产中不够用。原因在于“31%”在页面中可能写作“31 percent”“120 亿元”在页面中可能写作“12 billion”货币单位、量级、精度都可能被改写。所以在正式审计里建议同时加入以下“短语兜底”def normalize_words(text): return .join(re.findall(r[A-Za-z0-9], text.lower())) def check_phrase_in_page(page_text, sentence): page_norm normalize_words(page_text) words re.findall(r[A-Za-z0-9], sentence) if len(words) 5: return False for i in range(len(words) - 4): phrase .join(words[i:i 5]) if phrase in page_norm: return True return False这个函数抽出回答句子中连续的 5 个词在页面正文里做精确匹配。它能捕捉部分“页面确实包含这句话但脚本只看数字没发现”的情况但也会误报。所以它只能作为初筛补充不能替代人工复核。3.5 审计循环与结果输出把上面的函数串起来组成一个简单的审计流程def run_audit(samples, sleep_seconds1): results [] for idx, item in enumerate(samples): answer item[answer_text] number_matches extract_numbers(answer) for citation in item[citations]: page_text, status fetch_text(citation[url]) if page_text is None: results.append({ query: item[query], url: citation[url], http_status: status, number_hit: None, phrase_hit: None, conclusion: unavailable }) else: number_hit check_number_in_page(page_text, number_matches) sentence re.sub(r\[\d\], , answer) phrase_hit check_phrase_in_page(page_text, sentence) if number_hit: conclusion hit elif phrase_hit: conclusion semantic_suspect else: conclusion unmatched results.append({ query: item[query], url: citation[url], http_status: status, number_hit: number_hit, phrase_hit: phrase_hit, conclusion: conclusion }) time.sleep(sleep_seconds) return results运行后统计每个conclusion的数量from collections import Counter results run_audit(samples, sleep_seconds1) print(Counter(r[conclusion] for r in results))预期的输出可能类似Counter({unmatched: 10, hit: 18, unavailable: 4, semantic_suspect: 2})这只是一个过程演示。真实审计时你还需要把results写入 CSV 或 JSON字段至少包括回答原文、引用 URL、HTTP 状态码、页面是否可读、数字是否命中、人工复核结果。3.6 脚本结果只能作为初筛这个脚本最大的价值是把“引用页是否包含被引数字”从抽象问题变成一个可以批量观察的表格。但它也会犯两类错误漏报页面确实有数字但数字被渲染、转义、改写脚本检测不到误报页面恰好出现同一个数字但该数字说的是另一件事。因此所有unmatched样本都应该人工查看。只有经过人工复核的数据才适合放进最终报告。4. 为什么三分之一引用会出现“找不到被引数字”4.1 页面改版与内容迁移最常见的原因之一是页面内容已经变化。Perplexity 在生成回答时可能使用的是检索时的页面快照用户几天后点击链接时页面已经改版数字被挪到子页面或者被删除。这类情况不是模型幻觉而是“引用失效”。审计时如果只访问当前页面很容易把这种时效性问题判定为“引用不存在”。处理建议在审计记录中同时保存访问时间并在报告中区分“当前页面不存在该数字”和“历史页面曾经存在该数字”。如果预算允许可以访问网络存档中的历史快照作为辅助证据。4.2 动态渲染与反爬机制很多新闻网站的数字不是直接写在 HTML 里而是通过 JavaScript 从接口加载后再渲染。requests抓取的 HTML 中没有数字但浏览器打开时能看到数字。这种页面如果被脚本判为“不包含”实际上可能是抓取能力不足。判断时可以观察HTML 中是否有数字对应的 JSON 数据是否有独立的接口返回正文是否必须使用无头浏览器才能完成渲染。在审计研究中优先把这种页面标记为“无法判定”而不是“未命中”。否则统计结果会系统性偏高。4.3 模型生成引用时发生链接错位这是最需要关注的原因。模型可能从多个网页中综合信息最后生成回答时把来源 A 的数字标注成来源 B。典型表现是回答里有三个引用链接第一个链接页面内容与回答主题相关但被引用的具体数字实际出现在第二个链接中第一个链接只是因为“整体内容相关”被选进了引用列表。这种“相关但不对应”的引用比完全不相关更隐蔽。用户点击后会觉得页面内容差不多但仔细对不上数字。4.4 数字以图表、图片或附件形式存在不少财经和医疗内容的数字在图片里PDF 里或者交互式图表中。文本抽取脚本无法识别但人眼可以通过查看图片或下载文档确认。在审计标准中这种应该判为“无法通过文本抽取确认”不能直接归为“引用页面不包含被引数字”。特别是当页面主题完全吻合时要优先考虑页面中是否存在非文本信息承载数字。4.5 审计规则本身造成的假阴性还有一种情况是页面确实包含数字但表达方式和回答不同。例如回答中的表达页面中的表达31%百分之三十一31 percent31%120亿元120.0亿人民币1.2万12000如果审计脚本只做子串匹配这些都会被误判为“不包含”。这也是为什么前面反复强调自动化初筛必须配人工复核判定规则必须提前定义。4.6 原因对照表现象常见原因对审计结果的影响页面有数字但脚本没抓到JS 渲染、图片数字、格式转换假阴性可用无头浏览器复核页面 404 或权限受限链接失效、区域跳转、登录墙判定为无法判定不等同于引用错误页面主题相关但无精确数字模型综合多来源后错标引用可能是真实的引用质量问题页面内容被改版页面更新、内容迁移需要保存访问时间和页面快照页面有相同数字但含义不同数字冲突、上下文无关人工复核才能判断在写审计结论时一定要说明你排除了哪些干扰因素否则“三分之一”这个数字很容易被简化为“AI 搜索的引用三分之一都是假的”。5. 常见问题与排查链路5.1 页面明明有数字脚本却检测不到现象人工打开 URL能看到“31%”但脚本返回unmatched。排查顺序确认页面是否依赖 JavaScript 渲染确认数字是否存在图片、PDF、附件中确认数字格式是否包含全角字符或转义符确认回答里的数字和页面数字是否属于同一量级、同一统计口径人工复制页面中的数字与脚本抽取的文本做比较。解决方案先打印page_text[:500]看看页面文本里到底有什么。如果数字确实因为渲染缺失换成 Playwright 等无头浏览器方案但要增加请求成本和维护成本。5.2 页面打不开或出现 403、429现象resp.status_code为 403 或 429页面正文为空。常见原因目标网站禁止非浏览器 User-Agent审计请求频率过高触发限流页面限制了数据中心 IP。处理方式设置合理的 User-Agent但不要伪造过拟合的浏览器环境增加请求间隔降低并发将请求失败页面标记为unavailable不参与命中率计算不绕过验证码不伪造 Cookie 绕过反爬。5.3 被引内容在二级页面现象一级页面是新闻列表或财报摘要真正包含数字的链接在页面内。排查步骤查看一级页面是否有“阅读原文”“查看完整报告”链接在回复列表中查找是否包含完整正文如果内容必须多跳一次才能看到审计时应把“多跳可见”和“一级页面直接包含”分开统计。5.4 引用链接是搜索页或聚合页现象Perplexity 引用的 URL 是搜索引擎结果页或者某个标签聚合页并不是具体文章。这类引用对用户几乎没有验证价值。搜索页只会告诉你“网上有相关结果”并不会直接支撑回答中的数字。审计时应归类为“无法定位精确断言”并单独统计。5.5 如何区分引用错误和审计误判按下面的顺序排查步骤操作结论类型1页面能否正常访问不能则无法判定2页面是否依赖 JS 渲染依赖则补充渲染或无法判定3页面是否存在等价数字表达存在则语义等价4数字是否在图片、附件中存在则人工确认5数字是否存在二级页面存在则记录多跳6以上均不存在判定为未命中这套链路可以让审计结论更稳定也能在别人质疑结果时给出清楚证据。6. 从审计结论到生产实践如何评估 AI 搜索答案6.1 对普通用户不要因为带引用就默认可信引用能提供线索但不能自动证明回答正确。遇到以下场景时优先打开原始来源核对数字涉及医疗、投资、法律等高风险决策多个来源对同一数字的表述不一致回答中的数字特别精确但引用页面是短新闻或评论区引用 URL 是聚合页、搜索页或明显与主题无关的页面。验证时不要只看数字是否出现还要看数字的统计口径、时间范围和适用对象是否一致。例如“增长 31%”和“利润占比 31%”是两个完全不同的概念。6.2 对 AI 搜索产品把引用校验做成质量模块产品团队可以建立一条“引用可验证性”评估管线检索阶段记录候选来源和对应的摘要片段生成阶段让模型输出回答句与引用索引的对应关系验证阶段对回答句中的数字断言在对应引用页面上做匹配置信阶段若页面中没有找到对应数字降低该引用的展示置信度或提示用户“该引用可能无法直接支撑”。这一步不需要做到完美哪怕只做“数字字符串匹配 语义等价判断”已经能在上线前发现大量引用错位问题。6.3 对内容网站让数字更容易被识别和引用内容站点如果希望自己成为 AI 搜索的可靠信息来源可以做一些基础优化关键数字写进 HTML 正文字段不要只放在图片里避免用难以解析的动效图表承载唯一数据提供包含全文的独立详情页而不是只有摘要保持 URL 稳定改版时做 301 跳转对外提供结构化数据例如DataFeed、Dataset或Article等 Schema。这些做法不能保证 AI 一定正确引用但能显著降低“页面存在数字但抓取工具读不到”的概率。6.4 对研究机构建立可复现的审计平台引用质量审计是长期工作不应只在某次报告发布时做一次。建议建立查询样本集每个月固定更新一批数据型问题审计脚本仓库记录版本号随时可以重新跑判定规则文档明确什么是命中、未命中、无法判定结果数据库保存每次审计的完整记录发布模板固定披露样本量、时间、人工复核比例。只有可复现结果才有参考价值。否则下一次第三方做同样的审计可能因为规则不同得到完全不同的数字。6.5 可复用检查清单发布前的引用质量检查清单引用 URL 是否可以直接访问HTTP 状态是否正常被引数字是否出现在引用页面的 HTML 正文中数字格式是否与回答一致单位、小数点、百分号是否对应页面是否需要 JS 渲染才能显示数字数字是否存在于图片、PDF、附件或子页面引用 URL 是否为聚合页、搜索页或登录页审计时间是否记录页面快照是否保存未命中样本是否完成人工复核统计口径是否明确区分“未命中”和“无法判定”结论是否只针对本次抽样而不是泛化到所有 AI 搜索场景。7. 局限性、后续方向与一点建议7.1 审计结论有边界Haus Research 给出的“约三分之一”是一个值得重视的信号但它来自特定时间、特定抽样问题和特定判定规则。它不能说明 Perplexity 在所有问题类型上都是这个水平也不能说明其他 AI 搜索工具表现一致。引用完整性是评估 AI 搜索质量的重要维度但不是唯一维度。事实准确性、回答流畅度、时效性、覆盖面都需要分别评估。7.2 后续可以扩展的方向如果你打算做更深的研究可以考虑三个方向。第一语义匹配。不再只看数字字符串而是用一个轻量语义模型判断引用页面文本是否蕴含回答句。这样可以识别“百分之三十一”和“31%”这类改写也能一定程度处理表述差异。第二时序监测。对同一组查询每周跑一次引用审计观察引用质量随搜索算法升级、页面改版、模型迭代的变化。长期曲线比单次报告更有说服力。第三跨工具对比。把同样的查询集分别用不同 AI 搜索工具跑一遍对比引用命中率、来源质量和错误类型。这样可以帮助用户和产品团队理解各自系统的差异。7.3 对工程团队和普通开发者的建议最值得记住的一点不是“三分之一”本身而是AI 搜索的引用必须被当成一种可度量、可维护的产品指标而不是一个漂亮的界面元素。对普通开发者可以先从今天的最小脚本开始抓取 20 条数据型问题跑一遍引用匹配记录结果。你很快会发现两类问题有些页面打不开有些页面渲染不出数字有些引用页面和回答内容完全不相关。这个过程比阅读十份报告更能建立对 AI 搜索机制的体感。对团队负责人建议把“引用可验证率”纳入 AI 搜索产品的质量看板。上线新版本前除了看回答准确率也看引用页面的命中率。引用链接在用户理解中代表“证据来源”一旦证据经常缺失整个回答的可信度都会受影响。把审计脚本和人工复核流程沉淀下来比发布一篇道歉声明更有价值。