La verificación de clasificación por lotes durante la noche le permite realizar un seguimiento de miles de palabras clave sin agotar la cuota de API durante el día ni ralentizar los sistemas de producción. El patrón es sencillo: cargar palabras clave desde un CSV, recorrerlas con un breve retraso, almacenar posiciones en un archivo de resultados y programar el script con cron. Usando la API de Scavio a $0,005 por crédito, una ejecución nocturna de 4000 palabras clave cuesta $20, en comparación con $60+ en SerpAPI o $499,95+ en Semrush Business. Este tutorial crea una canalización por lotes completa durante la noche en Python.
Requisitos previos
- Python 3.9+ instalado
- solicitudes y módulos csv (ambos en stdlib excepto solicitudes)
- Una clave API de Scavio de scavio.dev
- Un archivo CSV con palabras clave para realizar un seguimiento
Guia paso a paso
Paso 1: Prepare su CSV de palabras clave
Cree un CSV con columnas para palabra clave y target_url. El script comprobará cada palabra clave y registrará la clasificación de la URL de destino.
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')Paso 2: Construya la función de verificación de rango
Para cada palabra clave, llame a la API de búsqueda y escanee los resultados orgánicos para su dominio de destino. Devuelve la posición o Ninguno si no se encuentra en los resultados principales.
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}Paso 3: Repasar por lotes todas las palabras clave con limitación de tasa
Lea el CSV, verifique cada palabra clave con un retraso de 0,5 segundos para mantenerse dentro de los límites de tasa y recopile resultados. Para 4000 palabras clave, esto lleva unos 35 minutos.
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 resultsPaso 4: Guardar resultados con marca de tiempo
Escriba los resultados en un CSV con fecha para que pueda realizar un seguimiento de los cambios de clasificación a lo largo del tiempo. Cada ejecución nocturna produce un archivo.
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 filenamePaso 5: Programar con cron para ejecuciones nocturnas
Agregue una entrada cron para ejecutar el script a las 2 a.m. todos los días. El guión finaliza mucho antes de la mañana, por lo que los resultados estarán listos cuando empiece a trabajar.
# 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}')Ejemplo en 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()Ejemplo en 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);Salida esperada
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