Масовий DR для списку клієнтів як 40-рядковий Python-скрипт
У вас є таблиця з доменами та дашборд, якому потрібне одне число на рядок. Інструмент для перевірки доменів посилань для цієї задачі — це насамперед інфраструктура, і цей скрипт є саме нею.

Скрипт
Він читає domains.txt, надсилає по 200 доменів за запит, зберігає кожну сир відповідь у SQLite та виводить вартість запуску. Ми зберігаємо весь тіло відповіді, оскільки назви полів найкраще копіювати з реальної відповіді, а не з блогу.
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"))Що робить кожна частина
action=bulk_dr
Один кредит за виклик. Документальний ліміт — 2000 доменів за виклик.
domains=
GET-параметр. Ендпоінт також приймає тіло POST-запиту, що є кращим варіантом для довгих списків. Перевірте роздільник з двома доменами перед повним запуском.
BATCH = 200
Наша звичка, а не ліміт: 200 коротких доменів тримають URL значно нижче загальних лімітів проксі.
_credits
Присутній у відповідях з автентифікацією: вартість виклику та залишок. Виведення його кожного батчу — найдешевший шлях аудиту.
Стовпець сирого body
Ви парсите його пізніше, один раз, спираючись на збережений зразок. Зміна схеми з нашого боку коштуватиме вам лише запиту, а не повторного запуску.

Гіпотетичний запуск з арифметикою
Це приклад, а не історія клієнта. Уявіть агентство, яке відстежує 1800 доменів клієнтів та потенційних клієнтів у Google Sheets і хоче оновлювати DR щопонеділка.
При 200 доменах у батчі це 9 запитів. Дев'ять запитів по 1 кредиту — це 9 кредитів, або $0.0009 за запуск, а за 52 понеділки загальна сума становить 468 кредитів, що округлюється до менше п'яти центів.
Вартість не є обмежувальним фактором. Важливіша свіжість. Індекс перебудовується приблизно щомісяця, тому тижневі запуски показуватимуть однакові значення три тижні з чотирьох, а місячний запуск фіксує той самий рух за приблизно чверть кількості запитів.
Тож запускайте його щомісяця, а тижневу таблицю залишайте для ручних редагувань. Деталі щодо того, чому показник може залишатися незмінним, наведено в довіднику DR.
Що може піти не так у цьому скрипті
| Симптом | Причина | Виправлення |
|---|---|---|
SystemExit: HTTP 402 | Баланс нульовий | Поповніть баланс на сторінці облікового запису, запустіть знову; завершені батчі вже в SQLite |
SystemExit: HTTP 401 | Змінна середовища порожня або помилка в ключі | Виведіть len(KEY) та перевірте префікс sk_ |
ok=false на батчі 3 | Некоректне значення в списку, наприклад, повний URL | Видаліть схему та шлях перед надсиланням, див. таблицю помилок |
| Такий самий DR, як минулого тижня | Індекс ще не перебудовано | Очікуване; порівнюйте щомісяця |
| Довгий URL відхилено | Занадто великий батч для GET | Зменшіть BATCH або перейдіть на тіло POST-запиту |
Розмір батчу, GET та POST для довгих списків
Ліміт становить 2000 доменів за виклик, а скрипт надсилає одну десяту від цього значення. Кожний виклик коштує фіксований кредит, тому розмір батчу — це питання помилок та розміру корисного навантаження, а не ціни.
Чому 200, а не 2000
Менший батч дозволяє дешево обробити помилку. Якщо запуск зупиниться посередині, хвіст, який потрібно повторити, буде довжиною в один батч, а не в увесь список.
Коротші списки також роблять кожне збережене тіло достатньо малим для візуального огляду, коли одне число здається неправильним, і це важливіше за кілька заощаджених запитів.
Якщо пізніше ви збільшите розмір батчу, зробіть це один раз, змінивши одну константу, і знову запустіть тест з двома доменами. Більше число — не привід пропускати тест.
Плануйте батч, спираючись на допустиму тривалість запуску, а не на ліміт. Нічний джоб, який має завершитися до ранкового звіту, може дозволити собі менші, безпечніші батчі, ніж одноразове заповнення даних.
GET для коротких списків, POST для довгих
Форма GET розміщує кожен домен у URL. Це працює, доки проксі або рядок логу не обріжуть запит, чому таблиця вище радить спочатку зменшити батч. Коли списки зростають, надсилайте те саме значення domains в тілі POST-запиту.
Перевірте роздільник та кодування з двома доменами перед повним запуском. Додаткова кома проявиться в першому батчі, і дешевше побачити її саме там.
Тіло POST також тримає список поза логами доступу, які багато хостів зберігають за замовчуванням. Це невелика перевага в конфіденційності для списків клієнтів, і це коштує один додатковий рядок заголовка в скрипті.
Коли переходите на POST, залиште GET-версію скрипта як резервний варіант для коротких списків. Два шляхи коду звучать громіздко, але одна невелика функція, яка обирає один за довжиною, легко читається і легко тестується.
Обрізання та видалення дублікатів перед першим запитом
Видаліть пробіли, приведіть хост до нижнього регістру та видаліть дублікати до того, як дані залишать ваш комп'ютер. Дублікати займають місце під лімітом у 2000 доменів, і вони змушують кількість у вашій таблиці не збігатися з кількістю в відповіді.
Оберіть одне правило для www. і застосовуйте його в одній функції, щоб один і той самий сайт ніколи не шукався під двома написами. Будь-який інструмент для перевірки доменів посилань, який масштабується на сотні імен, потребує такої дисципліни.
Що має писати лог запуску
Виводьте значення _credits після кожного батчу та записуйте номер батчу, кількість доменів і час поруч. Ці три числа відповідають майже на всі питання, які фінансовий читач матиме щодо запуску.
Запуск, який нічого не виводить до кінця, не дає вам підказки, коли він завис. Рядок на батч є дешевим, і він перетворює тиху завислість на номер батчу, який можна назвати.
Зберігайте лог у тій самій папці, що й файл SQLite, щоб одна резервна копія зберігала обидва файли. Лог, який живе десь інакше, — це той, що загубиться під час інциденту.

Три розширення, варті післяобіднього часу
Зберігайте номер батчу поруч із міткою часу. Він уже є в таблиці вище, і він дозволяє запустити знову лише той батч, який зазнав невдачі через 402 під час виконання.
Напишіть невеликий перегляд над сировою таблицею, як тільки ви ознайомитеся з реальною відповіддю: домен, показник, час отримання. Дашборди читають цей перегляд, а сировий стовпець залишається вашим доказом, якщо число коли-небудь поставлять під сумнів.
Запускайте його з GitHub Actions або звичайного cron на маленькому VPS. Підходить будь-який варіант. Важливо, щоб джоб виходив з ненульовим кодом помилки при будь-якому збої, бо тиха помилка виглядатиме як застарілий графік через три тижні.
Сортування виводу — це питання фронтенду. Ранжируйте портфель за показником, згрупуйте його або порівняйте з dr_distribution (1 кредит), щоб побачити, де знаходиться кожен клієнт серед усіх 181 132 097 доменів.
Дрейф полів та сирова таблиця
Довготривалий джоб тихо падає, коли форма змінюється. Сировий стовпець існує саме для того, щоб цей збій залишався помітним і дешевим у виправленні.
Спочатку збережіть сире тіло
Сировий стовпець — це страховка. Якщо поле перейменовано або з'явилося нове, ви перебираєте запит по збережених відповідях, а не платите за весь список знову. Парсинг пізніше є дешевим, тоді як повторна покупка — ні.
Також зберігайте один зразок на тиждень у окремому файлі. Коли парсинг ламається, ви порівнюєте зразок цього тижця з останнім, що працював.
Записуйте тіло точно так, як отримали, без форматування та обрізання. Будь-яка зміна, яку ви вносите в текст перед збереженням, є зміною, яку ви не зможете скасувати пізніше.
Як виглядає дрейф на практиці
Уявімо, що дашборд раптово показує порожні показники для одного батчу. Сировий рядок досі там. Ви відкриваєте його, порівнюєте його ключі з зразком минулого тижня і знаходите різницю за кілька хвилин.
Виправлення тоді — це один рядок у перегляді, а не повторний запуск всього списку. Саме в цьому суть збереження тіла.
Тримайте короткий запис поруч із переглядом, який вказує, яке поле він читає і коли ви востаннє перевіряли його за реальною відповіддю. Запис займає хвилину, але економить довгий пошук пізніше.
Порядок міграцій з даними
Створіть сирову таблицю перед переглядом, а перегляд — перед будь-яким запитом дашборда. Тримайте оператори створення в одному файлі, запускайте їх у порядку і записуйте номер батчу з кожною рядком.
Роздільні бази даних для клієнтів дрейфують так само, як і одна спільна база даних. Правило залишається тим самим: одне збережене тіло за виклик і одне місце для його парсингу.
Тримайте парсер поруч із тестами
Помістіть функцію парсингу в той самий репозиторій, що й тест, який читає одне збережене тіло. Тест — це місце, де дрейфне поле проявиться вперше, задовго до того, як це побачить графік.
Вкажіть назви полів, яких очікує парсер, в одній константі біля початку файлу. Тоді рецензент зможе побачити всі залежності від форми відповіді на одному екрані.
Перший запуск по порядку
- 1
Протестуйте один домен
Викличте
bulk_drз одним відомим вам доменом і прочитайте всю відповідь. Запишіть ключі, перш ніж писати код парсингу. - 2
Запустіть два батчі
Надайте скрипту список з 400 доменів, щоб запустилося рівно два батчі. Перевірте, чи збігається падіння
_creditsз двома викликами по одному кредиту. - 3
Підрахуйте збережені рядки
Відкрийте файл SQLite і порівняйте кількість рядків із кількістю батчів. Невідповідність означає, що батч був пропущений або збережений двічі, і ви дізнаєтеся про це до того, як це побачить дашборд.
- 4
Заплануйте повний список
Тільки після успішних тестових запусків додайте повний список і планувальник. Тримайте ненульовий вихід, щоб помилка була помітною з першої ночі.
Перед плануванням
- Спочатку видаліть дублікати та приведіть список до нижнього регістру
- Вирішіть раз і назавжди в одній функції, чи залишається
www., чи ні - Тримайте ключ у сховищі секретів, а не в crontab
- Сповіщайте про ненульовий код виходу
- Прорахуйте числа бюджету кредитів для вашого власного обсягу