Le suivi des changements de citations dans les AI Overviews vous permet de détecter le moment où vos URL gagnent ou perdent leur place dans les réponses générées par l'IA de Google. Alors que les AI Overviews redéfinissent la répartition des clics, savoir quels domaines apparaissent dans les citations — et quand cette liste évolue — est un signal direct pour la stratégie de contenu. L'API de recherche Scavio renvoie des données structurées sur les AI Overviews, y compris les URL citées, vous permettant ainsi d'interroger les requêtes selon un calendrier, de comparer les listes de citations et de déclencher des alertes lorsque des concurrents entrent ou que vos pages chutent. Ce tutoriel construit un script Python qui stocke des instantanés de citations au format JSON et rapporte les changements entre les exécutions.
Prérequis
- Python 3.8 ou supérieur installé
- bibliothèque requests installée (pip install requests)
- Une clé API Scavio depuis scavio.dev
- Connaissance de base des entrées/sorties de fichiers JSON en Python
Parcours
Étape 1: Définir les requêtes et les URL cibles à surveiller
Créez un dictionnaire de configuration faisant correspondre les requêtes cibles aux URL qui vous intéressent. Le script vérifiera si ces URL apparaissent dans les citations des AI Overviews pour chaque requête.
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
}
}Étape 2: Récupérer les citations des AI Overviews depuis Scavio
Envoyez une requête POST au point d'accès de recherche Scavio pour chaque requête. La réponse inclut un objet ai_overview avec un tableau citations contenant les URL référencées dans la réponse générée par l'IA.
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]Étape 3: Charger l'instantané précédent et calculer la différence
Lisez le dernier instantané enregistré depuis le disque et comparez-le à la nouvelle liste de citations. Identifiez les URL qui ont été ajoutées et celles qui ont été supprimées depuis la dernière vérification.
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)
}Étape 4: Exécuter la boucle de surveillance et enregistrer l'instantané mis à jour
Parcourez toutes les requêtes surveillées, récupérez les citations actuelles, comparez avec l'instantané précédent et affichez des alertes pour tout changement. Enregistrez le nouvel instantané sur le disque pour la prochaine exécution.
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')Exemple 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()Exemple 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);Sortie attendue
{
"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" }
]
}
}