Para hacer un estudio masivo de rankings en SERP, reúne una lista de palabras clave, obtén los resultados orgánicos de cada una con POST /api/v1/google, extrae la variable que te interesa (por ejemplo la longitud de la URL en caracteres) en cada posición, agrupa por tramo de posición y calcula una correlación. Eso es todo. Un SEO alemán probó si las URLs cortas posicionan mejor en 27.000 SERPs y el efecto fue mínimo. Puedes zanjar dudas así con datos reales en lugar de una opinión. Este tutorial recorre todo el bucle: lista de palabras clave, llamadas pausadas, extracción de la variable, agregación y una correlación de Pearson. La correlación no es causalidad y las SERPs tienen factores de confusión, así que lee el número como una pista, no como una prueba.
Requisitos previos
- Una clave de API de Scavio (50 créditos gratis al registrarte; cada llamada a Google con light_request:true cuesta 1 crédito)
- Python 3.9+ o Node 18+ con capacidad de enviar JSON por POST (requests/fetch)
- Una lista de 50 a unos pocos miles de palabras clave de tu nicho (un CSV o archivo de texto)
- Saber leer un coeficiente de correlación (un número entre -1 y 1)
Guia paso a paso
Paso 1: Construye tu lista de palabras clave
Un estudio es tan honesto como su muestra. Usa palabras clave reales de tu nicho en lugar de elegirlas a dedo. Cárgalas desde un archivo para que la ejecución sea reproducible. Apunta a varios cientos si quieres una correlación estable.
# keywords.txt has one query per line
with open('keywords.txt') as f:
keywords = [line.strip() for line in f if line.strip()]
print(f'Loaded {len(keywords)} keywords')Paso 2: Obtén una SERP y observa su estructura
Antes de iterar sobre miles, llama al endpoint una vez y mira la respuesta. Usa light_request:true para que cada llamada cueste 1 crédito. Obtienes resultados orgánicos, cada uno con posición, título, enlace y snippet.
import requests
API_KEY = 'YOUR_API_KEY'
resp = requests.post(
'https://api.scavio.dev/api/v1/google',
headers={'Authorization': f'Bearer {API_KEY}'},
json={'query': 'best running shoes', 'light_request': True},
)
data = resp.json()
for r in data['organic_results'][:3]:
print(r['position'], r['link'])Paso 3: Extrae la variable por posición
Aquí la variable es la longitud de la URL en caracteres, pero cámbiala por la longitud del título, la profundidad del dominio o la presencia de HTTPS según tu hipótesis. Guarda un par (posición, valor) por resultado para correlacionar después.
def url_length(result):
return len(result['link'])
def extract_pairs(serp):
return [(r['position'], url_length(r)) for r in serp['organic_results']]Paso 4: Itera sobre las palabras clave y respeta el límite de tasa
Las claves gratuitas permiten 1 petición por segundo; el plan de $30 permite 2. Espera entre llamadas para no recibir bloqueos. Envuelve cada llamada en try/except para que una consulta fallida no detenga toda la ejecución.
import time
all_pairs = []
for kw in keywords:
try:
resp = requests.post(
'https://api.scavio.dev/api/v1/google',
headers={'Authorization': f'Bearer {API_KEY}'},
json={'query': kw, 'light_request': True},
)
all_pairs += extract_pairs(resp.json())
except Exception as e:
print(f'skip {kw}: {e}')
time.sleep(1.0) # 1 req/sec on the free planPaso 5: Agrupa por tramo de posición
Agrupa los resultados en tramos (1-3, 4-6, 7-10) y promedia la variable en cada uno. Esto muestra la tendencia de un vistazo antes de confiar en un único número.
from collections import defaultdict
buckets = defaultdict(list)
for pos, val in all_pairs:
if pos <= 3: buckets['1-3'].append(val)
elif pos <= 6: buckets['4-6'].append(val)
else: buckets['7-10'].append(val)
for name in ['1-3', '4-6', '7-10']:
vals = buckets[name]
print(name, round(sum(vals)/len(vals), 1))Paso 6: Calcula la correlación
Un coeficiente de Pearson entre posición y variable indica dirección y fuerza. Cerca de 0 significa sin relación; el estudio alemán de longitud de URL quedó cerca de 0. Recuerda: abundan los factores de confusión y la correlación no es causalidad.
import statistics
def pearson(xs, ys):
mx, my = statistics.mean(xs), statistics.mean(ys)
num = sum((x-mx)*(y-my) for x, y in zip(xs, ys))
den = (sum((x-mx)**2 for x in xs) * sum((y-my)**2 for y in ys)) ** 0.5
return num/den if den else 0.0
positions = [p for p, _ in all_pairs]
values = [v for _, v in all_pairs]
print('correlation:', round(pearson(positions, values), 3))Ejemplo en Python
import time
import statistics
from collections import defaultdict
import requests
API_KEY = 'YOUR_API_KEY'
ENDPOINT = 'https://api.scavio.dev/api/v1/google'
def fetch_serp(query):
resp = requests.post(
ENDPOINT,
headers={'Authorization': f'Bearer {API_KEY}'},
json={'query': query, 'light_request': True},
)
resp.raise_for_status()
return resp.json()
def url_length(result):
return len(result['link'])
def pearson(xs, ys):
mx, my = statistics.mean(xs), statistics.mean(ys)
num = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
den = (sum((x - mx) ** 2 for x in xs) * sum((y - my) ** 2 for y in ys)) ** 0.5
return num / den if den else 0.0
def main():
with open('keywords.txt') as f:
keywords = [line.strip() for line in f if line.strip()]
print(f'Loaded {len(keywords)} keywords')
pairs = []
for kw in keywords:
try:
serp = fetch_serp(kw)
pairs += [(r['position'], url_length(r)) for r in serp['organic_results']]
except Exception as e:
print(f'skip {kw}: {e}')
time.sleep(1.0) # 1 req/sec on the free plan; 0.5 on the $30 plan
buckets = defaultdict(list)
for pos, val in pairs:
if pos <= 3:
buckets['1-3'].append(val)
elif pos <= 6:
buckets['4-6'].append(val)
else:
buckets['7-10'].append(val)
print('avg URL length by position bucket:')
for name in ['1-3', '4-6', '7-10']:
vals = buckets[name]
if vals:
print(f' {name}: {round(sum(vals) / len(vals), 1)}')
positions = [p for p, _ in pairs]
values = [v for _, v in pairs]
print('correlation (position vs url length):', round(pearson(positions, values), 3))
if __name__ == '__main__':
main()Ejemplo en JavaScript
import fs from 'node:fs';
const API_KEY = 'YOUR_API_KEY';
const ENDPOINT = 'https://api.scavio.dev/api/v1/google';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function fetchSerp(query) {
const resp = await fetch(ENDPOINT, {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, light_request: true }),
});
return resp.json();
}
function pearson(xs, ys) {
const mx = xs.reduce((a, b) => a + b, 0) / xs.length;
const my = ys.reduce((a, b) => a + b, 0) / ys.length;
let num = 0, dx = 0, dy = 0;
for (let i = 0; i < xs.length; i++) {
num += (xs[i] - mx) * (ys[i] - my);
dx += (xs[i] - mx) ** 2;
dy += (ys[i] - my) ** 2;
}
const den = Math.sqrt(dx * dy);
return den ? num / den : 0;
}
const keywords = fs.readFileSync('keywords.txt', 'utf8').split('\n').map((s) => s.trim()).filter(Boolean);
const pairs = [];
for (const kw of keywords) {
try {
const serp = await fetchSerp(kw);
for (const r of serp.organic_results) pairs.push([r.position, r.link.length]);
} catch (e) {
console.log(`skip ${kw}: ${e}`);
}
await sleep(1000); // 1 req/sec on the free plan
}
const positions = pairs.map((p) => p[0]);
const values = pairs.map((p) => p[1]);
console.log('correlation:', pearson(positions, values).toFixed(3));Salida esperada
Loaded 500 keywords
avg URL length by position bucket:
1-3: 48.2
4-6: 51.7
7-10: 53.9
correlation (position vs url length): 0.072