Еженедельные снимки обратных ссылок в GitHub Actions: руководство
Цель: каждый понедельник workflow записывает сегодняшние реферальные домены и анкоры в репозиторий. Ничего больше. Сравнение данных — позже.
Редакция Linkzoid ·
Перед началом
Вам понадобятся репозиторий, ключ Linkzoid, сохранённый как секрет LZ_KEY, и один домен, который вам принадлежит. Бюджет: около 6 кредитов на цель за запуск, что составляет $0.0006. Снимок отражает состояние за один месяц, поэтому еженедельные запуски чаще всего повторно загружают тот же индекс. Настройка проверки обратных ссылок на мой сайт таким образом займёт один вечер.
Руководство по шагам
01
1. Добавьте секрет
Настройки репозитория → Secrets and variables → Actions. Назовите его LZ_KEY. Если вы вставите его в файл workflow, смените ключ сегодня.
02
2. Зафиксируйте скрипт
Сохраните приведённый ниже Python-скрипт как snap.py. Он записывает данные в data/YYYY-MM-DD/refdomains.json и завершается с ошибкой, если ok равно false.
03
3. Зафиксируйте workflow
Используйте приведённый ниже YAML. Запустите его один раз вручную через триггер workflow_dispatch и прочитайте лог, прежде чем доверять расписанию.
04
4. Проверьте первый файл
Откройте JSON. Подтвердите количество строк по полю total и сравните число с тем, что сообщает бесплатный вызов overview для вашего домена.
05
5. Добавьте оповещение
Завершившийся с ошибкой job по умолчанию отправляет письмо владельцу репозитория. Оставьте это включённым. Job, который тихо останавливается, — это обычный способ выхода из строя таких настроек.
snap.py
Стандартная библиотека. Цикл завершается по достижении счётчика, а не по догадке.
python
import json, os, sys, datetime, pathlib, urllib.request as u
KEY = os.environ['LZ_KEY']
TARGET = sys.argv[1]
def get(**p):
q = '&'.join(f'{k}={v}' for k, v in p.items())
url = f'https://linkzoid.pro/api.php?{q}&key={KEY}'
with u.urlopen(url, timeout=30) as r:
return json.load(r)
rows, page = [], 1
while True:
d = get(action='refdomains', q=TARGET, page=page, per=1000)
if not d['ok']:
sys.exit(f'bad response: {d}')
rows += d['rows']
if not d['rows'] or len(rows) >= d['total']:
break
page += 1
day = datetime.date.today().isoformat()
out = pathlib.Path('data') / day
out.mkdir(parents=True, exist_ok=True)
(out / 'refdomains.json').write_text(json.dumps(rows))
print(len(rows), 'rows, balance', d['_credits'])
.github/workflows/snapshot.yml
Cron в GitHub запускается в UTC и может начаться с задержкой, если раннеры загружены. Не стройте логику, зависящую от точности минуты.
Ключ хранится в секретах репозитория, а в YAML он упоминается только по имени.
Файл workflow находится на ветке по умолчанию, так как именно эту копию читает планировщик.
Ручной запуск через workflow_dispatch завершился и создал одну папку с датой.
Количество строк в файле совпадает с общим числом, которое вернул API для этого вызова.
Job завершается с ненулевым кодом при плохом ответе, поэтому ошибка окрашивает запуск в красный.
Ошибки и их исправление
Что ломается первым в запланированной задаче по обратным ссылкам
Ошибка
Что вы видите
Исправление
Ключ вставлен в YAML
Ключ виден в истории репозитория
Смените ключ, переместите его в secrets.LZ_KEY
Странная минута, например 0 0
Запуск начинается с задержкой в 20 минут
Выберите 17 6; начало часа перегружено
Репозиторий бездействует 60 дней
GitHub приостанавливает расписание
Совершите коммит или повторно включите расписание во вкладке Actions
Цикл останавливается только на пустой странице
Бесконечный запуск на лимите страниц
Также останавливайтесь, когда len(rows) >= total
Код выхода игнорируется
Зелёная галочка, пустая папка
Используйте sys.exit при ok: false
Коммит не проходит из-за отсутствия изменений
Красный запуск впустую
Добавьте || exit 0 после коммита
Безопасность ключа и репозитория
Ключ — единственная вещь в этой настройке, которую стоит защищать. Относитесь к нему как к паролю, потому что любой, кто им владеет, может потратить ваш баланс.
Запланированные задачи тихо «утекают». Ключ, вставленный в лог, workflow, скопированный в публичный репозиторий, или бывший коллега с правами записи — обычные пути утечки. Проверяйте каждый пункт раз в квартал.
Сменяйте ключ при уходе сотрудника
Когда сотрудник с доступом уходит, создайте новый ключ, обновите секрет и выведите старый из обращения. Затем подтвердите, что следующий запуск прошёл успешно. Один зелёный запуск после смены — самое дешёвое тестирование, которое вы проведёте за год. Запишите дату смены ключа в заметках репозитория, чтобы следующая смена не стала сюрпризом.
Ограничьте круг лиц, редактирующих workflow
Любой, кто может редактировать YAML, может вывести секрет в лог. Держите права записи краткими и проверяйте изменения в файле workflow так же, как вы проверяете код. Двое людей с правами записи обычно достаточно для небольшого репозитория.
Никогда не выводите URL запроса
Выводите баланс и количество строк, но не URL запроса. Ключ передаётся в строке запроса, поэтому строка отладки, выводящая URL, записывает ключ в лог. Если вам нужно поделиться запросом в issue, сначала замените ключ на заполнитель.
Что такое снимок и чем он не является
Что вы получаете — это ежемесячный снимок. Наш собственный краулер обновляет индекс примерно раз в месяц, поэтому два запуска в понедельник в течение одного месяца часто возвращают одни и те же строки. Это ожидаемо.
Еженедельный ритм работает. Он поддерживает привычку, и вы замечаете сбой в течение семи дней, а не тридцати. Если ссылка, которую вы построили во вторник, отсутствует в понедельник, подождите следующего обновления, прежде чем паниковать.
Сохраните первую папку как базовую. Каждое последующее сравнение будет ссылаться на неё, а папка, названная по дате, гораздо надёжнее памяти о том, как выглядели цифры прошлой весной. Будьте последовательны.
Добавление анкоры и топ-страниц
Одно действие на файл делает диффы читаемыми. Скопируйте цикл в snap.py, измените refdomains на anchors или toppages и запишите во второй файл. Каждый стоит 2 кредита.
Объединённый файл скрывает, какая часть изменилась, а рецензент, просматривающий pull request, хочет видеть одну концепцию на один дифф. Держите их отдельно. Если вы отслеживаете три домена, перебирайте короткий список в workflow с помощью матрицы, чтобы каждая цель получала свой job и свою ошибку.
Анкоры — это второй файл со своим лимитом в диффе, поэтому проверяйте их отдельно от реферальных доменов. Каждый запуск остаётся дешёвым и повторяемым, что и является главной идеей.
Чтение лога запуска
Откройте лог первого запуска. Сделайте это до того, как доверите расписанию. Ищите количество строк, баланс и строку коммита. Если какое-то из трёх отсутствует, задача сделала меньше, чем заявляет. Также ведите краткую заметку о балансе при каждом запуске, чтобы ежемесячные расходы были видны без открытия панели управления.
Зелёная галочка говорит мало. Она означает только, что процесс завершился чисто. Она не говорит о том, что файл содержит правильные строки, поэтому скрипт выводит собственное резюме. Не меняйте эти строки сводки между версиями скрипта, чтобы дифф двух логов показывал только реальные изменения.
Как выглядит здоровый лог
Здоровый запуск выводит одну строку с количеством строк и балансом кредитов, затем коммит с сегодняшней датой. Всё, что скрывает эти строки под лишним шумом, стоит внимательного рассмотрения.
Обнаружение тихого сбоя
Следите за пустой папкой, отсутствующим коммитом или количеством строк, отличающимся от total. Каждое из этих указаний на цикл или парсинг, а не на сам API. Сохраните первый здоровый лог как эталон для будущих сравнений.
Некоторые сбои оставляют папку с меньшим количеством строк, чем обычно. Сравните количество с предыдущей неделей, прежде чем радоваться зелёному запуску, потому что тихое усечение выглядит точно так же, как тихий месяц.
Безопасное добавление второй цели
Второй домен — это запись в матрице, а не копия всего workflow. Дайте каждой цели свой job, чтобы одна ошибка не скрывала другую. Держите каждый job отдельно. Проверьте, что пути включают имя домена, чтобы два запуска никогда не записывали данные в один файл.
Начните только со второй цели. Когда её папка будет выглядеть правильно, добавьте остальные. Матрица, которая ломается посередине, читается сложнее, чем два простых запуска.
Называйте папки по цели
Добавьте домен в путь, например data/<domain>/YYYY-MM-DD, и папки останутся отдельными, даже если два job запустятся в одну минуту. Старые папки можно оставить там, где они есть, так как дата в пути делает их легко находимыми.
Используйте одно и то же правило именования для всех целей. Скрипт сможет перечислять папки без специальных случаев.
Сначала спланируйте бюджет кредитов
Умножьте количество вызовов на цель на количество целей, затем проверьте общую сумму против своего баланса перед первым запуском. Матрица умножает расходы так же быстро, как и количество job. Запишите ожидаемый ежемесячный итог в репозитории рядом с workflow. Когда баланс падает быстрее, чем ожидается, цикл, вероятно, повторно загружает страницы, и вы можете остановить его до следующего запуска.
Вопросы, которые возникают в первую неделю
Могу ли я запустить это на приватном репозитории бесплатно?
Обычно да, в рамках ежемесячных минут, которые дают бесплатные аккаунты GitHub. Задача снимка выполняется менее чем за минуту, поэтому еженедельное расписание использует крошечную долю. Проверьте текущие лимиты вашего плана, так как GitHub время от времени их меняет.
Где должен храниться ключ, если несколько человек делают push?
В секрете организации или репозитория, никогда в файлах. Любой, кто может редактировать workflow, может вывести секрет в лог, поэтому ограничьте круг лиц с правами записи и смените ключ, если кто-то покинет проект.
Почему мой запланированный запуск не сработал?
Три обычные причины. Файл workflow не находится на ветке по умолчанию, репозиторий бездействовал 60 дней и GitHub приостановил расписание, или в строке cron есть опечатка. Запустите его один раз через workflow_dispatch, чтобы исключить сам скрипт.
Насколько большими становятся папки с данными?
Небольшими. Тысяча реферальных доменов — это несколько сотен килобайт JSON. Год еженедельных файлов для одного домена остаётся значительно меньше ста мегабайт, что нормально для git, хотя вы можете сжать старые папки, если это вас беспокоит.