Bulk-DR für eine Kundenliste als 40-Zeilen-Python-Skript
Sie haben eine Tabelle mit Domains und ein Dashboard, das eine Zahl pro Zeile benötigt. Ein Backlink-Domain-Checker für diesen Job ist im Wesentlichen Infrastruktur, und dieses Skript ist genau diese Infrastruktur.

Das Skript
Es liest domains.txt, sendet 200 Domains pro Anfrage, speichert jede Rohantwort in SQLite und gibt die Kosten des Laufs aus. Wir speichern den gesamten Payload, weil Feldnamen am besten aus einer echten Antwort kopiert werden, nicht aus einem Blogbeitrag.
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"))Was jeder Teil tut
action=bulk_dr
Ein Credit pro Aufruf. Die dokumentierte Obergrenze liegt bei 2.000 Domains pro Aufruf.
domains=
Ein GET-Parameter hier. Der Endpunkt akzeptiert auch einen POST-Body, was die bessere Wahl ist, sobald die Listen länger werden. Testen Sie den Separator mit zwei Domains, bevor Sie einen vollständigen Lauf starten.
BATCH = 200
Unsere Gewohnheit, keine Grenze: 200 kurze Domains halten die URL deutlich unter den gängigen Proxy-Grenzen.
_credits
Vorhanden bei authentifizierten Antworten: die Kosten des Aufrufs und der verbleibende Betrag. Das Ausgeben jedes Batches ist die günstigste Audit-Spur.
Rohes body-Feld
Sie parsen später, einmalig, anhand einer gespeicherten Probe. Eine Schemaänderung auf unserer Seite kostet Sie dann eine Abfrage, keinen Neulauf.

Ein hypothetischer Lauf mit der Berechnung
Dies ist ein Beispiel, keine Kundenstory. Stellen Sie sich eine Agentur vor, die 1.800 Kunden- und Prospekt-Domains in einem Google Sheet verfolgt und jeden Montag ein DR-Update benötigt.
Bei 200 Domains pro Batch sind das 9 Anfragen. Neun Anfragen zu je 1 Credit ergeben 9 Credits, oder $0,0009 pro Lauf, und über 52 Montage hinweg beträgt die Gesamtsumme 468 Credits, was auf unter fünf Cent gerundet wird.
Kosten sind nicht das Limit. Frische ist es. Der Index wird etwa monatlich neu aufgebaut, daher zeigen wöchentliche Läufe drei von vier Wochen denselben Wert, und ein monatlicher Lauf erfasst dieselbe Bewegung für etwa ein Viertel der Aufrufe.
Führen Sie es also monatlich aus und behalten Sie das wöchentliche Sheet für manuelle Bearbeitungen bei. Details dazu, warum der Score stillstehen kann, finden Sie in der DR-Referenz.
Dinge, die in diesem Skript schiefgehen können
| Symptom | Ursache | Lösung |
|---|---|---|
SystemExit: HTTP 402 | Guthaben ist null | Aufladen über die Kontoseite, Neustart; abgeschlossene Batches sind bereits in SQLite gespeichert |
SystemExit: HTTP 401 | Env-Var leer oder Tippfehler im Key | Print len(KEY) und prüfen Sie das Präfix sk_ |
ok=false bei Batch 3 | Fehlerhafter Wert in der Liste, z. B. eine vollständige URL | Schema und Pfad vor dem Senden entfernen, siehe Fehler-Tabelle |
| Gleiches DR wie letzte Woche | Index wurde noch nicht neu aufgebaut | Erwartet; monatlich vergleichen |
| Lange URL abgelehnt | Batch zu groß für GET | BATCH verringern oder auf POST-Body umstellen |
Batch-Größe, GET und POST für lange Listen
Die Obergrenze liegt bei 2.000 Domains pro Aufruf, und das Skript sendet ein Zehntel davon. Jeder Aufruf kostet dieselbe feste Credit-Anzahl, daher ist die Batch-Größe eine Frage von Fehlern und Payload, nicht von Preis.
Warum 200 und nicht 2.000
Ein kleinerer Batch schlägt günstig fehl. Wenn ein Lauf in der Mitte stoppt, ist der Rest, der erneut ausgeführt werden muss, nur ein Batch lang, nicht die gesamte Liste.
Kürzere Listen halten auch jeden gespeicherten Body klein genug, um ihn bei falschen Zahlen mit dem Auge zu scannen, was wichtiger ist als ein paar gespeicherte Aufrufe.
Wenn Sie die Batch-Größe später erhöhen, tun Sie dies einmalig in einer Konstanten und führen Sie den Zwei-Domain-Test erneut durch. Eine größere Zahl ist kein Grund, den Test zu überspringen.
Planen Sie den Batch gegen die Laufzeit, die Sie tolerieren können, nicht gegen die Obergrenze. Ein nächtlicher Job, der vor einem Morgenbericht abgeschlossen sein muss, kann kleinere, sicherere Batches vertragen als ein einmaliger Nachhol-Lauf.
GET für kurze Listen, POST für lange
Die GET-Form packt jede Domain in die URL. Das funktioniert, bis ein Proxy oder eine Log-Zeile die Abfrage abschneidet, weshalb die Tabelle oben empfiehlt, zuerst den Batch zu verringern. Sobald die Listen länger werden, senden Sie denselben domains-Wert in einem POST-Body.
Testen Sie den Separator und die Codierung mit zwei Domains vor einem vollständigen Lauf. Ein fehlendes Komma zeigt sich im ersten Batch, und es ist günstiger, es dort zu sehen.
Ein POST-Body hält die Liste auch aus den Zugriffslogs, die viele Hosts standardmäßig führen. Das ist ein kleiner Privatsphäre-Gewinn für Kundenlisten und kostet eine zusätzliche Header-Zeile im Skript.
Wenn Sie auf POST umstellen, behalten Sie die GET-Version des Skripts als Fallback für kurze Listen bei. Zwei Codepfade klingen aufwendig, aber eine kleine Funktion, die je nach Länge den einen oder anderen wählt, ist leicht lesbar und testbar.
Bereinigen und Duplikate entfernen vor der ersten Anfrage
Leerzeichen entfernen, Host klein schreiben und Duplikate löschen, bevor etwas Ihre Maschine verlässt. Duplikate belegen Platz unter der 2.000-Domain-Obergrenze und sorgen dafür, dass die Anzahl in Ihrem Sheet nicht mit der im Antwort-Objekt übereinstimmt.
Wählen Sie eine Regel für www. und wenden Sie sie in einer einzigen Funktion an, damit dieselbe Seite nie unter zwei Schreibweisen abgefragt wird. Jeder Backlink-Domain-Checker, der über einige hundert Namen hinaus skaliert, benötigt diese Disziplin.
Was der Lauf-Log ausgeben sollte
Geben Sie den _credits-Wert nach jedem Batch aus und schreiben Sie die Batch-Nummer, die Domain-Anzahl und die Zeit daneben. Diese drei Zahlen beantworten fast jede Frage, die ein Finanzleser zu einem Lauf stellen wird.
Ein Lauf, der erst am Ende etwas ausdruckt, gibt Ihnen keinen Hinweis darauf, wann er hängt. Eine Zeile pro Batch ist günstig und verwandelt einen stillen Hang in eine Batch-Nummer, die Sie benennen können.
Halten Sie den Log im selben Ordner wie die SQLite-Datei, damit ein Backup beides kopiert. Ein Log, das woanders gespeichert ist, ist das, das bei einem Vorfall fehlt.

Drei Erweiterungen, die den Nachmittag wert sind
Speichern Sie die Batch-Nummer neben dem Zeitstempel. Sie ist bereits in der Tabelle oben enthalten und ermöglicht es Ihnen, nur den Batch erneut auszuführen, der nach einem 402-Mid-Run fehlgeschlagen ist.
Erstellen Sie eine kleine Ansicht über die Roh-Tabelle, sobald Sie eine echte Antwort inspiziert haben: Domain, Score, abgerufen-am. Dashboards lesen die Ansicht, und die Roh-Spalte bleibt als Beweis, wenn eine Zahl jemals angezweifelt wird.
Führen Sie es über GitHub Actions oder einen einfachen Cron auf einem kleinen VPS aus. Beides ist in Ordnung. Wichtig ist, dass der Job bei jedem Fehler mit einem Nicht-Null-Exit-Code beendet wird, denn ein stiller Fehler sieht drei Wochen später wie ein veralteter Chart aus.
Das Sortieren der Ausgabe ist eine Frontend-Aufgabe. Sortieren Sie das Portfolio nach Score, bucketen Sie es oder vergleichen Sie es mit dr_distribution (1 Credit), um zu sehen, wo jeder Kunde im Vergleich zu allen 181.132.097 Domains steht.
Feld-Drift und die Roh-Tabelle
Ein langlaufender Job schlägt leise fehl, wenn sich ein Feld in der Struktur ändert. Die Roh-Spalte sorgt dafür, dass dieser Fehler laut und kostengünstig zu beheben bleibt.
Speichern Sie den Roh-Body zuerst
Die Roh-Spalte ist das Sicherheitsnetz. Wenn ein Feld umbenannt wird oder ein neues erscheint, führen Sie eine Abfrage über gespeicherte Antworten durch, anstatt die gesamte Liste erneut zu kaufen. Das spätere Parsen ist günstig, während das Neukaufen nicht ist.
Halten Sie auch eine gespeicherte Probe pro Woche in einer eigenen Datei bereit. Wenn das Parsen fehlschlägt, vergleichen Sie die Probe dieser Woche mit der letzten, die funktioniert hat.
Schreiben Sie den Body genau so, wie er empfangen wurde, ohne Formatierung oder Beschneiden. Jede Änderung, die Sie vor dem Speichern am Text vornehmen, ist eine Änderung, die Sie später nicht rückgängig machen können.
Wie sich ein Drift in der Praxis zeigt
Sagen wir, ein Dashboard zeigt plötzlich leere Scores für einen Batch. Die Roh-Zeile ist noch vorhanden. Sie öffnen sie, vergleichen die Schlüssel mit der Probe von letzter Woche und finden den Unterschied in wenigen Minuten.
Die Lösung ist dann eine einzige Zeile in der Ansicht, kein Neulauf der gesamten Liste. Das ist der ganze Punkt des Speicherns des Bodies.
Halten Sie eine kurze Notiz neben der Ansicht, welches Feld sie liest und wann Sie sie zuletzt mit einer echten Antwort abgeglichen haben. Die Notiz dauert eine Minute zu schreiben und spart später eine lange Suche.
Migrationen mit den Daten ordnen
Erstellen Sie die Roh-Tabelle vor der Ansicht und die Ansicht vor jeder Dashboard-Abfrage. Halten Sie die Create-Anweisungen in einer Datei, führen Sie sie in der richtigen Reihenfolge aus und protokollieren Sie die Batch-Nummer mit jeder Zeile.
Getrennte Datenbanken pro Kunde drifteten genauso wie eine gemeinsam genutzte Datenbank. Die Regel bleibt dieselbe: ein gespeicherter Body pro Aufruf und ein Ort zum Parsen.
Den Parser neben die Tests legen
Legen Sie die Parse-Funktion in dasselbe Repository wie einen Test, der einen gespeicherten Body liest. Der Test ist der Ort, an dem ein gedriftetes Feld zuerst auffällt, lange bevor ein Chart es zeigt.
Nennen Sie die Felder, die der Parser erwartet, in einer Konstanten nahe dem Dateianfang. Ein Reviewer kann so alle Abhängigkeiten von der Antwortstruktur auf einem Bildschirm sehen.
Erster Lauf, in der richtigen Reihenfolge
- 1
Eine Domain testen
Rufen Sie
bulk_drmit einer bekannten Domain auf und lesen Sie die gesamte Antwort. Notieren Sie die Schlüssel, bevor Sie mit dem Parsen beginnen. - 2
Zwei Batches ausführen
Geben Sie dem Skript eine Liste von 400 Domains, damit genau zwei Batches laufen. Prüfen Sie, ob der
_credits-Abgleich zwei Aufrufe zu je einem Credit entspricht. - 3
Gespeicherte Zeilen zählen
Öffnen Sie die SQLite-Datei und vergleichen Sie die Zeilenanzahl mit der Batch-Anzahl. Eine Diskrepanz bedeutet, dass ein Batch übersprungen oder doppelt gespeichert wurde, und Sie finden das heraus, bevor das Dashboard es merkt.
- 4
Vollständige Liste planen
Erst nachdem die Testläufe sauber durchgelaufen sind, fügen Sie die vollständige Liste und den Scheduler hinzu. Halten Sie den Nicht-Null-Exit bei, damit ein Fehler in der ersten Nacht laut wird.
Bevor Sie es planen
- Liste deduplizieren und klein schreiben
- Entscheiden Sie einmalig in einer Funktion, ob
www.bleibt oder nicht - Halten Sie den Key in einem Secret-Store, nicht in crontab
- Bei einem Nicht-Null-Exit-Code alarmieren
- Berechnen Sie die Credit-Budget-Zahlen für Ihr eigenes Volumen