通过跟踪 AI Overview 引文变化,您可以检测您的网址在 Google AI 生成的答案中获得或失去排名的时刻。随着人工智能概述重塑点击分布,了解哪些领域出现在引文中以及该列表何时发生变化是内容策略的直接信号。 Scavio 搜索 API 返回结构化 AI 概述数据,包括引用的 URL,因此您可以按计划轮询查询、比较引文列表,并在竞争对手进入或您的页面删除时触发警报。本教程构建一个 Python 脚本,以 JSON 格式存储引文快照并报告运行之间的更改。
前置条件
- 安装了 Python 3.8 或更高版本
- 安装请求库(pip install requests)
- 来自 scavio.dev 的 Scavio API 密钥
- 基本熟悉 Python 中的 JSON 文件 I/O
操作指南
步骤 1: 定义要监控的查询和目标 URL
创建一个配置字典,将目标查询映射到您关心的 URL。该脚本将检查这些 URL 是否出现在每个查询的 AI 概述引用中。
MONITOR_CONFIG = {
"best crm for startups": {
"my_urls": ["https://mysite.com/crm-guide"],
"watch_competitors": True
},
"how to automate lead scoring": {
"my_urls": ["https://mysite.com/lead-scoring"],
"watch_competitors": True
}
}步骤 2: 从 Scavio 获取 AI 概述引文
每个查询的 POST 到 Scavio 搜索端点。响应包含一个 ai_overview 对象,其中包含一个引用数组,其中包含 AI 生成的答案中引用的 URL。
import requests
import os
API_KEY = os.environ.get('SCAVIO_API_KEY', 'your_scavio_api_key')
def fetch_citations(query: str) -> list[str]:
response = requests.post(
'https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY},
json={'query': query, 'country_code': 'us'}
)
response.raise_for_status()
data = response.json()
ai_overview = data.get('ai_overview', {})
citations = ai_overview.get('citations', [])
return [c.get('url', '') for c in citations]步骤 3: 加载以前的快照并计算差异
从磁盘读取最后保存的快照并将其与新的引文列表进行比较。确定自上次检查以来添加了哪些 URL 以及删除了哪些 URL。
import json
from pathlib import Path
SNAPSHOT_FILE = Path('citation_snapshots.json')
def load_snapshot() -> dict:
if SNAPSHOT_FILE.exists():
return json.loads(SNAPSHOT_FILE.read_text())
return {}
def diff_citations(old: list[str], new: list[str]) -> dict:
old_set, new_set = set(old), set(new)
return {
'added': list(new_set - old_set),
'removed': list(old_set - new_set),
'unchanged': list(old_set & new_set)
}步骤 4: 运行监视器循环并保存更新的快照
迭代所有受监控的查询,获取当前引用,与之前的快照进行比较,并打印任何更改的警报。将新快照保存到磁盘以供下次运行。
from datetime import datetime
def run_monitor():
snapshot = load_snapshot()
new_snapshot = {}
for query, config in MONITOR_CONFIG.items():
current = fetch_citations(query)
new_snapshot[query] = current
previous = snapshot.get(query, [])
changes = diff_citations(previous, current)
if changes['added'] or changes['removed']:
print(f'[{datetime.now().isoformat()}] Changes for: {query}')
for url in changes['added']:
label = 'MY URL' if url in config['my_urls'] else 'COMPETITOR'
print(f' + ADDED ({label}): {url}')
for url in changes['removed']:
label = 'MY URL' if url in config['my_urls'] else 'COMPETITOR'
print(f' - REMOVED ({label}): {url}')
SNAPSHOT_FILE.write_text(json.dumps(new_snapshot, indent=2))
print(f'Snapshot saved with {len(new_snapshot)} queries')Python 示例
import os
import json
import requests
from pathlib import Path
from datetime import datetime
API_KEY = os.environ.get('SCAVIO_API_KEY', 'your_scavio_api_key')
ENDPOINT = 'https://api.scavio.dev/api/v1/search'
SNAPSHOT_FILE = Path('citation_snapshots.json')
MONITOR_CONFIG = {
'best crm for startups': {
'my_urls': ['https://mysite.com/crm-guide'],
'watch_competitors': True
},
'how to automate lead scoring': {
'my_urls': ['https://mysite.com/lead-scoring'],
'watch_competitors': True
}
}
def fetch_citations(query: str) -> list[str]:
response = requests.post(
ENDPOINT,
headers={'x-api-key': API_KEY},
json={'query': query, 'country_code': 'us'}
)
response.raise_for_status()
data = response.json()
ai_overview = data.get('ai_overview', {})
return [c.get('url', '') for c in ai_overview.get('citations', [])]
def load_snapshot() -> dict:
if SNAPSHOT_FILE.exists():
return json.loads(SNAPSHOT_FILE.read_text())
return {}
def diff_citations(old: list[str], new: list[str]) -> dict:
old_set, new_set = set(old), set(new)
return {'added': list(new_set - old_set), 'removed': list(old_set - new_set)}
def run_monitor():
snapshot = load_snapshot()
new_snapshot = {}
for query, config in MONITOR_CONFIG.items():
current = fetch_citations(query)
new_snapshot[query] = current
previous = snapshot.get(query, [])
changes = diff_citations(previous, current)
if changes['added'] or changes['removed']:
print(f'[{datetime.now().isoformat()}] Changes for: {query}')
for url in changes['added']:
label = 'MY URL' if url in config['my_urls'] else 'COMPETITOR'
print(f' + ADDED ({label}): {url}')
for url in changes['removed']:
label = 'MY URL' if url in config['my_urls'] else 'COMPETITOR'
print(f' - REMOVED ({label}): {url}')
SNAPSHOT_FILE.write_text(json.dumps(new_snapshot, indent=2))
print(f'Snapshot saved with {len(new_snapshot)} queries')
if __name__ == '__main__':
run_monitor()JavaScript 示例
const API_KEY = process.env.SCAVIO_API_KEY || 'your_scavio_api_key';
const ENDPOINT = 'https://api.scavio.dev/api/v1/search';
const fs = require('fs');
const SNAPSHOT_FILE = 'citation_snapshots.json';
const MONITOR_CONFIG = {
'best crm for startups': {
myUrls: ['https://mysite.com/crm-guide'],
watchCompetitors: true
},
'how to automate lead scoring': {
myUrls: ['https://mysite.com/lead-scoring'],
watchCompetitors: true
}
};
async function fetchCitations(query) {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us' })
});
if (!response.ok) throw new Error('HTTP ' + response.status);
const data = await response.json();
const aiOverview = data.ai_overview || {};
return (aiOverview.citations || []).map(c => c.url || '');
}
function loadSnapshot() {
if (fs.existsSync(SNAPSHOT_FILE)) {
return JSON.parse(fs.readFileSync(SNAPSHOT_FILE, 'utf-8'));
}
return {};
}
function diffCitations(oldList, newList) {
const oldSet = new Set(oldList);
const newSet = new Set(newList);
return {
added: [...newSet].filter(u => !oldSet.has(u)),
removed: [...oldSet].filter(u => !newSet.has(u))
};
}
async function main() {
const snapshot = loadSnapshot();
const newSnapshot = {};
for (const [query, config] of Object.entries(MONITOR_CONFIG)) {
const current = await fetchCitations(query);
newSnapshot[query] = current;
const previous = snapshot[query] || [];
const changes = diffCitations(previous, current);
if (changes.added.length || changes.removed.length) {
console.log('[' + new Date().toISOString() + '] Changes for: ' + query);
changes.added.forEach(url => {
const label = config.myUrls.includes(url) ? 'MY URL' : 'COMPETITOR';
console.log(' + ADDED (' + label + '): ' + url);
});
changes.removed.forEach(url => {
const label = config.myUrls.includes(url) ? 'MY URL' : 'COMPETITOR';
console.log(' - REMOVED (' + label + '): ' + url);
});
}
}
fs.writeFileSync(SNAPSHOT_FILE, JSON.stringify(newSnapshot, null, 2));
console.log('Snapshot saved with ' + Object.keys(newSnapshot).length + ' queries');
}
main().catch(console.error);预期输出
{
"search_metadata": { "query": "best crm for startups", "country_code": "us" },
"ai_overview": {
"text": "The best CRM for startups depends on team size and budget...",
"citations": [
{ "url": "https://mysite.com/crm-guide", "title": "Top CRM Tools for Startups" },
{ "url": "https://competitor.com/crm-review", "title": "CRM Comparison 2026" }
]
}
}