Le contrôle par lot des classements de nuit vous permet de suivre des milliers de mots-clés sans épuiser le quota d'API diurne ni ralentir les systèmes de production. Le principe est simple : chargez les mots-clés à partir d'un CSV, parcourez-les avec un court délai, stockez les positions dans un fichier de résultats et planifiez le script avec cron. En utilisant l'API Scavio à 0,005 $ par crédit, une exécution nocturne de 4 000 mots-clés coûte 20 $, contre 60 $+ sur SerpAPI ou 499,95 $+ sur Semrush Business. Ce tutoriel construit un pipeline complet de traitement par lot de nuit en Python.
Prérequis
- Python 3.9+ installé
- modules requests et csv (les deux dans la bibliothèque standard sauf requests)
- Une clé API Scavio depuis scavio.dev
- Un fichier CSV avec les mots-clés à suivre
Parcours
Étape 1: Préparez votre CSV de mots-clés
Créez un CSV avec des colonnes pour keyword et target_url. Le script vérifiera chaque mot-clé et enregistrera la position de l'URL cible.
import csv
keywords = [
{'keyword': 'best crm for startups', 'target_url': 'yoursite.com'},
{'keyword': 'crm pricing comparison 2026', 'target_url': 'yoursite.com'},
{'keyword': 'hubspot alternative small business', 'target_url': 'yoursite.com'},
]
with open('keywords.csv', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['keyword', 'target_url'])
writer.writeheader()
writer.writerows(keywords)
print(f'Wrote {len(keywords)} keywords')Étape 2: Construisez la fonction de vérification de classement
Pour chaque mot-clé, appelez l'API de recherche et parcourez les résultats organiques pour votre domaine cible. Renvoyez la position ou None si non trouvé dans les premiers résultats.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
def check_rank(keyword: str, target_domain: str) -> dict:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': keyword, 'country_code': 'us'})
resp.raise_for_status()
for r in resp.json().get('organic_results', []):
if target_domain in r.get('link', ''):
return {'keyword': keyword, 'position': r['position'], 'url': r['link']}
return {'keyword': keyword, 'position': None, 'url': None}Étape 3: Traitez tous les mots-clés par lot avec limitation de débit
Lisez le CSV, vérifiez chaque mot-clé avec un délai de 0,5 seconde pour rester bien en dessous des limites de débit, et collectez les résultats. Pour 4 000 mots-clés, cela prend environ 35 minutes.
import time
def batch_rank_check(csv_path: str) -> list:
results = []
with open(csv_path) as f:
reader = csv.DictReader(f)
rows = list(reader)
print(f'Checking {len(rows)} keywords...')
for i, row in enumerate(rows):
result = check_rank(row['keyword'], row['target_url'])
results.append(result)
if (i + 1) % 100 == 0:
print(f' Checked {i + 1}/{len(rows)}')
time.sleep(0.5)
return resultsÉtape 4: Enregistrez les résultats avec un horodatage
Écrivez les résultats dans un CSV daté afin de pouvoir suivre l'évolution des classements au fil du temps. Chaque exécution nocturne produit un fichier.
from datetime import date
def save_results(results: list) -> str:
filename = f'ranks_{date.today().isoformat()}.csv'
with open(filename, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['keyword', 'position', 'url'])
writer.writeheader()
writer.writerows(results)
ranked = [r for r in results if r['position'] is not None]
print(f'Saved {len(results)} results to {filename}')
print(f' Ranking: {len(ranked)}, Not found: {len(results) - len(ranked)}')
return filenameÉtape 5: Planifiez avec cron pour des exécutions nocturnes
Ajoutez une entrée cron pour exécuter le script à 2 heures du matin chaque jour. Le script se termine bien avant le matin, donc les résultats sont prêts lorsque vous commencez à travailler.
# Add to crontab (crontab -e):
# 0 2 * * * cd /path/to/project && SCAVIO_API_KEY=your_key python batch_ranks.py
# Full script entry point:
if __name__ == '__main__':
results = batch_rank_check('keywords.csv')
save_results(results)
cost = len(results) * 0.005
print(f'Total cost: ${cost:.2f}')Exemple Python
import os, csv, time, requests
from datetime import date
API_KEY = os.environ['SCAVIO_API_KEY']
ENDPOINT = 'https://api.scavio.dev/api/v1/search'
def check_rank(keyword: str, target: str) -> dict:
resp = requests.post(ENDPOINT,
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': keyword, 'country_code': 'us'})
resp.raise_for_status()
for r in resp.json().get('organic_results', []):
if target in r.get('link', ''):
return {'keyword': keyword, 'position': r['position'], 'url': r['link']}
return {'keyword': keyword, 'position': None, 'url': None}
def main():
with open('keywords.csv') as f:
rows = list(csv.DictReader(f))
results = []
for i, row in enumerate(rows):
results.append(check_rank(row['keyword'], row['target_url']))
if (i + 1) % 100 == 0:
print(f'Checked {i + 1}/{len(rows)}')
time.sleep(0.5)
filename = f'ranks_{date.today()}.csv'
with open(filename, 'w', newline='') as f:
w = csv.DictWriter(f, fieldnames=['keyword', 'position', 'url'])
w.writeheader()
w.writerows(results)
ranked = sum(1 for r in results if r['position'])
print(f'{ranked}/{len(results)} ranking, cost: ${len(results) * 0.005:.2f}')
if __name__ == '__main__':
main()Exemple JavaScript
const fs = require('fs');
const API_KEY = process.env.SCAVIO_API_KEY;
async function checkRank(keyword, target) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: keyword, country_code: 'us' })
});
const data = await resp.json();
const match = (data.organic_results || []).find(r => r.link?.includes(target));
return { keyword, position: match?.position || null, url: match?.link || null };
}
async function main() {
const lines = fs.readFileSync('keywords.csv', 'utf8').trim().split('\n').slice(1);
const results = [];
for (const line of lines) {
const [keyword, target] = line.split(',');
results.push(await checkRank(keyword, target));
await new Promise(r => setTimeout(r, 500));
}
const ranked = results.filter(r => r.position).length;
console.log(`${ranked}/${results.length} ranking, cost: $${(results.length * 0.005).toFixed(2)}`);
}
main().catch(console.error);Sortie attendue
Checking 4000 keywords...
Checked 100/4000
Checked 200/4000
...
Checked 4000/4000
Saved 4000 results to ranks_2026-05-13.csv
Ranking: 2847, Not found: 1153
Total cost: $20.00