Masowe DR dla listy klientów jako 40-wierszowy skrypt Pythona
Masz arkusz z domenami i dashboard, który wymaga jednej wartości w każdym wierszu. Narzędzie do sprawdzania domen backlinków do tego zadania to głównie „rury”, a ten skrypt jest właśnie tymi rurami.

Skrypt
Odczytuje domains.txt, wysyła 200 domen w jednym żądaniu, zapisuje każdą surową odpowiedź w SQLite i wypisuje koszt wykonania. Przechowujemy pełną zawartość, ponieważ nazwy pól najlepiej kopiować z rzeczywistej odpowiedzi, a nie z wpisu na blogu.
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"))Co robi każda część
action=bulk_dr
Jeden kredyt za wywołanie. Dokumentowany limit to 2000 domen na wywołanie.
domains=
Parametr GET. Endpoint akceptuje również ciało POST, co jest lepszą opcją przy długich listach. Przetestuj separator na dwóch domenach przed pełnym uruchomieniem.
BATCH = 200
Nasza praktyka, nie limit: 200 krótkich domen utrzymuje URL znacznie poniżej typowych limitów proxy.
_credits
Obecne w odpowiedziach uwierzytelnionych: koszt wywołania i pozostałe środki. Wypisywanie ich w każdej partii to najtańszy ślad audytowy.
Kolumna surowego body
Parsujesz później, jednorazowo, na podstawie zapisanej próbki. Zmiana schematu po naszej stronie kosztuje Cię wtedy tylko zapytanie, a nie ponowne uruchomienie.

Hipotetyczne uruchomienie z obliczeniami
To jest przykład, nie historia klienta. Wyobraź sobie agencję, która śledzi 1800 domen klientów i potencjalnych klientów w arkuszu Google i chce odświeżać DR każdego poniedziałku.
Przy 200 domenach w partii to 9 żądań. Dziewięć żądań po 1 kredencie to 9 kredytów, czyli $0.0009 za uruchomienie, a w ciągu 52 poniedziałków suma to 468 kredytów, co zaokrąglone daje mniej niż pięć centów.
Koszt nie jest ograniczeniem. Ograniczeniem jest świeżość. Indeks rebuilduje się około raz w miesiącu, więc cotygodniowe uruchomienia będą pokazywać te same wartości przez trzy tygodnie na cztery, a miesięczne uruchomienie odzwierciedla te same zmiany przy około ćwierci liczby wywołań.
Więc uruchamiaj go miesięcznie i utrzymuj cotygodniowy arkusz do ręcznych edycji. Szczegóły dotyczące tego, dlaczego wynik może stać w miejscu, znajdziesz w referencji DR.
Co może pójść nie tak w tym skrypcie
| Objaw | Przyczyna | Naprawa |
|---|---|---|
SystemExit: HTTP 402 | Saldo wynosi zero | Doładuj konto na stronie profilu, uruchom ponownie; ukończone partie są już w SQLite |
SystemExit: HTTP 401 | Zmienna środowiskowa pusta lub literówka w kluczu | Wypisz len(KEY) i sprawdź prefiks sk_ |
ok=false na partii 3 | Nieprawidłowa wartość na liście, np. pełny URL | Usuń schemat i ścieżkę przed wysłaniem, zobacz tabelę błędów |
| To samo DR co w zeszłym tygodniu | Indeks nie został jeszcze odbudowany | Oczekiwane; porównuj miesięcznie |
| Długi URL odrzucony | Partia za duża dla GET | Zmniejsz BATCH lub przejdź na ciało POST |
Rozmiar partii, GET i POST dla długich list
Limit to 2000 domen na wywołanie, a skrypt wysyła jedną dziesiątą tej wartości. Każde wywołanie kosztuje tyle samo kredytu, więc rozmiar partii to kwestia awarii i ładunku, a nie ceny.
Dlaczego 200, a nie 2000
Mniejsza partia awariuje taniej. Jeśli uruchomienie zatrzyma się w połowie, ogon do ponownego uruchomienia ma długość jednej partii, a nie całej listy.
Krótsze listy utrzymują każdą zapisaną zawartość na małym poziomie, co pozwala na przeglądnięcie jej wzrokowo, gdy jakaś wartość wydaje się niepoprawna, i to ma większe znaczenie niż kilka oszczędzonych wywołań.
Jeśli później zwiększysz rozmiar partii, zrób to raz, w jednej stałej, i ponownie przetestuj na dwóch domenach. Większa liczba nie jest powodem do pominięcia testu.
Zaplanuj partię względem długości uruchomienia, jaką jesteś w tolerować, a nie względem limitu. Zadanie dzienne, które musi zakończyć się przed porannym raportem, może pozwolić sobie na mniejsze, bezpieczniejsze partie niż jednorazowe uzupełnianie danych.
GET dla krótkich list, POST dla długich
Forma GET umieszcza każdą domenę w URL. Działa to, dopóki proxy lub linia logu nie uetkują zapytania, dlatego tabela powyżej sugeruje najpierw zmniejszenie partii. Gdy listy rosną, wyślij tę samą wartość domains w ciele POST.
Przetestuj separator i kodowanie na dwóch domenach przed pełnym uruchomieniem. Przypadkowy przecinek pojawi się w pierwszej partii, a taniej go zauważyć tam.
Ciało POST utrzymuje również listę poza logami dostępu, które wiele hostów zapisuje domyślnie. To mała korzyść prywatności dla list klientów, a kosztuje jedną dodatkową linię nagłówka w skrypcie.
Po przełączeniu się na POST zachowaj wersję GET skryptu jako rezerwę dla krótkich list. Dwie ścieżki kodu brzzą ciężko, ale jedna mała funkcja wybierająca jedną z nich na podstawie długości jest łatwa do odczytania i testowania.
Wycinanie i usuwanie duplikatów przed pierwszym żądaniem
Usuń białe znaki, zamień host na małe litery i usuń duplikaty przed wysłaniem czegokolwiek z Twojej maszyny. Duplikaty zajmują miejsce pod limitem 2000 domen i powodują rozbieżności między licznikiem w Twoim arkuszu a licznikiem w odpowiedzi.
Wybierz jedną regułę dla www. i zastosuj ją w jednej funkcji, aby ta sama strona nigdy nie była wyszukiwana pod dwiema pisowniami. Każde narzędzie do sprawdzania domen backlinków, które skaluje się powyżej kilku setek nazw, wymaga tej dyscypliny.
Co powinien zawierać dziennik uruchomienia
Wypisuj wartość _credits po każdej partii i zapisuj numer partii, liczbę domen i czas obok niej. Te trzy liczby odpowiadają na prawie każde pytanie, jakie czytelnik z działu finansów zada o uruchomienie.
Uruchomienie, które nic nie wypisuje do końca, nie daje Ci pojęcia, kiedy utknęło. Linia na partię jest tania i zamienia ciche zawieszenie w numer partii, który możesz wskazać.
Przechowuj log w tym samym folderze co plik SQLite, aby jedna kopia zapasowa kopiowała oba. Log znajdujący się gdzieś indziej to ten, który zaginie podczas incydentu.

Trzy rozszerzenia warte popołudnia
Zapisz numer partii obok znacznika czasu. Jest już w tabeli powyżej i pozwala na ponowne uruchomienie tylko partii, która uległa awarii po 402 w trakcie działania.
Stwórz małą widokową tabelę nad surową tabelą, gdy tylko zbadasz rzeczywistą odpowiedź: domena, wynik, data pobrania. Dashboardy odczytują widok, a kolumna surowa pozostaje Twoim dowodem, jeśli jakaś wartość zostanie kiedykolwiek zakwestionowana.
Uruchamiaj go z GitHub Actions lub zwykłego cron na małym VPS. Oboje są OK. Najważniejsze, aby zadanie kończyło się kodem niezerowym w przypadku każdej awarii, ponieważ cicha awaria wygląda jak przestarzały wykres trzy tygodnie później.
Sortowanie wyniku to kwestia front-endu. Posortuj portfel według wyniku, podziel go na przedziały lub porównaj go z dr_distribution (1 kredyt), aby zobaczyć, gdzie znajduje się każdy klient wśród wszystkich 181 132 097 domen.
Drgania pól i surowa tabela
Długotrwałe zadanie cicho awariuje, gdy zmieni się kształt pola. Kolumna surowa istnieje po to, aby awaria była głośna i tania do naprawy.
Najpierw zapisz surowe ciało
Kolumna surowa to sieć bezpieczeństwa. Jeśli pole zostanie zmienione lub pojawi się nowe, ponownie uruchamiasz zapytanie nad zapisanymi odpowiedziami zamiast płacić za całą listę ponownie. Parsowanie później jest tanie, podczas gdy ponowne kupowanie nie jest.
Przechowuj jedną zapisaną próbkę na tydzień w osobnym pliku. Gdy parsowanie się zepsuje, porównaj próbkę z tego tygodnia z ostatnią, która działała.
Zapisz ciało dokładnie tak, jak zostało odebrane, bez formatowania i bez przycinania. Każda zmiana, którą wprowadzisz w tekście przed zapisaniem, jest zmianą, której nie będziesz mógł cofnąć później.
Jak drgania wyglądają w praktyce
Załóżmy, że dashboard nagle pokazuje puste wyniki dla jednej partii. Wiersz surowy jest nadal obecny. Otwierasz go, porównujesz jego klucze z próbką z zeszłego tygodnia i znajdujesz różnicę w ciągu kilku minut.
Naprawa to wtedy jedna linia w widoku, a nie ponowne uruchomienie całej listy. O to właśnie chodzi przy przechowywaniu ciała.
Zachowaj krótką notatkę obok widoku mówiącą, które pole odczytuje i kiedy ostatnio sprawdzałeś ją na podstawie rzeczywistej odpowiedzi. Notatka zajmuje minutę do napisania i oszczędza długie wyszukiwanie później.
Kolejność migracji z danymi
Stwórz tabelę surową przed widokiem, a widok przed jakimkolwiek zapytaniem dashboardu. Trzymaj instrukcje tworzenia w jednym pliku, uruchamiaj je w kolejności i zapisuj numer partii z każdym wierszem.
Oddzielne bazy danych dla klientów drgają tak samo, jak jedna wspólna baza danych. Zasada pozostaje ta sama: jedno przechowywane ciało na wywołanie i jedno miejsce do jego parsowania.
Trzymaj parser obok testów
Umieść funkcję parsowania w tym samym repozytorium co test, który odczytuje jedną zapisaną zawartość. Test jest miejscem, w którym zdrętwiałe pole pojawia się po raz pierwszy, długo przed tym, jak zrobi to wykres.
Nazwij pola, których oczekuje parser, w jednej stałej blisko początku pliku. Recenzent może wtedy zobaczyć wszystkie zależności od kształtu odpowiedzi na jednym ekranie.
Pierwsze uruchomienie, w kolejności
- 1
Przetestuj jedną domenę
Wywołaj
bulk_drz jedną znaną domeną i przeczytaj całą odpowiedź. Zapisz klucze przed napisaniem jakiegokolwiek kodu parsującego. - 2
Uruchom dwie partie
Daj skryptowi listę 400 domen, aby uruchomiły się dokładnie dwie partie. Sprawdź, czy spadek
_creditsodpowiada dwóm wywołaniom po jednym kredencie. - 3
Policz zapisane wiersze
Otwórz plik SQLite i porównaj liczbę wierszy z liczbą partii. Rozbieżność oznacza, że partia została pominięta lub zapisana dwukrotnie, i dowiesz się o tym przed dashboardem.
- 4
Zaplanuj pełną listę
Tylko po czystym uruchomieniu testów dodaj pełną listę i harmonogram. Zachowaj wyjscie niezerowe, aby awaria była głośna w pierwszą noc.
Przed zaplanowaniem go
- Najpierw usuń duplikaty i zamień listę na małe litery
- Zdecyduj raz, w jednej funkcji, czy
www.zostaje, czy znika - Przechowuj klucz w magazynie sekretów, a nie w crontab
- Alert przy kodzie wyjścia niezerowym
- Przelicz liczby budżetu kredytów dla swojego własnego wolumenu