DR masivo para una lista de clientes, como un trabajo de Python de 40 líneas
Tienes una hoja de cálculo con dominios y un dashboard que necesita un número por fila. Un verificador de dominios con backlinks para ese trabajo es principalmente infraestructura, y este script es esa infraestructura.

El script
Lee domains.txt, envía 200 dominios por solicitud, guarda cada respuesta cruda en SQLite e imprime el costo de la ejecución. Almacenamos la carga completa porque los nombres de los campos se copian mejor de una respuesta real que de una publicación de blog.
import os, sqlite3, time, json, requests
BASE = "https://seomasters.dev/api.php"
KEY = os.environ["SEO_API_KEY"]
BATCH = 200
db = sqlite3.connect("dr.db")
db.execute("create table if not exists raw (ts int, batch text, body text)")
domains = [d.strip() for d in open("domains.txt") if d.strip()]
spent = 0
for i in range(0, len(domains), BATCH):
chunk = domains[i:i + BATCH]
for attempt in range(4):
try:
r = requests.get(BASE, timeout=30,
params={"action": "bulk_dr", "domains": ",".join(chunk)},
headers={"Authorization": "Bearer " + KEY})
break
except requests.RequestException:
time.sleep(2 ** attempt)
else:
raise SystemExit(f"network gave up at batch {i}")
if r.status_code in (401, 402):
raise SystemExit(f"HTTP {r.status_code}: fix the key or top up, do not retry")
body = r.json()
if not body.get("ok"):
raise SystemExit(f"ok=false at batch {i}: {json.dumps(body)[:300]}")
db.execute("insert into raw values (?,?,?)", (int(time.time()), str(i), r.text))
db.commit()
print(body.get("_credits"))Qué hace cada parte
action=bulk_dr
Un crédito por llamada. El límite documentado es de 2.000 dominios por llamada.
domains=
Un parámetro GET aquí. El endpoint también acepta un cuerpo POST, que es la mejor opción cuando las listas son largas. Prueba el separador con dos dominios antes de una ejecución completa.
BATCH = 200
Nuestra costumbre, no un límite: 200 dominios cortos mantienen la URL muy por debajo de los límites comunes de los proxies.
_credits
Presente en las respuestas autenticadas: el costo de la llamada y lo que queda. Imprimirlo en cada batch es la pista de auditoría más barata.
Columna body cruda
Lo analizas más tarde, una sola vez, contra una muestra almacenada. Un cambio de esquema por nuestra parte te costará una consulta, no una nueva ejecución.

Una ejecución hipotética, con la aritmética
Esto es un ejemplo, no una historia de cliente. Imagina una agencia que rastrea 1.800 dominios de clientes y prospectos en una Hoja de Google y quiere actualizar el DR cada lunes.
A 200 dominios por batch, son 9 solicitudes. Nueve solicitudes a 1 crédito cada una son 9 créditos, o $0.0009 por ejecución, y durante 52 lunes el total es de 468 créditos, que redondea a menos de cinco centavos.
El costo no es la restricción. La frescura sí. El índice se reconstruye aproximadamente mensualmente, por lo que las ejecuciones semanales mostrarán los mismos valores tres de cada cuatro semanas, y una ejecución mensual captura el mismo movimiento por aproximadamente una cuarta parte de las llamadas.
Así que ejecútalo mensualmente y mantén la hoja semanal para ediciones humanas. Los detalles sobre por qué la puntuación puede permanecer igual están en la referencia de DR.
Cosas que salen mal en este script
| Síntoma | Causa | Solución |
|---|---|---|
SystemExit: HTTP 402 | El saldo es cero | Recarga desde la página de la cuenta, vuelve a ejecutar; los batches finalizados ya están en SQLite |
SystemExit: HTTP 401 | Variable de entorno vacía o error tipográfico en la clave | Imprime len(KEY) y verifica el prefijo sk_ |
ok=false en el batch 3 | Valor incorrecto en la lista, como una URL completa | Elimina el esquema y la ruta antes de enviar, consulta la tabla de errores |
| Mismo DR que la semana pasada | El índice aún no se ha reconstruido | Esperado; compara mensualmente |
| URL larga rechazada | Batch demasiado grande para GET | Reduce BATCH o pasa a un cuerpo POST |
Tamaño del batch, GET y POST para listas largas
El límite es de 2.000 dominios por llamada, y el script envía una décima parte de eso. Cada llamada cuesta el mismo crédito fijo, por lo que el tamaño del batch es una cuestión de fallos y carga útil, no de precio.
Por qué 200 y no 2.000
Un batch más pequeño falla de forma barata. Si una ejecución se detiene a mitad de camino, la cola restante a rehacer tiene la longitud de un batch, no la de toda la lista.
Las listas más cortas también mantienen cada cuerpo guardado lo suficientemente pequeño como para escanearlo a ojo cuando un número parece incorrecto, y eso es más importante que unas pocas llamadas guardadas.
Si más adelante aumentas el tamaño del batch, hazlo una vez, en una constante, y vuelve a ejecutar la prueba con dos dominios. Un número mayor no es razón para saltarse la prueba.
Planifica el batch según la duración de ejecución que puedas tolerar, no según el límite. Un trabajo nocturno que debe terminar antes de un informe matutino puede permitirse batches más pequeños y seguros que un relleno único.
GET para listas cortas, POST para las largas
La forma GET pone cada dominio en la URL. Eso funciona hasta que un proxy o una línea de registro truncan la consulta, por eso la tabla de arriba dice reducir el batch primero. Una vez que las listas crecen, envía el mismo valor de domains en un cuerpo POST.
Prueba el separador y la codificación con dos dominios antes de una ejecución completa. Una coma perdida aparece en el primer batch, y es más barato verla allí.
Un cuerpo POST también mantiene la lista fuera de los registros de acceso que muchos hosts guardan por defecto. Esa es una pequeña ganancia de privacidad para las listas de clientes, y cuesta una línea extra de cabecera en el script.
Cuando cambies a POST, mantén la versión GET del script como respaldo para listas cortas. Dos rutas de código suenan pesadas, pero una única función pequeña que elige una por longitud es fácil de leer y de probar.
Recorta y elimina duplicados antes de la primera solicitud
Elimina espacios en blanco, convierte el host a minúsculas y elimina duplicados antes de que cualquier cosa salga de tu máquina. Los duplicados ocupan espacio bajo el límite de 2.000 dominios, y hacen que el conteo en tu hoja de cálculo difiera del conteo en la respuesta.
Elige una regla para www. y aplícala en una sola función, para que el mismo sitio nunca se busque con dos ortografías. Cualquier verificador de dominios con backlinks que escale más allá de unos cientos de nombres necesita esa disciplina.
Lo que debería decir el registro de ejecución
Imprime el valor _credits después de cada batch, y escribe el número del batch, el conteo de dominios y la hora junto a él. Esos tres números responden a casi todas las preguntas que un lector financiero hará sobre una ejecución.
Una ejecución que no imprime nada hasta el final no te da ninguna pista cuando se queda colgada. Una línea por batch es barata, y convierte un bloqueo silencioso en un número de batch que puedes identificar.
Mantén el registro en la misma carpeta que el archivo SQLite, para que una copia de seguridad copie ambos. Un registro que vive en otro lugar es el que falta durante un incidente.

Tres extensiones que valen la tarde
Guarda el número del batch junto a la marca de tiempo. Ya está en la tabla de arriba, y te permite volver a ejecutar solo el batch que falló después de un 402 a mitad de ejecución.
Escribe una vista pequeña sobre la tabla cruda una vez que hayas inspeccionado una respuesta real: dominio, puntuación, fecha de obtención. Los dashboards leen la vista, y la columna cruda se mantiene como tu evidencia si algún número es cuestionado.
Ejecútalo desde GitHub Actions o un cron simple en un VPS pequeño. Cualquiera está bien. Lo importante es que el trabajo salga con código no cero ante cualquier fallo, porque un fallo silencioso parece un gráfico desactualizado tres semanas después.
Ordenar la salida es un asunto de front-end. Clasifica el portafolio por puntuación, agrúpalo o compáralo con dr_distribution (1 crédito) para ver dónde se sitúa cada cliente entre los 181.132.097 dominios.
Deriva de campos y la tabla cruda
Un trabajo de larga duración falla silenciosamente cuando un campo cambia de forma. La columna cruda está ahí para que el fallo sea ruidoso y barato de arreglar.
Guarda primero el cuerpo crudo
La columna cruda es la red de seguridad. Si se renombra un campo o aparece uno nuevo, vuelves a ejecutar una consulta sobre las respuestas almacenadas en lugar de pagar por toda la lista de nuevo. Analizar más tarde es barato, mientras que volver a comprar no lo es.
Mantén una muestra guardada por semana en un archivo propio también. Cuando un análisis falla, comparas la muestra de esta semana con la última que funcionó.
Escribe el cuerpo exactamente como se recibió, sin formato bonito ni recortes. Cualquier cambio que hagas al texto antes de guardarlo es un cambio que no podrás deshacer más tarde.
Cómo se ve una deriva en la práctica
Digamos que un dashboard muestra repentinamente puntuaciones vacías para un batch. La fila cruda sigue ahí. La abres, comparas sus claves con la muestra de la semana pasada y encuentras la diferencia en unos minutos.
La solución es entonces una sola línea en la vista, no una nueva ejecución de toda la lista. Ese es todo el punto de guardar el cuerpo.
Mantén una nota corta junto a la vista indicando qué campo lee y cuándo la verificaste por última vez contra una respuesta real. La nota tarda un minuto en escribirse y ahorra una búsqueda larga más tarde.
Ordena las migraciones con los datos
Crea la tabla cruda antes que la vista, y la vista antes que cualquier consulta de dashboard. Mantén las sentencias de creación en un solo archivo, ejecútalas en orden y registra el número del batch con cada fila.
Bases de datos separadas por cliente derivan de la misma manera que una base de datos compartida. La regla sigue siendo la misma: un cuerpo almacenado por llamada, y un lugar para analizarlo.
Mantén el analizador junto a las pruebas
Pon la función de análisis en el mismo repositorio que una prueba que lee un cuerpo guardado. La prueba es el lugar donde un campo derivado aparece primero, mucho antes que en un gráfico.
Nombra los campos que espera el analizador en una constante cerca de la parte superior del archivo. Un revisor puede entonces ver todas las dependencias en la forma de la respuesta en una sola pantalla.
Primera ejecución, en orden
- 1
Prueba un dominio
Llama a
bulk_drcon un dominio que conozcas, y lee toda la respuesta. Anota las claves antes de escribir cualquier código de análisis. - 2
Ejecuta dos batches
Dale al script una lista de 400 dominios, para que se ejecuten exactamente dos batches. Verifica que la caída de
_creditscoincida con dos llamadas a un crédito cada una. - 3
Cuenta las filas almacenadas
Abre el archivo SQLite y compara el conteo de filas con el conteo de batches. Una discrepancia significa que se omitió un batch o se almacenó dos veces, y lo descubres antes que el dashboard.
- 4
Programa la lista completa
Solo después de que las pruebas de ejecución estén limpias, añade la lista completa y el programador. Mantén la salida no cero, para que un fallo sea ruidoso desde la primera noche.
Antes de programarlo
- Elimina duplicados y convierte a minúsculas la lista primero
- Decide una vez, en una función, si
www.se queda o se va - Mantén la clave en un almacén de secretos, no en crontab
- Alerta ante un código de salida no cero
- Ejecuta los números del presupuesto de créditos para tu propio volumen